2

While I am trying to create a dictionary using dict(x), where x is a slice of another dictionary,d(y) where y is collections.Counter() object. This is the one-liner:

lengths=dict(islice(dict(Counter(input())),3))

The exception I am getting is this

lengths=dict(islice(dict(Counter(input())),3))
ValueError: dictionary update sequence element #0 has length 1; 2 is required

According to my understanding, this error is caused when the update function is called with only one value(instead of key value pair). I know something is bad in the nested function calls, but couldn't find it.

How can I get a slice of dictionary items? Is there a way I could do this without actually iterating through the entire dictionary and updating to a new dictionary?

jpp
  • 159,742
  • 34
  • 281
  • 339
control-zed
  • 45
  • 1
  • 12
  • An `islice` of a `dict` will give you an iterator over part of the keys. You are getting an error because the `dict` constructor expects an iterable of two-element-iterables. – timgeb Oct 04 '18 at 08:42
  • You need to unnest the code then and check the return of `islice(dict(Counter(input())),3)`- it's probably not a dict, but the keys – 576i Oct 04 '18 at 08:43
  • How can I get a slice of dictionary items? Is there a way I get a sliced dictionary without actually iterating through the entire dictionary and updating to a new dictionary? – control-zed Oct 04 '18 at 08:59

4 Answers4

1

Iterating a dictionary will only yield keys. To slice a dictionary, you need to extract both key and value via dict.items. In addition, note collections.Counter is a subclass of dict, so no dict conversion is necessary.

How can I get a slice of dictionary items? Is there a way I could do this without actually iterating through the entire dictionary and updating to a new dictionary?

No, you cannot slice a dictionary without iteration. You can create a new Counter object and use islice to return the first 3 values by insertion order. This still requires iteration, and works in Python 3.6+ where dictionaries are insertion ordered.

from collections import Counter
from itertools import islice

c = Counter('abbcccddeeff')

lengths = Counter()
lengths.update(dict(islice(c.items(), 3)))

print(lengths)

Counter({'c': 3, 'b': 2, 'a': 1})

A couple of points to note:

  1. The order in which Counter objects are printed do not correspond to the internal order in which items are stored, which is still insertion ordered. See also How are Counter / defaultdict ordered in Python 3.7?
  2. You may wonder why, if dictionaries are ordered, as they are in Python 3.6+, you cannot slice directly. There are structural reasons why this is not possible: see Accessing dictionary items by position in Python 3.6+ efficiently.
jpp
  • 159,742
  • 34
  • 281
  • 339
  • Probably it was a NO. But I've seen that in 3.7+ Counter also maintains insertion order but its __repr__ wasn't updated. According to your comment on the order of Counter, `list(Counter('aadddassaaa'))` will probably give the insertion order. So I'd like to store the Counter() as another list and then slice the list and access each key? `from collections import Counter from itertools import islice x = Counter('abbcccddeeff') c = [(i,x[i]) for i in islice(list(x),3)] print(c)` – control-zed Oct 05 '18 at 05:59
0

You can use islice on the items of the Counter (which is a dict subclass so no need for dict conversion) object and then convert the sliced items to a dict using the dict constructor.

For example,

dict(islice(Counter('abbcccddeeff').items(), 3))

returns: (note the absence of d, e and f)

{'a': 1, 'b': 2, 'c': 3}
blhsing
  • 91,368
  • 6
  • 71
  • 106
0

So isslice expects a iterables. So to slice a dictionary you should probably convert the dictionary to list of tuples. But dictionary does not maintain the insertion order. So to maintain that you can probably go with Ordered dict from collections lib in python.

    from collections import Counter, OrderedDict
    from itertools import islice

    data = OrderedDict(list(islice(sorted(Counter("aaabbbccccddddd").items(),key=lambda element: (-element[1], element[0])), 3)))
  • Can you explain that lambda function? – control-zed Oct 05 '18 at 06:17
  • I loved your solution here is mine(using the lambda from you :p) `print([(i[0],i[1]) for i,j in zip(sorted(Counter(input()).items(),key=lambda x:(-x[1],x[0])),range(3))])` – control-zed Oct 05 '18 at 06:45
  • look at this solution too: `from collections import Counter, OrderedDict class OrderedCounter(Counter, OrderedDict): pass [print(*c) for c in OrderedCounter(sorted(input())).most_common(3)]` – control-zed Oct 05 '18 at 06:47
  • `But dictionary does not maintain the insertion order.` This is incorrect. In Python 3.6+, dictionaries (including `Counter`) *are* insertion ordered. – jpp Oct 05 '18 at 08:17
  • @jpp Thanks for bringing this point. Yes they do maintain the order in python 3.6+ higher version. But as per the old implementation it doesn't. – Rohith Rangaraju Oct 05 '18 at 08:26
  • But Counter isn't giving the insertion order. I've also tried list(Counter()). No help. They maintain, but how to retrieve? – control-zed Oct 05 '18 at 08:27
  • @control-zed, See [How are Counter / defaultdict ordered in Python 3.7?](https://stackoverflow.com/questions/52174284/how-are-counter-defaultdict-ordered-in-python-3-7) You need to specify which Python version you are using, it's not clear in your question. – jpp Oct 05 '18 at 08:33
-1

(Python 2.6+) I could solve this using the OrderedCounter. You can see for the explanation of it here : How Ordered Counter recipe works

from collections import Counter, OrderedDict


class OrderedCounter(Counter, OrderedDict):
    pass

dict([c for c in OrderedCounter(sorted(input())).most_common(3)])

Further adding, most_common(n) is a method of collections.Counter class that returns the first n elements in that dictionary. Ref: most_common([n])

control-zed
  • 45
  • 1
  • 12