Search code examples
pythonlistfunctioncyclic

Cyclically reduce a list of operands to a single result


(This is a rehashed, self-answered version of another question which has been closed because it was not asked well.)

I have a list of integers:

numbers = [1, 2, 3, 4, 5, 6]

My goal is to alternatively apply the sum and multiplication operators successively on these numbers to get a single result.

For example, for this input, the result is to be

((1 + 2) * 3 + 4) * 5 + 6

Which reduces to 71. Essentially, this can be broken down into:

t1 =  1 + 2 
t2 = t1 * 3 
t3 = t2 + 4
... 

and so on.

Bonus: A solution that can generalise to more than two cyclical operations would be welcome.


Solution

  • Here's a non-itertools approach for this situation.

    Imagine first that there was a version of functools.reduce that took in 3 items from an iterable at a time. Let's call this hypothetical function reduce3.

    If this existed, we could do something like:

    reduce3(lambda a, b, c: (a+b)*c, numbers)
    

    If we were to view the intermediate results of this operation, we'd have something like:

    1, 2, 3, 4, 5, 6  # Initial list
    9, 4, 5, 6        # Step 1
    65, 6             # Step 2
    (65 + 6) * ??     # Step 3
    

    So this is almost what we want, except there's no 3rd item to multiply by in Step 3. In fact, this will happen for any even length list. Well then let's just append a 1 to the list if it's length is even:

    if not len(numbers) % 2:
        numbers.append(1)
    

    After this, the third step will be:

    (65 + 6)*1
    

    Which results in the correct answer of 71.

    Unfortunately, this magical function does not exist. But, we can modify the original list to mimic this functionality. We just need to take the numbers list and group consecutive pairs of numbers, not including the first element, into tuples. Also, if the list is even length we need to add the element 1 to the end.

    In essence, let's write a function preprocess() to turn [1, 2, 3, 4, 5, 6] into [1, (2, 3), (4, 5), (6, 1)].

    def preprocess(myList):
        my_output = [myList[0], *zip(numbers[1::2], numbers[2::2])]
        if not len(myList) % 2:
            my_output.append((myList[-1], 1))
        return my_output
    
    print(preprocess(numbers))
    #[1, (2, 3), (4, 5), (6, 1)]
    

    Now we can reduce the processed list:

    from functools import reduce
    result = reduce(lambda a, b: (a+b[0])*b[1], preprocess(numbers))
    print(result)
    #71
    

    The reducer takes 2 inputs- a number and a tuple. It adds the number to the first element of the tuple, and multiplies the result by the second. The result is another number, which is then passed to the next reduce operation.


    Update

    Here is a general implementation of reduceN. The N is determined by the length of the functions passed in, so this can be generalized to any number of functions.

    from itertools import islice  # couldn't get away from itertools this time
    
    def reduceN(functions, iterable, initializer=None):
        it = iter(iterable)
        n = len(functions)
        if initializer is None:
            value = next(it)
        else:
            value = initializer
        elements = list(islice(it, n))
        while(elements):
            for fn, el in zip(functions, elements):
                value = fn(value, el)
            elements = list(islice(it, n))
        return value
    

    We can use this to apply any number of functions cyclically. So the original example:

    from operator import add, mul
    numbers = [1, 2, 3, 4, 5, 6]
    functions = [add, mul]
    print(reduceN(functions, numbers))
    #71
    

    And if we remove the last element from numbers:

    print(reduceN(functions=functions, iterable=[1, 2, 3, 4, 5]))
    #65