summer_tree_home

Check iOでPython3をマスターするぜっ

Min and Max (Home) - min,max関数を実装

またまたHome島に新しい問題が追加されていた。

どんな問題?

Min and Max
http://www.checkio.org/mission/min-max/

ビルトインのmin(),max()関数を実装せよ。
ただし、import, eval, exec, globalsは使ってはならない。
オプションのkey引数にも対応させること。

ビルトインのmin/maxのように、複数の引数だったり、リスト、文字列、ジェネレータなどが渡される。
オプション引数のkeyが指定される場合もある。

例題:

max(3, 2) == 3
min(3, 2) == 2
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]

どうやって解く?

オプションのkey引数は、デフォルトでNoneなので、Noneなら値をそのまま返すように、

if key is None:
    key = labmda x: x

としておいた。

また、argsは可変長引数なので、引数が一つなら args[0]に複数の要素が入っていると考える。
あとは、要素を1つ1つ比較して最大値または最小値を求める。

minとmaxでは、要素の比較部分だけが違うので、比較用の関数をlambda式で渡すようにする。

def min_or_max(args, key, func):
    if key is None:
        key = lambda x: x

    if len(args) == 1:
        args = args[0]

    result = None
    for arg in args:
        if result is None:
            result = arg
        elif func(key(arg), key(result)):
            result = arg
    return result

def min(*args, key=None):
    return min_or_max(args, key, lambda a, b: a.__lt__(b))

def max(*args, key=None):
    return min_or_max(args, key, lambda a, b: a.__gt__(b))

http://www.checkio.org/mission/min-max/publications/natsuki/python-3/first/

あっ、a.__lt__(b) のところは、a < b でいいのに。なんで__lt__を使ったんだろう??? orz

他の人の答え

sortを使うとか、iterを使うとか、いろいろな解答があって面白い。

気になったのが、こちらのコメント欄でのやりとり。
http://www.checkio.org/mission/min-max/publications/Sim0000/python-3/first/

ようするに、1つの要素に対して、key(~) を何度も呼び出すのは良くないという内容。私の書いたコードでも、key(result)は何度も呼び出されるので、以下のテストを試すと AssertionError となってしまう。

x = iter([1,2,3])
y = iter([4,5,6])
z = iter([7,8,9])
least = min(x, y, z, key=next)
assert len(list(x)) == 2
assert len(list(y)) == 2
assert len(list(z)) == 2

これを避けるには、1つの要素につき、key(~) を1度しか使わないようにすればいい。書き直してみた。

def min_or_max(args, key, func):
    if key is None:
        key = lambda x: x

    if len(args) == 1:
        args = args[0]

    result_key = None
    for arg in args:
        arg_key = key(arg)
        if result_key is None or func(arg_key, result_key):
            result, result_key = arg, arg_key
    return result

def min(*args, key=None):
    return min_or_max(args, key, lambda a, b: a < b)

def max(*args, key=None):
    return min_or_max(args, key, lambda a, b: a > b)