user1144616
user1144616

Reputation: 1231

Maintain count in python list comprehension

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.

Upvotes: 29

Views: 55619

Answers (8)

Al.
Al.

Reputation: 25

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

Upvotes: 0

devmonaut
devmonaut

Reputation: 469

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.

Upvotes: 22

9000
9000

Reputation: 40884

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.

Upvotes: 4

Niclas Nilsson
Niclas Nilsson

Reputation: 5901

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.

Upvotes: 67

Reinstate Monica
Reinstate Monica

Reputation: 4723

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.

Upvotes: 1

ThisIsNotAnId
ThisIsNotAnId

Reputation: 197

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.

Upvotes: 0

Rik Poggi
Rik Poggi

Reputation: 29302

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.

Upvotes: 7

Samvel
Samvel

Reputation: 182

L = ['p', 'q', 'r', 's']
[(i + 1, x) for i, x in enumerate(L)]

Upvotes: 0

Related Questions