2

I have a flat list of numbers that are logically in groups of 3, where each triple is (number, __ignored, flag[0 or 1]), eg:

[7,56,1, 8,0,0, 2,0,0, 6,1,1, 7,2,0, 2,99,1]

I would like to (pythonically) process this list to create a new list of numbers based on the value of 'flag': if 'flag' is 1, then I want 'number', else 0. So the above list would become:

[7, 0, 0, 6, 0, 2]

My initial attempt at doing this is:

list = [7,56,1, 8,0,0, 2,0,0, 6,1,1, 7,2,0, 2,99,1]
numbers = list[::3]
flags = list[2::3]

result = []
for i,number in enumerate(numbers):
  result.append(number if flags[i] == 1 else 0)

This works, but it seems to me that there should be a better way to extract tuples cleanly from a list. Something like:

list = [7,56,1, 8,0,0, 2,0,0, 6,1,1, 7,2,0, 2,99,1]
for (number, __, flag) in list:
    ...etc

But I don't seem to be able to do this.

I could just loop through the entire list:

result = []
for i in range(0, len(list), 3):
    result.append(list[i] if list[i+2] == 1 else 0)

Which seems smaller and more efficient.

I am unclear about the best option here. Any advice would be appreciated.

Note: I have accepted the answer by wim:

[L[i]*L[i+2] for i in range(0, len(L), 3)]

But want to reiterate that both wims and ShadowRangers responses work. I accepted wim's answer based on simplicity and clarity (and, to a lesser extent, compatibility with python 2, though ShadowRanger pointed out that zip was in Py2 as well, so this basis is invalid).

The answer by ShadowRanger:

[number if flag else 0 for number, _, flag in zip(*[iter(mylist)]*3)]

also does exactly what I thought I wanted (providing tuples), but is a little obscure and requires zip. As wim noted, ShadowRanger's answer would be very well suited to a stream of data rather than a fixed list.

I would also note that ShadowRanger's answer adds obscure use of zip() (which will become less obscure with time), but adds clarity by the use of named values for the tuple, so it's a bit of a win/lose.

For those struggling to understand zip(*[iter(mylist)]*3)], it creates three copies of the one iterator, which are then used to contruct tuples. Because it's the same iterator, each use advances the iterator, making the tuples exactly as I requested.

For both clarity and generality, I am also somewhat inclined to a modified version of the solution from @ShadowRanger:

i = iter(mylist)
[number if flag else 0 for number, _, flag in zip(i, i, i)]

(which, to me, seems much less obscure).

3 Answers 3

4

I think the most direct way would be a simple list comprehension:

>>> [L[i]*L[i+2] for i in range(0, len(L), 3)]
[7, 0, 0, 6, 0, 2]

Or consider numpy, it's powerful for tasks like this:

>>> import numpy as np
>>> a = np.array(L).reshape(-1, 3).T
>>> a
array([[ 7,  8,  2,  6,  7,  2],
       [56,  0,  0,  1,  2, 99],
       [ 1,  0,  0,  1,  0,  1]])
>>> a[0]*a[2]
array([7, 0, 0, 6, 0, 2])
Sign up to request clarification or add additional context in comments.

6 Comments

Both of those look pretty solid -- though I did oversimplify, the flag field is a text field, so multiplication is not an option. But the basic solutions you suggest work in the more general case I think. Thank you!
As a side-issue, given it can be done is a basic list through your first option, am I correct in assuming restructuring the array with numpy would be considerably less efficient?
Not necessarily - numpy is usually much more efficient that Python loops for bigger data. But if you have a mix of text and numbers, then numpy is probably not a great choice - you would want to pre-process to turn the textual flags into numeric flags in order to get the efficiency gains of using numpy arrays.
Note: I accepted this answer based on simplicity and clarity (and, to a lesser extent, compatibility with python 2). The answer by @ShadowRanger is also great; it does exactly what I thought I wanted (providing tuples), but is a little obscure and requires zip. As wim noted, the latter would be very well suited to a stream of data rather than a fixed list.
@RabidMutant: Not objecting to accepting this one, but a note: My answer works just fine with Python 2. It works more efficiently in Py2 if you add a conditional import of from itertools import izip as zip (so you get a lazy, Py3-style zip even in Py2), or izip_longest as zip_longest if you went that route, but it works with plain zip on Py2 (zip has been around forever, only change from Py2 to Py3 is that Py2 version produced list of tuples, Py3 version produces iterator of tuples).
|
3

Since you have logical triples, you can use a little hack with iter, sequence multiplication and zip to accomplish your goal:

result = []
for number, _, flag in zip(*[iter(mylist)]*3):
    result.append(number if flag else 0)  # flag is only 1 or 0, so no need to compare it

That unpacks the same iterator over mylist as three arguments to zip; since it's the same iterator, zip pulls element 0, 1 and 2 for the first output, then 3, 4 and 5, etc. Your loop then unpacks the three elements to logical names (using _ for the value you don't care about).

A list comprehension could even one-line it to:

result = [number if flag else 0 for number, _, flag in zip(*[iter(mylist)]*3)]

though that's getting a little dense, meaning-wise.

Advantages to this approach are:

  1. It works with any iterable, not just lists
  2. It's performant; the input is traversed exactly once, where any slicing solution would traverse it multiple times
  3. It uses meaningful names, not anonymous magic numbers for index offsets

Downside:

  1. zip(*[iter(mylist)]*3) is a little magical
  2. It will silently omit data if your input turns out to not have a length that's a multiple of three (the partial group at the end is dropped)

Note: In anything resembling production code, don't inline the zip/iter/unpack trick. Use the grouper recipe from the itertools module (or a zip-based variant) and call that:

# Defined somewhere else for common use
def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.zip_longest(*args, fillvalue=fillvalue)

for number, _, flag in grouper(mylist, 3):

The recipe is tested and predictable, and by giving it a name you make the code using it much more obvious.

11 Comments

I think your suggestion is significantly worse than OP's original code (needless sophistication/complication)
Great solution; and I do like what seem to be good performance implications of using zip. Now I wish I could accept both answers. They are both excellent.
Because you have an input list, which offers the nice feature of O(1) access by index. May as well take advantage of that feature. The zip and splat tricks are not readable.
@RabidMutant: Using the zip(*[iter(x)]*3) trick requires understanding multiple somewhat obscure facets of Python, and if you don't understand even one of them, it's confusing as hell (and initially confusing no matter what). Hiding that complexity in a function with a useful name and descriptive docstring means you don't get slammed with that complexity when evaluating each bit of code using it, that's all.
@RabidMutant: It works around most of it. It's still a little confusing to folks that don't understand explicit creation of iterators, and how zip can consume the same iterator as multiple arguments independently. And it's still going to take longer for a maintainer to grok than just using the grouper recipe.
|
2

I guess this may work:

>>> list_ = [7,56,1, 8,0,0, 2,0,0, 6,1,1, 7,2,0, 2,99,1]
>>> [a if c == 1 else 0 for a,c in zip(list_[::3],list_[2::3])]
... 
 [7, 0, 0, 6, 0, 2]

2 Comments

I think this one loses out to the solution from @ShadowRanger because it uses two lists, whereas theirs uses two iterators.
End of my comment should read "...whereas theirs uses three copies of a single iterator."

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.