Search code examples
pythonarraysloopsgroup-byintrospection

How to create a function that can iterate over an arbitrary number of functions within an item within an array?


I have the below function called function_iterate_over_array that takes in an array and a list of function names. Although the function does work, I need to create a function that achieves the same results but can take in an arbitrary number of function_names.

For example, the below function must take in four function names

from operator import itemgetter
from itertools import groupby
from dictionary_functions import DictionaryFunctions

group_func = lambda iterable,key:  [list(group) for key, group in groupby(sorted(iterable, key=itemgetter(key)), itemgetter(key))]

def add_one(i):
    return i + 1
def subtract_one(i):
    return i - 1


array = [{'f1':add_one,'f2':add_one,'f3':add_one,'f4':add_one},
{'f1':add_one,'f2':add_one,'f3':add_one,'f4':add_one},
{'f1':subtract_one,'f2':subtract_one,'f3':subtract_one,'f4':subtract_one},
{'f1':subtract_one,'f2':add_one,'f3':add_one,'f4':add_one}]

functions = ['f1','f2','f3','f4']

def function_iterate_over_array(array,functions,starting_value=0):
    L = []
    function_key_name = functions[0]
    grouped_array = group_func(array,function_key_name)
    for grouped_array_item in grouped_array:
        function_one = grouped_array_item[0][function_key_name]
        function_one_result = function_one(starting_value)

        function_two_name = functions[1]
        sub_grouped_array = group_func(grouped_array_item,function_two_name)
        for sub_grouped_array_item in sub_grouped_array:
            function_two = sub_grouped_array_item[0][function_two_name]
            function_two_result = function_two(function_one_result)


            function_three_name = functions[2]
            sub_sub_grouped_array = group_func(sub_grouped_array_item,function_three_name)
            for sub_sub_grouped_array_item in sub_sub_grouped_array:
                function_three = sub_sub_grouped_array_item[0][function_three_name]
                function_three_result = function_three(function_two_result)


                function_four_name = functions[3]
                sub_sub_sub_grouped_array = group_func(sub_sub_grouped_array_item,function_four_name)
                for sub_sub_sub_grouped_array_item in sub_sub_sub_grouped_array:
                    function_four = sub_sub_sub_grouped_array_item[0][function_four_name]
                    function_four_result = function_four(function_three_result)


                    for dictionary_from_array in sub_sub_sub_grouped_array_item:
                        D = dict(dictionary_from_array.items()+{'result':function_four_result}.items())
                        L.append(D)

    return L 



L = function_iterate_over_array(array,functions)
#-->[{'f1': <function add_one at 0x1006d5cf8>, 'f2': <function add_one at 0x1006d5cf8>, 'f3': <function add_one at 0x1006d5cf8>, 'f4': <function add_one at 0x1006d5cf8>, 'result': 4}, {'f1': <function add_one at 0x1006d5cf8>, 'f2': <function add_one at 0x1006d5cf8>, 'f3': <function add_one at 0x1006d5cf8>, 'f4': <function add_one at 0x1006d5cf8>, 'result': 4}, {'f1': <function subtract_one at 0x100763ed8>, 'f2': <function add_one at 0x1006d5cf8>, 'f3': <function add_one at 0x1006d5cf8>, 'f4': <function add_one at 0x1006d5cf8>, 'result': 2}, {'f1': <function subtract_one at 0x100763ed8>, 'f2': <function subtract_one at 0x100763ed8>, 'f3': <function subtract_one at 0x100763ed8>, 'f4': <function subtract_one at 0x100763ed8>, 'result': -4}]

I'd like to achieve the same result with a better function than function_iterate_over_array that can take any number of functions. For example, I'd like the following to work:

array = [{'f1':add_one,'f2':add_one,'f3':add_one,'f4':add_one},
{'f1':add_one,'f2':add_one,'f3':add_one,'f4':add_one},
{'f1':subtract_one,'f2':subtract_one,'f3':subtract_one,'f4':subtract_one},
{'f1':subtract_one,'f2':add_one,'f3':add_one,'f4':add_one}]
function_iterate_over_array(array,['f1','f2'])

function_iterate_over_array(array,['f1','f2']) should return the following:

[{'f1': <function add_one at 0x101a0fcf8>, 'f2': <function add_one at 0x101a0fcf8>, 'f3': <function add_one at 0x101a0fcf8>, 'f4': <function add_one at 0x101a0fcf8>, 'f5': <function add_one at 0x101a0fcf8>, 'result': 2}, {'f1': <function add_one at 0x101a0fcf8>, 'f2': <function add_one at 0x101a0fcf8>, 'f3': <function add_one at 0x101a0fcf8>, 'f4': <function add_one at 0x101a0fcf8>, 'f5': <function add_one at 0x101a0fcf8>, 'result': 2}]

which I achieved by altering function_iterate_over_array to the following:

def function_iterate_over_array(array,functions,starting_value=0):
    L = []
    function_key_name = functions[0]
    grouped_array = group_func(array,function_key_name)
    for grouped_array_item in grouped_array:
        function_one = grouped_array_item[0][function_key_name]
        function_one_result = function_one(starting_value)

        function_two_name = functions[1]
        sub_grouped_array = group_func(grouped_array_item,function_two_name)
        for sub_grouped_array_item in sub_grouped_array:
            function_two = sub_grouped_array_item[0][function_two_name]
            function_two_result = function_two(function_one_result)


            for dictionary_from_array in sub_grouped_array_item:
                D = dict(dictionary_from_array.items()+{'result':function_two_result}.items())
                L.append(D)
    return L 

I'd also like the following to work

array = [{'f1':add_one,'f2':add_one,'f3':add_one,'f4':add_one,'f5':add_one},
{'f1':add_one,'f2':add_one,'f3':add_one,'f4':add_one,'f5':add_one}]
functions = ['f1','f2','f3','f4','f5']
function_iterate_over_array(array,functions)

In particular I'm looking for a better way to write the above function so that it can take in an arbitrary number of functions.


Solution

  • It looks like reduce could help you a lot.

    Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). The left argument, x, is the accumulated value and the right argument, y, is the update value from the iterable. If the optional initializer is present, it is placed before the items of the iterable in the calculation, and serves as a default when the iterable is empty. If initializer is not given and iterable contains only one item, the first item is returned.

    It's specifically designed to apply a function over and over to a collection of values. Using a lambda, you can use a different function at each step.

    from functools import reduce
    
    def add_one(i):
        return i + 1
    
    def subtract_one(i):
        return i - 1
    
    function_dicts = [{'f1':add_one,'f2':add_one,'f3':add_one,'f4':add_one},
    {'f1':subtract_one,'f2':subtract_one,'f3':subtract_one,'f4':subtract_one},
    {'f1':subtract_one,'f2':add_one,'f3':add_one,'f4':add_one}]
    
    functions = ['f1','f2','f3','f4']
    
    for function_dict in function_dicts:
        result = reduce((lambda value, fname : function_dict[fname](value)), functions, 0)
        print(result)
    # 4 
    # -4
    # 2
    

    It doesn't return the exact same format as in your question, but this provides the desired core functionality.