• not understanding the result of functools.reduce

    From Ethan Carter@ec1828@somewhere.edu to comp.lang.python on Fri Aug 22 11:46:34 2025
    From Newsgroup: comp.lang.python

    # -*- mode: python; python-indent-offset: 2 -*-
    from functools import reduce

    ## I'm puzzle with this result of reduce. Can you spot where my mind
    ## is going wrong?
    ##
    ## The problem I want to solve with /reduce/ (merely for the sake of
    ## exercise) is to collapse a list of lists to a list of booleans.
    ## The lists given as input can contain strings or integers. The
    ## output value for each list will be True if the input list contains
    ## an integer; False otherwise. For example, the desired
    ## transformation is
    ##
    ## [["a","b"], ["a", 1]] --> [False, True]
    ##
    ## because only the second list has an integer. The exercise is to
    ## use only map, filter and reduce. The problem can be solved by
    ## mapping a function that reduces each list to the desired boolean.

    ## Here's the scheme of a solution:

    def solution(ls):
    return list(map(transform, ls))

    ## To transform each list, we can apply reduce with a suitable binary
    ## operator here called /op/

    def transform(ls):
    return reduce(op, ls, False)

    ## The question is how to write such an operator. I wrote this one:

    def op(x, b):
    return isinstance(x, int) or b

    ## My hand computation produces the correct answer, but when I invoke
    ## functools.reduce, I get [True, True, ...] no matter what. For
    ## example:

    ## >>> solution([[""],["aa",1]])
    ## [True, True]

    ## which is not correct. When I write my own reduce function, I get
    ## the expected results. Here's two implementations of my
    ## understanding of reduce:

    def reduc1(op, ls, init):
    if ls == []:
    return init
    return op(ls[0], reduc1(op, ls[1:], init))

    def reduc2(op, ls, init):
    r = init
    for x in ls:
    r = op(x, r)
    return r

    ## They both compute the expected result:
    ##
    ## >>> list(map(lambda ls: reduc1(op, ls, False), [[""], ["aa",1]]))
    ## [False, True]
    ##
    ## >>> list(map(lambda ls: reduc2(op, ls, False), [[""], ["aa",1]]))
    ## [False, True]
    ##
    ## Can you help? Thanks so much.
    --- Synchronet 3.21a-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python on Fri Aug 22 15:30:02 2025
    From Newsgroup: comp.lang.python

    Ethan Carter <ec1828@somewhere.edu> wrote or quoted:
    ## The question is how to write such an operator. I wrote this one:
    def op(x, b):
    return isinstance(x, int) or b

    The main snag with your op function and how you're using
    reduce is the switch-up in the argument order that "reduce"
    expects versus how you set up op.

    "functools.reduce" calls the reducer function "op" with two
    arguments: the accumulated value so far first, then the
    next element from the iterable.

    You wrote "op" as "op(x, b)", where you treat "x" like an element
    and "b" like a boolean accumulator, but actually "reduce" hands
    the accumulator first, then the element.

    So when "reduce" runs, the first argument "x" is actually the boolean
    accumulator, and the second "b" is the next element.

    The fix:

    You want to flip your operator function so the accumulator is
    the first argument, and the element is second:

    def op(x, b):
    return x or isinstance(b, int)

    That way, the accumulator keeps track if any integer's popped up
    so far, and you update it by or'ing that with whether the current
    element is an int.

    Why your custom reduce versions worked:

    Your custom reduce functions call "op" like "op(ls,
    reduc1(...))" or "op(x, r)" in the loop, so your argument order
    is reversed from functools.reduce, which is why you didn't
    see this problem there. Correct version example:

    from functools import reduce

    def op(acc, elem):
    return acc or isinstance(elem, int)

    def transform(ls):
    return reduce(op, ls, False)

    def solution(ls):
    return list(map(transform, ls))

    print(solution([[""], ["aa", 1]])) Output: [False, True] print(solution([["a", "b"], ["a", 1]])) Output: [False, True]

    This will give the right output now, checking if each sublist
    has any integer and returning "True" or "False" accordingly.


    --- Synchronet 3.21a-Linux NewsLink 1.2