2

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...

5
  • 1
    You can iterate over generators. You can't do generator[0] ("not subscriptable"). It complains about min=args[0][0]. Commented Aug 11, 2014 at 18:07
  • But how can I set a default value to min if i can't pick it up in its values ? Commented Aug 11, 2014 at 18:22
  • Set it to None first and then check if it is None when iterating over the generator (so you can set it to the first element) and afterwards (if there are no elements). Commented Aug 11, 2014 at 18:23
  • Just so you know, args will always be a tuple, so your if block will always be entered, and your else block will never be entered. Furthermore, since both if and else have an unconditional return inside them, your first for loop will never get past the first iteration. You may as well remove it, and the argv check as well. Commented Aug 11, 2014 at 18:25
  • ok thank you, i didn't know, i'll try hlt solution and change my code, Commented Aug 11, 2014 at 18:27

3 Answers 3

3

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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 

1 Comment

It is a pretty elegant way to do it, I must admit i wouldn't have think to it this way. But i don't understand why you create an iterator with iter(args)... args is already an iterator isn't it ?
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] 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.