0

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

8
  • 3
    Can you give an example of what the output should look like given your example inputs? I don't have the attention span to visualize it with just your words. (sorry) Commented Oct 1, 2013 at 14:21
  • The output should just be a list of the same dimensions as the original second list, with the elements being the output of the function given (which is only a sample function). It just needs to satisfy the extra bits of criteria which I referred to as "the problems i'm having" which discuss when the lengths of each list are zero/non-equivalent. Commented Oct 1, 2013 at 14:25
  • "If the second list is shorter than the second list". What do you mean by that? Commented Oct 1, 2013 at 14:28
  • If you're concerned the answers will take it too far and hard code the add operation into the solution, well, maybe some will, but right now I'm just struggling to understand exactly how you want the grouping to work. An actual example output would help greatly with that. Commented Oct 1, 2013 at 14:28
  • You have explained well what you want it to do if the lengths differ. But the transformation in the chain(chain()) section should be explained better. Commented Oct 1, 2013 at 14:32

3 Answers 3

0

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
Sign up to request clarification or add additional context in comments.

2 Comments

Padding with zeroes works well if the given function is add. But what if it becomes mult? Multiplying by zero will zero out the leftover elements in second, when they are supposed to be left alone.
instead of padding with zeroes, pad with a sentinel value (None is a common choice). then you can do something like 'y if x != None else add(x,y) for x,y in zip(stuff)'. a little more complicated, but still possible to one-line and decently readable.
0

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 Comments

cool man! Cheers for that...I'm trying now to implement it. It doesn't like finding the len of a flattened list though. You get something like: 'TypeError: object of type 'itertools.chain' has no len()' being returned. Any ideas?
zip zips to the smaller of the two lists, but you can also use itertools.izip_longest() which takes a fill value to pad the shorter list with. wrap with itertools.islice() to force it to not process past the length of the second list (if the first is longer).
@JamesAdams, it sounds like an itertools.chain object is being passed to special_apply instead of a list. (perhaps you are using a different flatten function than me? Mine converts to a list before returning). You can convert a chain object to a list with list(chain_object_goes_here)
0

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

2 Comments

Okay, I think this is on the right track to what I'd like. From where you've used the if loop onwards, is that where it is dealing with the lists having different sizes? How would it look if I don't want to have the first and second lists defined within that if loop, because I want to just use them as the inputs for func(first, second)?
This deals with different length lists by first explicitly checking to see whether one or both are empty, and if not then implicitly with the izip_longest() call, which will pad the shorter list out with zeros. There's no such thing as an if loop. Here, two lists are defined outside the function definition and then passed to it for testing purposes when the script is the main one being run (as opposed to it having been imported by another script, which would supply it's own lists).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.