27

In Python, is there any counter available during the list comprehension as it would be in case of a for loop?

It would be more clear why I need a counter, with this example:

I wish to achieve the following:

Initial List: ['p', 'q', 'r', 's']

Desired List: [(1, 'P'), (2, 'Q'), (3, 'R'), (4, 'S')]

In the desired list, first element of every tuple are ordinal numbers. If it were just flat list, I could have used zip to achieve this. But however, the list I am dealing with is nested, three level deep (think of hierarchical data), and it is generated through list comprehension.

So, I was wondering is there any way to introduce those ordinal numbers during list comprehension. If not, what would be the best possible solution.

P.S. : Here the lower case letters are converted to uppercase, but that is not a part of problem, think of it as just a data conversion.

Code:

allObj = Category.objects.all()

tree =[(_, l1.name, [(__, l2.name, [(___, l3.name) for l3 in allObj if l3.parentid == l2.categoryid]) for l2 in allObj if l2.parentid == l1.categoryid]) for l1 in allObj if l1.parentid == None]

allObj contains data from table category, which in turn contains hierarchical data represented in the form of Adjacency List.

I have put _ where I need ordinal numbers to be. Notice that the list is nested, so there will be a separate counter at each level represented by 1, 2 & 3 _s.

user1144616
  • 1,201
  • 2
  • 15
  • 16

8 Answers8

63

The most basic case

[(i, x) for i, x in enumerate(some_list, 1)]

Apply a filter with an if-statements

[(i, x) for i, x in enumerate(some_list, 1) if i > 2]

or like this

[(i, x) for i, x in enumerate(some_list, 1) if x != 'p']

A word of advice

Most often you don't need to do this. Instead you just call enumerate(some_list, 1) where the enumeration is needed, in a for loop for example.

Niclas Nilsson
  • 5,691
  • 3
  • 30
  • 43
  • 3
    Nice one, I didn't know about the second argument in enumerate :) – Samvel Feb 08 '12 at 16:59
  • 9000: Now I don't follow you. According to your answer you know that it's done automaticly (if you did't comment before I added 1 as initial value which I forgot at first) – Niclas Nilsson Feb 08 '12 at 17:01
  • 1
    Maybe even better: `list(enumerate(map(str.upper, oldList), 1))`. Also note `oldList` to avoid the builtin name `list`. – Nobody moving away from SE Feb 08 '12 at 17:05
  • Yeah, may be a better solution. Even thought the user asked about comprehension. :) And by list i refered to the list and not to the variable name. But you're right, could be confusing. – Niclas Nilsson Feb 08 '12 at 17:11
  • @NiclasNilsson: How would `if` play in the above solution using enumerate(). – user1144616 Feb 08 '12 at 17:28
  • btw i found out it seems string addition (concat) operation cannot be done inside a list comprehension? – cryanbhu Oct 02 '18 at 01:28
20

I was looking to something slightly different when I stumbled upon this answer.

My case was to keep a count based on a condition inside the list comprehension. Just in case it's useful to someone else, this is how I solved it:

import itertools counter = itertools.count(0) [(next(counter), x) for x in some_list if x != 'p']

In this way the counter will only be incremented when the condition is met and not at every iteration.

mdev
  • 449
  • 7
  • 12
7

As already showed in the other answers the standard library gives you enumerate, which means that you probably wont even need a list like:

[(1, 'P'), (2, 'Q'), (3, 'R'), (4, 'S')]

because every time you need to bind the letter with a number related to its position you can just call enumerate.
Example:

>>> low = ['p', 'q', 'r', 's']
>>> upp = [c.upper() for c in low]
>>>
>>> for i,c in enumerate(upp, 1):
...     print(i,c)
...
1 P
2 Q
3 R
4 S

This was just an example, maybe you actually need to that kind of list.

Rik Poggi
  • 28,332
  • 6
  • 65
  • 82
  • @user1144616: You're welcome! :) I just saw your updated question (with the code snippet). If you have to stick with that design I wish you all the luck, because it'll be hard to build and even more hard to read. If instead you can refactor your code, take a look at [networkx](http://networkx.lanl.gov/), maybe it suites your case. – Rik Poggi Feb 08 '12 at 18:57
4

RTM: enumerate(['p', 'q', 'r', 's'], 1) gives you a generator yielding (1, 'p'), (2, 'q'), (3, 'r'), (4, 's'), convert it to list to taste.

Funk Forty Niner
  • 74,450
  • 15
  • 68
  • 141
9000
  • 39,899
  • 9
  • 66
  • 104
  • I can use `if` to filter elements in case of List comprehension, how would that play here in case of enumerate(). – user1144616 Feb 08 '12 at 17:33
  • 1
    Filter the incoming sequence, enumerate the result: `enumerate((x for x in some_sequence if is_good(x)), 1)` – 9000 Feb 08 '12 at 19:14
  • 1
    RTFM? He didn't mention `enumerate` in his original post, so how can he RTFM? – Rabarberski Jan 20 '14 at 10:37
  • @Rabarberski: He could read the fine manual to learn about `enumerate` and enjoy using it. If you think that the word "RTFM" is rude and condescending, I did not mean it, and such overtones are absent, if "The New Hacker Dictionary" (nee "Jargon File") is [to be believed](http://www.outpost9.com/reference/jargon/jargon_33.html#TAG1524). – 9000 Jan 20 '14 at 18:22
1

I guess you want something like numbering all items, independent of the level of nesting. Maybe the following will help. Don't forget to create a new number for each list comprehension. next may be spelled __next__ in your version of Python.

>>> import itertools
>>> number = itertools.count().next
>>> [(number(), [(number(), x + 1) for x in range(y) if x % 2]) for y in range(10) if y % 3]
[(0, []), (1, [(2, 2)]), (3, [(4, 2), (5, 4)]), (6, [(7, 2), (8, 4)]), (9, [(10, 2), (11, 4), (12, 6)]), (13, [(14, 2), (15, 4), (16, 6), (17, 8)])]

Update: I know understand that you need different counters for each level of nesting. Just use more than one counter:

>>> number1 = itertools.count().__next__
>>> number2 = itertools.count().__next__
>>> print([(number1(), [(number2(), x + 1) for x in range(y) if x % 2]) for y in range(10) if y % 3])
[(0, []), (1, [(0, 2)]), (2, [(1, 2), (2, 4)]), (3, [(3, 2), (4, 4)]), (4, [(5, 2), (6, 4), (7, 6)]), (5, [(8, 2), (9, 4), (10, 6), (11, 8)])]

I.e., replace _ with number1() as defined above, __ with number2(), and so on. That's it.

Reinstate Monica
  • 4,568
  • 1
  • 24
  • 35
0

Great thread! The approved answer and later expansions gave me an idea, which I think might help someone. So here it is.

[print('{}. Key: {}, Value: {}'.format(i, k, v)) for i, (k, v) in enumerate(some_list.items(), 1)]

The above can be used to get a pretty output of a dictionary. You can use it for debugging, logging, etc... Oh yes, one more possibility, creating a csv file, so your manager can open it in Excel. Managers love their spreadsheets.

Example output:

1. Key: AwesomeKey1, Value: AwesomeValue1
2. Key: AwesomeKey2, Value: AwesomeValue2
3. Key: AwesomeKey3, Value: AwesomeValue3
4. Key: AwesomeKey4, Value: AwesomeValue4
5. Key: AwesomeKey5, Value: AwesomeValue5
Al.
  • 25
  • 5
0
L = ['p', 'q', 'r', 's']
[(i + 1, x) for i, x in enumerate(L)]
Samvel
  • 182
  • 2
  • 6
0

Would something like this help?

i = 1

y = range(10)

s = [(i + y.index(x), x**2) for x in y]

print s

>>> [(1, 0), (2, 1), (3, 4), (4, 9), (5, 16), (6, 25), (7, 36), (8, 49), (9, 64), (10, 81)]

I have a suspicion that there may be a better way to do this than through comprehensions though.

ThisIsNotAnId
  • 177
  • 4
  • 20