Finding median without using a sort function - python

As a homework assignment I have to write a script which finds the median of 3 given numbers without using a standard sort function of Python.
This is my first week in class and my first programming experience so I find it very difficult to get any further than I am right now.
Here's what I have so far:
def med3(a,b,c):
list = [a, b, c]
newlist = []
if list:
minimum = list[0]
for x in list:
if x < minimum:
minimum = x
newlist.append(minimum)
list.remove(minimum)
elif x >= minimum:
newlist.append(x)
list.remove(x)
return newlist[1]
This seems to do the trick, but only for the first two entries of the list. The loop doesn't include the third entry.
How can I make the script include all three entries?
Thanks in advance!
Sander

sum([a, b, c]) - min(a, b, c) - max(a, b, c) - no sorting!

You are modifying the list in-place while looping over it, which has consequences for what elements you see:
>>> numbers = [1,2,3]
>>> for i in numbers:
... if i == 2: numbers.remove(i)
... print i
...
1
2
Note how 3 is never printed; by removing the second entry in the list, we've shortened it by one element and the loop finds the list exhausted early.
Note that you don't need to loop over the items, a few simple comparisons will tell you what item is the median if you think about it for a second. :-)

There are a number of simpler ways to go about this, but as for your approach:
You're modifying list inside of your loop. Don't do that. :)
In your case, you should be removing elements from newlist:
def med3(a,b,c):
list = [a, b, c]
newlist = []
if list:
minimum = list[0]
for x in list:
if x < minimum:
minimum = x
newlist.pop()
newlist.append(minimum)
elif x >= minimum:
newlist.append(x)
return newlist[1]
But as an exercise, you might want to think about a few things:
Why are you putting the elements in a list and looping over them? What advantage does this have over comparing a,b,c with simply if statements?
Why the if list:?

The fastest way to do it:
def medianFast(a, b, c):
if a > b:
if b > c:
return b
elif a > c:
return c
else:
return a
else:
if b < c:
return b
elif a > c:
return a
else:
return c
Guarantees you 3 comparisons at the worst case and 2 comparisons in the best case. 2,5 comparisons in average.
Using ternary conditional we can write it shorter as:
def medianTernary(a, b, c):
return (b if b > c else (c if a > c else a)) if a > b else (b if b < c else (a if a > c else c))
If you could use sorting you would have the shortest version:
def medianSorted(a, b, c):
return sorted([a, b, c])[1]

Related

How to remove repetition when there is a common condition [duplicate]

This question already has answers here:
How to compare multiple variables to the same value?
(7 answers)
Closed 1 year ago.
How do i remove the repetitions
if a < 2 or b < 2 or c < 2 or d < 2:
pass #logic
I want to remove the repetitions. Something like this:
if (a,b,c,d) < 2:
pass #logic
Another slightly different way is to check if the smallest variable is less than the target value. In other words, if the smallest variable is not less than the target - then surely none of them is:
if min(a, b, c, d) < 2:
# do something
This however needs to actually find the minimal value behind the scenes and loses the short-circuiting advantage of using any with a generator expression.
I wouldn't import numpy just for this as other answers suggest. It's probably overkill.
I would do something like:
condition = lambda x: x < 2
if any(condition(x) for x in (a, b, c, d)):
# whatever you want
Or more compact (but less reusable):
if any(x < 2 for x in (a, b, c, d)):
# whatever you want
When using numpy this can be done with .all() and .any():
import numpy as np
array = np.asarray([1,2,3])
if (array < 2).any():
print('True')
else:
print('False')
#output: True
if (array < 2).all():
print('True')
else:
print('False')
#output: False
In addition to other answers, use generator expression to reduce computing time
if True in (i < 2 for i in [a,b,c,d]):
pass #logic
I would have used combination of Numpy and any here.
For example:
a, b, c, d = 10, 10, 10, 10
Convert it into a numpy array:
import numpy as np
arr = np.array([a, b, c, d])
And now you can do this:
if any(arr < 2):
pass #logic
Use built-in any function:
if any(map(lambda x: x < 2, (a, b, c, d))):
pass # whatever here

Why is my function so slow for specific values?

Beginner programmer here!
So I'm trying to create functions for tetration using a recursive formula. I use that same function tetrate_F in finding the nth super root of some big number.
In sprRt(a, b), when b gets a tiny bit too high the function seems to slow down by a ton. I find that 2,3 and 5 works for pretty much any value of a. But when b is set to 4 or any other value it just takes so damn long for some reason.
def tetrate_F(a, b):
if b == 0:
return b+1
else:
res = a ** tetrate_F(a, b-1)
return(res)
def sprRt(a, b):
l_head = []
lt0 = []
if b == 1:
print("Undefined")
elif b > 1:
for j in range(0, a+1):
if tetrate_F(j, b) < a:
lt0.append(j)
elif tetrate_F(j, b) > a:
break
val0 = lt0[-1]
lt0.clear()
l_head.append(val0)
print(val0)
sprRt(1234567890, 4)
Like in sprRt(1234567890, 2), I can use 2,3 or even 5 for b and get a result almost immediately but not any other number.
Help will be much appreciated thx!

Given two strings, how do I assign the shorter string to one variable and the longer string to another

I am learning python and would like to know if there is a pythonic way of doing this. Of course I can do an
if len(a) > len(b) :
(x,y) = (b,a)
and so on. But this seems a bit verbose. Is there a more beautiful way to do this in python?
Sorting seems a bit overkill. You can do it in one line just with if.
x,y = (a,b) if len(a) < len(b) else (b,a)
x = min(a, b, key=len)
y = max(a, b, key=len)
Repetition, but intention is clear.
Edit:
Removed parentheses that makes tuples out of a and b since min and max takes in variable number of arguments.
Found one way.
(x,y) = sorted([a,b], key = len)
You can use sequence unpacking with min / max. This is still O(n) complexity, albeit a 2-pass solution, which doesn't involve creating an intermediary list or tuple of strings.
a = 'hello'
b = 'test'
x, y = (func(a, b, key=len) for func in (min, max))
print(x, y, sep='\n')
# test
# hello
Here's a functional version:
from operator import methodcaller
x, y = map(methodcaller('__call__', a, b, key=len), (min, max))
Use min(n1, n2, n3, ...)
>>> n1 = "abcdefghijkl"
>>> n2 = "abc"
>>> min((n1,n2), key=len)
'abc'
>>> max((n1,n2), key=len)
'abcdefghijkl'
The question seems really opinion-based, and for this reason I think we should define beautiful referring to the Zen of Python, in particular:
Flat is better than nested.
Sparse is better than dense.
Readability counts.
For these reasons, I think that your approach:
if len(a) > len(b) :
(x,y) = (b,a)
else:
(x,y) = (a,b)
is the best one.
Just a demo how dict can also be used
Solution 1:
a = [3]
b = [2,3 ]
_bool = len(a)<len(b)
d = { True : a, False : b}
x = d[_bool]
y = d[not _bool]
print(x, y)
Using Lists:
Solution 2:
x, y = [[a, b], [b, a]][len(a) > len(b)]
Although my preferred approach is
x, y = sorted([a, b], key=len)

How to check if sum of 3 integers in list matches another integer? (python)

Here's my issue:
I have a large integer (anywhere between 0 and 2^32-1). Let's call this number X.
I also have a list of integers, unsorted currently. They are all unique numbers, greater than 0 and less than X. Assume that there is a large amount of items in this list, let's say over 100,000 items.
I need to find up to 3 numbers in this list (let's call them A, B and C) that add up to X.
A, B and C all need to be inside of the list, and they can be repeated (for example, if X is 4, I can have A=1, B=1 and C=2 even though 1 would only appear once in the list).
There can be multiple solutions for A, B and C but I just need to find one possible solution for each the quickest way possible.
I've tried creating a for loop structure like this:
For A in itemlist:
For B in itemlist:
For C in itemlist:
if A + B + C == X:
exit("Done")
But since my list of integers contains over 100,000 items, this uses too much memory and would take far too long.
Is there any way to find a solution for A, B and C without using an insane amount of memory or taking an insane amount of time? Thanks in advance.
you can reduce the running time from n^3 to n^2 by using set something like that
s = set(itemlist)
for A in itemlist:
for B in itemlist:
if X-(A+B) in s:
print A,B,X-(A+B)
break
you can also sort the list and use binary search if you want to save memory
import itertools
nums = collections.Counter(itemlist)
target = t # the target sum
for i in range(len(itemlist)):
if itemlist[i] > target: continue
for j in range(i+1, len(itemlist)):
if itemlist[i]+itemlist[j] > target: continue
if target - (itemlist[i]+itemlist[j]) in nums - collections.Counter([itemlist[i], itemlist[j]]):
print("Found", itemlist[i], itemlist[j], target - (itemlist[i]+itemlist[j]))
Borrowing from #inspectorG4dget's code, this has two modifications:
If C < B then we can short-circuit the loop.
Use bisect_left() instead of collections.Counter().
This seems to run more quickly.
from random import randint
from bisect import bisect_left
X = randint(0, 2**32 - 1)
itemset = set(randint(0,X) for _ in range(100000))
itemlist = sorted(list(itemset)) # sort the list for binary search
l = len(itemlist)
for i,A in enumerate(itemlist):
for j in range(i+1, l): # use numbers above A
B = itemlist[j]
C = X - A - B # calculate C
if C <= B: continue
# see https://docs.python.org/2/library/bisect.html#searching-sorted-lists
i = bisect_left(itemlist, C)
if i != l and itemlist[i] == C:
print("Found", A, B, C)
To reduce the number of comparisons, we enforce A < B < C.

Removing common elements in two lists

I have two sorted lists of positive integers which can have repeated elements and I must remove matching pairs of numbers, one from each list:
a=[1,2,2,2,3]
b=[2,3,4,5,5]
should become:
a=[1,2,2]
b=[4,5,5]
That is, the 2's and the 3's have been removed because they appear in both lists.
Set intersection can't be used here because of the repeated elements.
How do I go about this?
To remove elements appearing in both lists, use the following:
for i in a[:]:
if i in b:
a.remove(i)
b.remove(i)
To create a function which does it for you, simply do:
def removeCommonElements(a, b):
for e in a[:]:
if e in b:
a.remove(e)
b.remove(e)
Or to return new lists and not to edit the old ones:
def getWithoutCommonElements(a, b): # Name subject to change
a2 = a.copy()
b2 = b.copy()
for e in a:
if e not in b:
a2.remove(e)
b2.remove(e)
return a2, b2
However the former could be replaced with removeCommonElements like so:
a2, b2 = a.copy(), b.copy()
removeCommonElements(a2, b2)
Which would keep a and b, but create a duplicates without common elements.
The Counter object from collections can do this quite concisely:
from collections import Counter
a=Counter([1,2,2,2,3])
b=Counter([2,3,4,5,5])
print list((a-b).elements())
print list((b-a).elements())
The idea is:
Count up how often each element appears (e.g. 2 appears 3 times in a, and 1 time in b)
Subtract the counts to work out how many extra times the element appears (e.g. 2 appears 3-1=2 times more in a than b)
Output each element the extra number of times it appears (the collections elements method automatically drops any elements with counts less than 1)
(Warning: the output lists won't necessarily be sorted)
Given that the lists are sorted, you can merge/distribute element-wise, like for example:
x, y = [], []
while a and b:
if a[0] < b[0]:
x.append(a.pop(0))
elif a[0] > b[0]:
y.append(b.pop(0))
else: # a[0]==b[0]
a.pop(0)
b.pop(0)
x += a
y += b
The solution given by #Mahi is nearly correct. The simplest way to achieve what you want is this:
def remove_common_elements(a, b):
for i in a[:]:
if i in b:
a.remove(i)
b.remove(i)
return a, b
The important thing here is to make a copy of a by writing a[:]. If you iterate through a list while removing elements from it, you won't get correct results.
If you don't want to modify the lists in place, make a copy of both lists beforehand and return the copied lists.
def remove_common_elements(a, b):
a_new = a[:]
b_new = b[:]
for i in a:
if i in b_new:
a_new.remove(i)
b_new.remove(i)
return a_new, b_new
One solution would be to create a new copy of a and removing common elements from b.
a = [1,2,2,2,3]
b = [2,2,3,4,5]
a_new = []
for ai in a:
if ai in b:
b.remove(ai)
else:
a_new.append(ai)
print a_new
print b

Categories

Resources