Min-max python v3 implementation - python

I'm currently trying to code an equivalent for the built-in min-max function in python, and my code return a pretty weird exception which I don't understand at all:
TypeError: 'generator' object is not subscriptable, min, 7, , 9
when i try it with:
min(abs(i) for i in range(-10, 10))
Here is my code:
def min(*args, **kwargs):
key = kwargs.get("key", None)
argv=0
for i in args:
argv+=1
if argv == 1 and (type(args) is list or type(args) is tuple or type(args) is str):
min=args[0][0]
for i in args[0]:
if key != None:
if key(i) < key(min):
min = i
else:
if i < min:
min = i
return min
else:
min=args[0]
for i in args:
if key != None:
if key(i) < key(min):
min = i
else:
if i < min:
min = i
return min
According to the documentation, i should be able to iterate over a generator...

Here is my implementation:
def max(*args, **kwargs):
key = kwargs.get("key", lambda x: x)
if len(args) == 1:
args = args[0]
maxi = None
for i in args:
if maxi == None or key(i) > key(maxi):
maxi = i
return maxi
def min(*args, **kwargs):
key = kwargs.get("key", lambda x: x)
if len(args) == 1:
args = args[0]
mini = None
for i in args:
if mini == None or key(i) < key(mini):
mini = i
return mini
A little bit more concise than preview post.

The issue you are having is due to the fact that min has two function signatures. From its docstring:
min(...)
min(iterable[, key=func]) -> value
min(a, b, c, ...[, key=func]) -> value
So, it will accept either a single positional argument (an iterable, who's values you need to compare) or several positional arguments which are the values themselves. I think you need to test which mode you're in at the start of your function. It is pretty easy to turn the one argument version into the multiple argument version simply by doing args = args[0].
Here's my attempt to implement the function. key is a keyword-only argument, since it appears after *args.
def min(*args, key=None): # args is a tuple of the positional arguments initially
if len(args) == 1: # if there's just one, assume it's an iterable of values
args = args[0] # replace args with the iterable
it = iter(args) # get an iterator
try:
min_val = next(it) # take the first value from the iterator
except StopIteration:
raise ValueError("min() called with no values")
if key is None: # separate loops for key=None and otherwise, for efficiency
for val in it: # loop on the iterator, which has already yielded one value
if val < min_val
min_val = val
else:
min_keyval = key(min_val) # initialize the minimum keyval
for val in it:
keyval = key(val)
if keyval < min_keyval: # compare keyvals, rather than regular values
min_val = val
min_keyval = keyval
return min_val
Here's some testing:
>>> min([4, 5, 3, 2])
2
>>> min([1, 4, 5, 3, 2])
1
>>> min(4, 5, 3, 2)
2
>>> min(4, 5, 3, 2, 1)
1
>>> min(4, 5, 3, 2, key=lambda x: -x)
5
>>> min(4, -5, 3, -2, key=abs)
-2
>>> min(abs(i) for i in range(-10, 10))
0

Functions in question have a lot in common. In fact, the only difference is comparison (< vs >). In the light of this fact we can implement generic function for finding and element, which will use comparison function passed as an argument. The min and max example might look as follows:
def lessThan(val1, val2):
return val1 < val2
def greaterThan(val1, val2):
return val1 > val2
def find(cmp, *args, **kwargs):
if len(args) < 1:
return None
key = kwargs.get("key", lambda x: x)
arguments = list(args[0]) if len(args) == 1 else args
result = arguments[0]
for val in arguments:
if cmp(key(val), key(result)):
result = val
return result
min = lambda *args, **kwargs: find(lessThan, *args, **kwargs)
max = lambda *args, **kwargs: find(greaterThan, *args, **kwargs)
Some tests:
>>> min(3, 2)
2
>>> max(3, 2)
3
>>> max([1, 2, 0, 3, 4])
4
>>> min("hello")
'e'
>>> max(2.2, 5.6, 5.9, key=int)
5.6
>>> min([[1, 2], [3, 4], [9, 0]], key=lambda x: x[1])
[9, 0]
>>> min((9,))
9
>>> max(range(6))
5
>>> min(abs(i) for i in range(-10, 10))
0
>>> max([1, 2, 3], [5, 6], [7], [0, 0, 0, 1])
[7]

Related

Point values not being handled correctly

I have a PointND class representing a point in N-dimensional space (shown below).
class PointND:
"""
Class to represent an N-dimensional point.
"""
def __init__(self, *vals):
if len(vals) == 1 and isinstance(vals, list):
self.vals = vals[0]
else:
self.vals = vals
self.point = []
self.dimensions = len(self.vals)
for i in self.vals:
self.point.append(i)
This works fine when I give it a series of inputs (ex: PointND(1, 2, 3, 4)). However, whenever I give it a list of inputs (ex: PointND([1, 2, 3, 4])) it breaks.
I have the __repr__ function defined as:
def __repr__(self):
__a = f"{self.dimensions}D Point: ({self.vals[0]}"
for i in range(1, len(self.vals)):
__a = f"{__a}, {self.vals[i]}"
__a = f"{__a})"
return __a
And when I do print(PointND(1, 2, 3, 4, 5)), it outputs:
> 4D Point: (1, 2, 3, 4)
However when I simply change it to: print(PointND([1, 2, 3, 4, 5])), it outputs:
> 1D Point: ([1, 2, 3, 4])
I don't know why it does this, but if anyone knows and/or has a fix, please let me know!
Here you probably meant to check the type of vals[0], not vals.
def __init__(self, *vals):
- if len(vals) == 1 and isinstance(vals, list):
+ if len(vals) == 1 and isinstance(vals[0], list):
self.vals = vals[0]
else:
self.vals = vals
(vals will never be a list, it will always be a tuple)
I would argue that this is a bad interface: it lets you express the same thing in two different ways. I would pick one way of creating a point:
# a) accept varargs
def __init__(self, *vals):
self.point = list(vals)
self.dimensions = len(vals)
# b) accept a list (or any iterable, really)
def __init__(self, vals):
self.point = list(vals)
self.dimensions = len(vals)

Loops and Dictionary

How to loop in a list while using dictionaries and return the value that repeats the most, and if the values are repeated the same amount return that which is greater?
Here some context with code unfinished
def most_frequent(lst):
dict = {}
count, itm = 0, ''
for item in lst:
dict[item] = dict.get(item, 0) + 1
if dict[item] >= count:
count, itm = dict[item], item
return itm
#lst = ["a","b","b","c","a","c"]
lst = [2, 3, 2, 2, 1, 3, 3,1,1,1,1] #this should return 1
lst2 = [2, 3, 2, 2, 1, 3, 3] # should return 3
print(most_frequent(lst))
Here is a different way to go about it:
def most_frequent(lst):
# Simple check to ensure lst has something.
if not lst:
return -1
# Organize your data as: {number: count, ...}
dct = {}
for i in lst:
dct[i] = dct[i] + 1 if i in dct else 1
# Iterate through your data and create a list of all large elements.
large_list, large_count = [], 0
for num, count in dct.items():
if count > large_count:
large_count = count
large_list = [num]
elif count == large_count:
large_list.append(num)
# Return the largest element in the large_list list.
return max(large_list)
There are many other ways to solve this problem, including using filter and other built-ins, but this is intended to give you a working solution so that you can start thinking on how to possibly optimize it better.
Things to take out of this; always think:
How can I break this problem down into smaller parts?
How can I organize my data so that it is more useful and easier to manipulate?
What shortcuts can I use along the way to make this function easier/better/faster?
Your code produces the result as you describe in your question, i.e. 1. However, your question states that you want to consider the case where two list elements are co-equals in maximum occurrence and return the largest. Therefore, tracking and returning a single element doesn't satisfy this requirement. You need to compile the dict and then evaluate the result.
def most_frequent(lst):
dict = {}
for item in lst:
dict[item] = dict.get(item, 0) + 1
itm = sorted(dict.items(), key = lambda kv:(-kv[1], -kv[0]))
return itm[0]
#lst = ["a","b","b","c","a","c"]
lst = [2, 3, 2, 2, 2, 2, 1, 3, 3,1,1,1,1] #this should return 1
lst2 = [2, 3, 2, 2, 1, 3, 3] # should return 3
print(most_frequent(lst))
I edited the list 'lst' so that '1' and '2' both occur 5 times. The result returned is a tuple:
(2,5)
I reuse your idea which is quite neat, and I just modified your program a bit.
def get_most_frequent(lst):
counts = dict()
most_frequent = (None, 0) # (item, count)
ITEM_IDX = 0
COUNT_IDX = 1
for item in lst:
counts[item] = counts.get(item, 0) + 1
if most_frequent[ITEM_IDX] is None:
# first loop, most_frequent is "None"
most_frequent = (item, counts[item])
elif counts[item] > most_frequent[COUNT_IDX]:
# if current item's "counter" is bigger than the most_frequent's counter
most_frequent = (item, counts[item])
elif counts[item] == most_frequent[COUNT_IDX] and item > most_frequent[ITEM_IDX]:
# if the current item's "counter" is the same as the most_frequent's counter
most_frequent = (item, counts[item])
else:
pass # do nothing
return most_frequent
lst1 = [2, 3, 2, 2, 1, 3, 3,1,1,1,1, 2] # 1: 5 times
lst2 = [2, 3, 1, 3, 3, 2, 2] # 3: 3 times
lst3 = [1]
lst4 = []
print(get_most_frequent(lst1))
print(get_most_frequent(lst2))
print(get_most_frequent(lst3))
print(get_most_frequent(lst4))

Return the only element in the hash table in Python

I am working on a problem as:
In a non-empty array of integers, every number appears twice except for one, find that single number.
I tried to work it out by the hash table:
class Solution:
def singleNumber(self, array):
hash = {}
for i in array:
if i not in hash:
hash[i] = 0
hash[i] += 1
if hash[i] == 2:
del hash[i]
return hash.keys()
def main():
print(Solution().singleNumber([1, 4, 2, 1, 3, 2, 3]))
print(Solution().singleNumber([7, 9, 7]))
main()
returning the result as:
dict_keys([4])
dict_keys([9])
Process finished with exit code 0
I am not sure if there is any way that I can return only the number, e.g. 4 and 9. Thanks for your help.
Instead of return hash.keys() do return hash.popitem()[0] or return list(hash.keys())[0].
Of course this assumes that there is at least one pair in the hashmap. You can check for this using len(hash) > 0 before accessing the first element:
class Solution:
def singleNumber(self, array):
hash = {}
for i in array:
if i not in hash:
hash[i] = 0
hash[i] += 1
if hash[i] == 2:
del hash[i]
return hash.popitem()[0] if len(hash) > 0 else -1 # or throw an error
One solution that might be simpler is to use the .count method.
myList = [1, 4, 2, 1, 3, 2, 3]
non_repeating_numbers = []
for n in myList:
if myList.count(n) < 2:
non_repeating_numbers.append(n)
Applied to your code it could look something like this:
class Solution:
def singleNumber(self, array):
for n in array:
if array.count(n) < 2:
return n
def main():
print(Solution().singleNumber([1, 4, 2, 1, 3, 2, 3]))
print(Solution().singleNumber([7, 9, 7]))

How do I check if a parameter given to a function is a list?

I will like the function to return a comment if the parameter given to a not a list. If it's a list, I'll like to perform some operation.
below is the code:
def manipulate_data(*num):
if type(num) is not list:
return "Only lists allowed"
else:
positive = 0
for n in num:
if n >= 0:
positive = positive + 1
By using the *nums argument you are packaging all the arguments into a list. The official documentation may be useful.
manipulate_data(1, 2, 3, 4, 5) will result in num = (1, 2, 3, 4, 5) while manipulate_data([1, 2, 3, 4, 5])will result in num = ([1, 2, 3, 4, 5],) that is a tuple with only one element. Remove the * or if needing to process multiple lists as arguments use a loop to do the check on every tuple element.
def manipulate_data(num):
if type(num) is not list:
return "Only lists allowed"
else:
positive = 0
for n in num:
if n >= 0:
positive += 1
return positive
def manipulate_multiple(*nums):
for num in nums:
manipulate_data(num)
Remember this function is not returning anything yet
ok, make it more clean, except a, b ,all left positional parameter will be pass to c
>>> def func(a, b, *c):
... print a
... print b
... print c
...
>>> func(1, 2, 3, 4, 5)
1
2
(3, 4, 5)

Freezing all but one positional arguments

What's a good way to define a function partial_k(f, k, args) that takes an arbitrary function f as input (f takes n positional arguments), a value k, and a list of n-1 values, and returns a new function that freezes all the arguments of f except the k-th argument?
For example:
def f(a, b, c):
return (a, b, c)
assert partial_k(f, 2, [0, 1])(10) == (0, 1, 10) # lambda x: (0, 1, x)
assert partial_k(f, 1, [0, 1])(10) == (0, 10, 1) # lambda x: (0, x, 1)
I could only find some very verbose ways of doing that.
You can use a wrapper function and pass the arguments before and after kth item using slicing to the original function f:
def partial_k(f, k, seq):
seq = tuple(seq) # To handle any iterable
def wrapper(x):
return f(*(seq[:k] + (x,) + seq[k:]))
return wrapper
print(partial_k(f, 2, [0, 1])(10))
print(partial_k(f, 1, [0, 1])(10))
Output:
(0, 1, 10)
(0, 10, 1)
For Python 3.5+:
def partial_k(f, k, seq):
def wrapper(x):
return f(*seq[:k], x, *seq[k:])
return wrapper
You could probably use things from functools package to simplify further, but basically:
def make_partial(f, args, k):
def func(x):
new_args = args[:k] + [x] + args[k:]
return f(*new_args)
return func

Categories