Making a function only run for certain conditions

2019-09-05 08:34发布

G'day!

So I have a function which is taking the elements from two lists, the first of which is in a standard list format, the second being a list of lists, the inner lists containing elements in the form of 3-tuples. My output is a new list in the format of the the second list, containing the same number of elements in the same number of inner lists, with some of the values slightly adjusted as a result of being passed through the function.

Here is an example code, and an example function, where chain is being imported from itertools. first is some list such as [0,1,2,3,1,5,6,7,1,2,3,5,1,1,2,3,5,6] whilst second is some list such as [[(13,12,32),(11,444,25)],[(312,443,12),(123,4,123)],[(545,541,1),(561,112,560)]]

def add(x, y):
    return x + y 

foo = [add(x, y) for x, y in zip(first, chain(*(chain(*second))))]
bar = [foo[i:i+3] for i in range(0, len(foo), 3)]
second = [bar[i:i+2]  for i in range(0, len(foo) / 3, 2)]

**Note: The Chain(chain()) part is for the following purpose: Because it's generally a bit harder to handle a list of list containing 3-tuples, The chain(chain()) is just flattening (into a traditional list of individual elements) that second list with the aforementioned 'odd format'. The rest of the code is just rebuilding the new list into the original format from the output of the function, which is already in flattened form.

The problems I'm having are as such:

I want the output to be of the exact same size and format as the original 'second' list. If both lists are empty, I want the empty list returned. If the first list is empty, I want the original second list returned. If the second list is empty, I want the empty list returned.

If the first list is shorter than the second list, I want the function to run for however elements can be matched between the two lists, then the 'excess' of the second list remaining unchanged.

If the second list is shorter than the first list, I want the function to run for however many elements there are in the second list, then just ignore the 'excess' elements from list 1, thus still outputting a new list that has the same dimensions and formatting as the original second list.

My problem is, that I have no idea how to implement these little nuances into my code. Any help would be appreciated.

Cheers, James

3条回答
▲ chillily
2楼-- · 2019-09-05 08:46

since zip only zips to the smaller of the two lists, it isn't too useful here. You could create your own algorithm that applies a function to two lists in the way you specify:

from itertools import *


def flatten(seq):
    return list(chain(*(chain(*seq))))

def special_apply(a,b, func):
    """
    applies a two argument function to the given flat lists.
    The result will have the same size as the second list, even if the first list is shorter.
    """
    result = []
    for i in range(len(b)):
        if i < len(a):
            element = func(a[i], b[i])
        #if a ran out of elements, just supply an unmodified element of b
        else:
            element = b[i]
        result.append(element)
    return result

def add(x,y):
    return x+y

a = [1,1,1] 
b = [[(13,12,32),(11,444,25)],[(312,443,12),(123,4,123)],[(545,541,1),(561,112,560)]]
foo = special_apply(a, flatten(b), add)
bar = [foo[i:i+3] for i in range(0, len(foo), 3)]
result = [bar[i:i+2]  for i in range(0, len(foo) / 3, 2)]
print result

Result:

[[[14, 13, 33], [11, 444, 25]], [[312, 443, 12], [123, 4, 123]], [[545, 541, 1], [561, 112, 560]]]
查看更多
闹够了就滚
3楼-- · 2019-09-05 08:48

I think this does everything you want, if I've understood all the requirements. The major difference from your code is that it also usesizip_longest() fromitertoolswith a customfillvalue, instead of plainzip(). It also just checks for the special cases involving empty input lists at the beginning, which seemed easier than trying to devise list comprehensions or whatever to handle them.

from itertools import chain, izip_longest

def add(x, y):
    return x + y

def func(first, second):
    if not first: return second
    if not second: return []
    second = chain(*(chain(*second)))  # flatten
    foo = [add(x, y) for x, y in izip_longest(first, second, fillvalue=0)]
    bar = [tuple(foo[i:i+3]) for i in range(0, len(foo), 3)]
    return [bar[i:i+2]  for i in range(0, len(foo) / 3, 2)]

if __name__ == '__main__':
    first = [
        0, 1, 2,  3, 1, 5,
        6, 7, 1,  2, 3, 5,
        1, 1, 2,  3, 5, 6]
    second = [
        [(13, 12, 32), (11, 444, 25)],      [(312, 443, 12), (123, 4, 123)],
        [(545, 541, 1), (561, 112, 560)],   [(13, 12, 32), (11, 444, 25)],
        [(312, 443, 12), (123, 4, 123)],    [(545, 541, 1), (561, 112, 560)],
    ]

    print func(first, second)
    print
    print func(first[:-1], second) # 1st shorter, as many as poss, rest unchanged
    print
    print func(first, second[:-1]) # 2nd shorter, do only as many as in second
查看更多
Lonely孤独者°
4楼-- · 2019-09-05 09:09

Could you pad the first list with None where its not long enough and trim it where its too long.

then only carry out the function where x is not None otherwise return y

i've tried to code an example

from itertools import chain

first = [0, 1, 2, 3, 1, 5, 6, 7, 1, 2, 3, 5, 1, 1, 2, 3, 5, 6]
second = [
    [(13, 12, 32), (11, 444, 25)],
    [(312, 443, 12), (123, 4, 123)],
    [(545, 541, 1), (561, 112, 560)],
    [(13, 12, 32), (11, 444, 25)],
    [(312, 443, 12), (123, 4, 123)],
    [(545, 541, 1), (561, 112, 560)],
]

def add(x, y):
    return x + y 


def pad(list,length):
    for i in range(length-len(list)):
        list.append(None)
    return list[0:length]


first = pad(first,len(list(chain(*(chain(*second))) )))
# There is probably a better way to achieve this
foo = [add(x, y) if x else y for x, y in zip(first, chain(*(chain(*second))))]
bar = [foo[i:i+3] for i in range(0, len(foo), 3)]
second = [bar[i:i+2]  for i in range(0, len(foo) / 3, 2)]
print second
查看更多
登录 后发表回答