Use collections.Counter()
perhaps; those are multi-sets, or bags, in datatype parlance:
from collections import Counter
counterA = Counter(listA)
counterB = Counter(listB)
Now you can compare these by entries or frequencies:
>>> counterA
Counter({'apple': 3, 'orange': 2, 'banana': 1})
>>> counterB
Counter({'apple': 2, 'orange': 1, 'grapefruit': 1})
>>> counterA - counterB
Counter({'orange': 1, 'apple': 1, 'banana': 1})
>>> counterB - counterA
Counter({'grapefruit': 1})
You can calculate their cosine similarity using:
import math
def counter_cosine_similarity(c1, c2):
terms = set(c1).union(c2)
dotprod = sum(c1.get(k, 0) * c2.get(k, 0) for k in terms)
magA = math.sqrt(sum(c1.get(k, 0)**2 for k in terms))
magB = math.sqrt(sum(c2.get(k, 0)**2 for k in terms))
return dotprod / (magA * magB)
Which gives:
>>> counter_cosine_similarity(counterA, counterB)
0.8728715609439696
The closer to 1 that value, the more similar the two lists are.
The cosine similarity is one score you can calculate. If you care about the length of the list, you can calculate another; if you keep that score between 0.0 and 1.0 as well you can multiply the two values for a final score between -1.0 and 1.0.
For example, to take relative lengths into account you could use:
def length_similarity(c1, c2):
lenc1 = sum(c1.itervalues())
lenc2 = sum(c2.itervalues())
return min(lenc1, lenc2) / float(max(lenc1, lenc2))
and then combine into a function that takes the lists as inputs:
def similarity_score(l1, l2):
c1, c2 = Counter(l1), Counter(l2)
return length_similarity(c1, c2) * counter_cosine_similarity(c1, c2)
For your two example lists, that results in:
>>> similarity_score(['apple', 'orange', 'apple', 'apple', 'banana', 'orange'], ['apple', 'orange', 'grapefruit', 'apple'])
0.5819143739626463
>>> similarity_score(['apple', 'apple', 'orange', 'orange'], ['apple', 'orange'])
0.4999999999999999
You can mix in other metrics as needed.