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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.