tags:

views:

140

answers:

7

I have a list which has repeating items and I want a list of the unique items with their frequency.

For example, I have ['a', 'a', 'b', 'b', 'b'], and I want [('a', 2), ('b', 3)]

Looking for a simple way to do this without looping twice.

+4  A: 

If your items are grouped (i.e. similar items come together in a bunch), the most efficient method to use is itertools.groupby:

>>> [(g[0], len(list(g[1]))) for g in itertools.groupby(['a', 'a', 'b', 'b', 'b'])]
[('a', 2), ('b', 3)]
Eli Bendersky
I'm sorry but this is wrong. Try it on the input list `['b', 'a', 'a', 'b', 'b']`. You will get: `[('b', 1), ('a', 2), ('b', 2)]`. You only got the right answer because of the way the input was formatted.
Tom
@Tom: I'm aware of this limitation. When the items are grouped, however, `groupby` is the efficient and preferred approach
Eli Bendersky
You should make that clear... notice the constraint in the question says "I have a list which has repeating items"... the list the OP gave was just an example. I don't think this solution is general enough. If the OP specified that the input list always had the elements grouped, I would agree.
Tom
@Tom: you're right - I've updated the answer (BTW I assumed from his "repeating items" that they're grouped)
Eli Bendersky
Ok Eli... thanks for the update :-). I revoke my -1 because your answer is now more clear.
Tom
+4  A: 

When Python 2.7 comes out you can use its collections.Counter class

otherwise see counter receipe

Under Python 2.7a3

from collections import Counter
input =  ['a', 'a', 'b', 'b', 'b']
c = Counter( input )

print( c.items() )

output is

[('a', 2), ('b', 3)]

Mark
Hey, even though python 2.7 doesn't help the OP right now... +1! The collections.Counter class is interesting and seems like a nice shorthand for the solution I provided. (It also has some cool extras). This answer is surely one that people will want to read in the future. You should update with an example of usage.
Tom
+1  A: 

I know this isn't a one-liner... but to me I like it because it's clear to me that we pass over the initial list of values once (instead of calling count on it):

>>> from collections import defaultdict
>>> l = ['a', 'a', 'b', 'b', 'b']
>>> d = defaultdict(int)
>>> for i in l:
...  d[i] += 1
... 
>>> d
defaultdict(<type 'int'>, {'a': 2, 'b': 3})
>>> list(d.iteritems())
[('a', 2), ('b', 3)]
>>>
Tom
A: 

Another way to do this would be

mylist = [1, 1, 2, 3, 3, 3, 4, 4, 4, 4]
mydict = {}
for i in mylist:
    if i in mydict: mydict[i] += 1
    else: mydict[i] = 1

then to get the list of tuples,

mytups = [(i, mydict[i]) for i in mydict]

This only goes over the list once, but it does have to traverse the dictionary once as well. However, given that there are a lot of duplicates in the list, then the dictionary should be a lot smaller, hence faster to traverse.

Nevertheless, not a very pretty or concise bit of code, I'll admit.

Aaron
This is identical in spirit to my solution... except defaultdict consolidates the first part (since you don't have to check for existence) and list(mydict.iteritems()) is shorter than the list comprehension.
Tom
`mytups = mydict.items()` is a simpler way to get the list of tuples.
Paul McGuire
Thanks @Paul and @Tom. It seems like there is always a better way to do something in Python. :)
Aaron
A: 

the "old school way".

>>> alist=['a', 'a', 'b', 'b', 'b']
>>> d={}
>>> for i in alist:
...    if not d.has_key(i): d[i]=1  #also: if not i in d
...    else: d[i]+=1
...
>>> d
{'a': 2, 'b': 3}
ghostdog74
A: 
>>> mylist=['a', 'a', 'b', 'b', 'b']
>>> [ (i,mylist.count(i)) for i in set(mylist) ]
[('a', 2), ('b', 3)]
A: 

A solution without hashing:

def lcount(lst):
   return reduce(lambda a, b: a[0:-1] + [(a[-1][0], a[-1][1]+1)] if a and b == a[-1][0] else a + [(b, 1)], lst, [])

>>> lcount([])
[]
>>> lcount(['a'])
[('a', 1)]
>>> lcount(['a', 'a', 'a', 'b', 'b'])
[('a', 3), ('b', 2)]
arte