15

I’m working on an algorithm in Python that would take user input and tell them what new letters they would need to add to a string to make it into a different string, and I’ve been playing around a lot with the dictionaries created by the Counter method.

I want to compare two different dictionaries that are counting letters from strings (like the objects returned from using the Counter tool from the collections module). We can call these dictionaries D1 and D2. I want there to be two resulting dictionaries (R1 and R2), the first being the shared letters between the two, and the second being the letters needed to make R1 into R2 (the letters that are in D2 but not in D1).

For example:

# assuming they’ve been converted from counter objects into regular 
dictionaries #

D1 = {‘A’: 2, ‘B’: 1, ‘C’: 4, ‘D’: 5}
D2 = {‘A’: 3, ‘B’: 4, ‘C’ : 4, ‘D’: 7}

# Some sort of comparison function executed here #

Result:

R1={‘A’: 2, ‘B’: 3, ‘C’: 4, ‘D’: 5} 
R2 = {‘A’: 1, ‘B’: 1, ‘C’: 0 , ‘D’: 2} 
Nick Hart
  • 165
  • 1
  • 1
  • 5

5 Answers5

17

If by shared letters you mean the Counter intersection, you can use the & operator and the amount of letters needed to convert R1 into R2 can be seen as the difference:

from collections import Counter

D1 = Counter({'A': 2, 'B': 1, 'C': 4, 'D': 5})
D2 = Counter({'A': 3, 'B': 4, 'C': 4, 'D': 7})

R1 = D1 & D2

print(R1)  # intersection:  min(c[x], d[x])
print(D2 - D1)  # subtract (keeping only positive counts)

Output

Counter({'D': 5, 'C': 4, 'A': 2, 'B': 1})
Counter({'B': 3, 'D': 2, 'A': 1})

If you want to keep negative counts, you can do it like this:

from collections import Counter

D1 = Counter({'A': 2, 'B': 1, 'C': 4, 'D': 5, 'E': 5})
D2 = Counter({'A': 3, 'B': 4, 'C': 4, 'D': 7, 'E': 3})

R2 = Counter({key: D2.get(key, 0) - value for key, value in D1.items()})
print(R2)

Output

Counter({'B': 3, 'D': 2, 'A': 1, 'C': 0, 'E': -2})

In the above example 'E' : -2 because the count of E is 5 in D1 and 3 in D2. Note: All the examples are in Python 3.5.

Dani Mesejo
  • 61,499
  • 6
  • 49
  • 76
  • wow thanks so much.. i guess i seriously underestimated what Counter can actually do on its own. this pretty much solves my problem – Nick Hart Sep 14 '18 at 02:47
9

Those operations are already built-in to the Counter type:

Several mathematical operations are provided for combining Counter objects to produce multisets (counters that have counts greater than zero). Addition and subtraction combine counters by adding or subtracting the counts of corresponding elements. Intersection and union return the minimum and maximum of corresponding counts.

(Quoted from Python collections.Counter docs.)

So assuming D1 and D2 are Counters, try

R1 = D1 & D2
R2 = D2 - R1
rici
  • 234,347
  • 28
  • 237
  • 341
1
IntersectCounter=[]
for each in D1:
        if D1[each]==D2[each]:
              IntersectCounter.append(each)

this gives common content on both the Counters D1 and D2

alanpaivaa
  • 1,959
  • 1
  • 14
  • 23
Jai Balaj
  • 11
  • 2
0

I was not able to understand the question, but from my understanding:

R1 = {k: min(v, D2[k]) for k, v in D1.items()}
R2 = {k: abs(v - D2[k]) for k, v in D1.items()}

Result

>>> {k: min(v, D2[k]) for k, v in D1.items()}
{'A': 2, 'B': 1, 'C': 4, 'D': 5}
>>> {k: abs(v - D2[k]) for k, v in D1.items()}
{'A': 1, 'B': 3, 'C': 0, 'D': 2}
0

Since Dani Mesejo's answer, the subtract method (docs) has been added for keeping negative counts in Counter objects (only inplace however), i.e. the following is a more succinct solution:

from collections import Counter

D1 = Counter({'A': 2, 'B': 1, 'C': 4, 'D': 5, 'E': 5})
D2 = Counter({'A': 3, 'B': 4, 'C': 4, 'D': 7, 'E': 3})

R1 = D1 & D2
print(R1)  

D2.subtract(D1)
print(D2)

Output:

Counter({'D': 5, 'C': 4, 'E': 3, 'A': 2, 'B': 1})
Counter({'B': 3, 'D': 2, 'A': 1, 'C': 0, 'E': -2})
jackkirk
  • 73
  • 1
  • 7