Select elements from a list of lists based on another list of lists

Viewed 311

I have two lists of lists that will always have the same lengths.

l1 = [[0, 1, 2, 2, 0, 1, 2], [0, 1, 0, 0, 0], [0, 1, 2, 0, 0]]
l2 = [[0, (1, 'a', 'b'), (1, 'a', 'b'), (1, 'a', 'b'), 0, (3, 'x', 'y'), 0], [0, (2, 'c', 'd'), 0, 0, 0], [0, (3, 'e', 'f'), 0, 0, 0]]

What I want is to return the nonzero elements of l2 only if the quantity of identical elements much the sequence of` 1-2s from l1 (or just 1s). In this case for example, I would like it to return just

out = [[(1, 'a', 'b'), (1, 'a', 'b'), (1, 'a', 'b')], [(2, 'c', 'd')], []]

because there are three identical tuples and a sequence of 1, 2, 2, while for (3, 'e', 'f'), there is only on but there is a sequence of 1, 2 (so it should have been (3, 'e', 'f'), (3, 'e', 'f') instead in l2.

What I have been trying to do is use collections.Counter to count the occurrences of nonzero elements, but when counting 1 and 2s it does not differentiate between sequences, so I cannot do a comparison:

for x, y in zip(l1, l2):
    print(Counter(x), Counter(y))

returns

Counter({2: 3, 0: 2, 1: 2}) Counter({0: 3, (1, 'a', 'b'): 3, (3, 'x', 'y'): 1})
Counter({0: 4, 1: 1}) Counter({0: 3, (2, 'c', 'd'): 2})
Counter({0: 5}) Counter({0: 4, (3, 'e', 'f'): 1})

There is no way to no what the count of 1 and 2 correspond to.

Any help on how to go about doing this?

Some input-expected output examples:

l1 = [[0, 1, 2, 0], [0, 1]]
l2 = [[0, (1, 'a', 'b'), (1, 'a', 'b'), 0], [(2, 'a', 'b'), (2, 'a', 'b')]]
out = [[(1, 'a', 'b'), (1, 'a', 'b')], []]

l1 = [[2, 2, 2], [1, 2]]
l2 = [[(1, 'a', 'b'), (1, 'a', 'b'), (1, 'a', 'b')], [(2, 'a', 'b'), (2, 'a', 'b')]]
out = [[(1, 'a', 'b'), (1, 'a', 'b'), (1, 'a', 'b')], [(2, 'a', 'b'), (2, 'a', 'b')]]

l1 = [[0, 1], [0, 0]]
l2 = [[0, (3, 'x', 'y')], [0, 0]]
out = [[(3, 'x', 'y')], []]
1 Answers

You are trying to find run lengths in your lists. One way to do that is to step through the list and turn a flag on or off when you come to a run boundary. This is a common trick in numpy, where you can mask, diff and sum, but it's not too bad with vanilla python either.

You can label runs by using whatever marker you want. For example, you can label with the run length and start element. In this particular case, that will allow you to compare matching elements directly:

def label_runs(lst, zero=0, marker=lambda s, n: (s, n)):
    marks = [0] * len(lst)
    start = -1
    for i in range(len(lst)):
        if lst[i] == zero and start >= 0:
            n = i - start
            marks[start:i] = [marker(start, n)] * n
            start = -1
        elif lst[i] != zero and start < 0:
            start = i
    if start >= 0:
        n = len(lst) - start
        marks[start:] = [marker(start, n)] * n
    return marks

Now you have a function that does the following:

>>> label_runs(l1[0])
[0, (1, 3), (1, 3), (1, 3), 0, (5, 2), (5, 2)]
>>> label_runs(l2[0])
[0, (1, 3), (1, 3), (1, 3), 0, (5, 1), 0]

The reason for marking with the start and length instead of just the length is to avoid something like this:

>>> label_runs_nostart([0, 1, 2, 2, 0])
[0, 3, 3, 3, 0]
>>> label_runs_nostart([(1, 'a', 'b'), (1, 'a', 'b'), (1, 'a', 'b'), 0, 0])
[3, 3, 3, 0, 0]

A direct comparison of such a pathological case would falsely grab the last pair of (1, 'a', 'b') if you used direct matching. With the start as an additional check, though, you can trivially take the corresponding elements of each label list and use them to filter the second array where they match:

[[x for e1, e2, x in zip(label_runs(i1), label_runs(i2), i2) if x !=0 and e1 == e2] for i1, i2 in zip(l1, l2)]
Related