1

This is my function:

def freq(*var):
    from functools import reduce
    from operator import getitem
    for c,i in enumerate(reversed(var)): 
        d={k:0 for k in set(i)} if c==0 else {k:d for k in set(i)}
    for row in zip(*var):
        *k,last_k=row
        reduce(getitem,k,d)[last_k]+=1

    return d

var argument would look like (['a','b','b','c'],['one','two','two','two'])

I am trying to return a nested dictionary that holds a frequency count. So result d should look like:

{'a':{'one':1, 'two':0}, 'b':{'one':0, 'two':2}, 'c':{'one':0, 'two':1}}

However my function returns, which is wrong:

{'a': {'one': 1, 'two': 3}, 'b': {'one': 1, 'two': 3}, 'c': {'one': 1, 'two': 3}}

Any idea why?

Andriy Ivaneyko
  • 20,639
  • 6
  • 60
  • 82
LMc
  • 12,577
  • 3
  • 31
  • 43

3 Answers3

2

The problem is that all nested dicts in d dict is the same object. Use dict.copy() method to fix problem, so changing line:

d={k:0 for k in set(i)} if c==0 else {k:d for k in set(i)}

to

d={k:0 for k in set(i)} if c==0 else {k:d.copy() for k in set(i)}

Would fix your problem. See more on Immutable vs Mutable types and How do I pass a variable by reference?

Good Luck!

Community
  • 1
  • 1
Andriy Ivaneyko
  • 20,639
  • 6
  • 60
  • 82
0

You've somehow initialized d's values to the same dictionary. I can't fix this confidently, since I don't have Python3 installed here (blush). However, I think the problem is that k:d value in the middle line: I think it plugs the identical default dictionary into each value. Try creating a new empty dictionary for each pass through this iteration.

Prune
  • 76,765
  • 14
  • 60
  • 81
0

Apparently, the inner dict is referencing the same object and everything gets updated simultenously, as seen from the enumerate loop:

for c,i in enumerate(reversed(var)): 
     d={k:0 for k in set(i)} if c==0 else {k:d for k in set(i)}
#                                            ^

The inner dictionary d used in the second iteration of the loop is the same object. You can use d.copy() in place of d to create multiple copies for each key.


But why go through all the trouble of using reduce on getitem. That part of your code is quite hard to debug.

You can achieve the same thing by using a Counter object on your zipped items:

from collections import Counter

var = ['a','b','b','c'],['one','two','two','two']

c = Counter(zip(*var))
d = {k[0]: {j: v if j in k else 0 for j in set(var[1])} for k,v in c.items()}
print(d)
# {'b': {'two': 2, 'one': 0}, 'c': {'two': 1, 'one': 0}, 'a': {'two': 0, 'one': 1}}
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • `var` may have one or more elements. Here I used two, but it could have more or less. – LMc Jun 28 '16 at 19:13
  • The code can be extended by creating deeper nests in the dict comprehension. You can make this dynamic by refactoring it into a loop that takes cognizance to the number of elements in `var` – Moses Koledoye Jun 28 '16 at 19:18