0

Given a word and a list of words, I have to find the list elements/words that can be built using letters (count of letter matters) of the given word. I have tried to use Counter object from collections and a definition of python 2.7's cmp() function (I'm using 3.6.5).

I have since come to realize this approach seems to be bad practice for such a problem (Earlier, I was trying to use counter object-dictionaries to compare). The reason my program doesn't work is because the compare_fn relies on '>','<' operations between lists, which give result based on lexicographical order (referred from here). So even though 'raven' can be made from 'ravenous', the program below will fail because of the order of char in a sorted list.

from collections import Counter    
word = 'ravenous'
candidates = ["raven", "mealwheel", "rasputin"]

def count_fn(mystr):
    return sorted(list(Counter(mystr).items()))

def compare_fn (c1,c2):
    return ((c1>c2) - (c1<c2))

list_word =  count_fn(word)
list_candidates = list(map(count_fn,candidates))
cmp_list = [compare_fn(list_word,i) for i in list_candidates]
cmp_list
#[-1, -1, -1]    #should be [1,-1,-1]

So, for below two lists, how can I confirm that list_candidates[0] is a subset of list_word. Please note that the comparison ('a',1) in list_word against ('a',1) in list_candidates[i] could also be ('a',5) in list_word against ('a',1) in list_candidates[i] ; both cases are true.

print(list_word)
#[('a', 1), ('e', 1), ('n', 1), ('o', 1), ('r', 1), ('s', 1), ('u', 1), ('v', 1)]
print(list_candidates[0])
#[('a', 1), ('e', 1), ('n', 1), ('r', 1), ('v', 1)]
pyeR_biz
  • 986
  • 12
  • 36
  • What happens `('a', 1)` is in `list_word` and `('a', 2)` is in `list_candidates` ? – BcK Jun 05 '18 at 06:41
  • 1
    Related question: [Find all letters of a string in another string](//stackoverflow.com/q/49975213) – Aran-Fey Jun 05 '18 at 06:45
  • @BcK, it will be False. Example - I can't make 'Rusts' from 'rustify' (need two s) ; but I can make 'Ub' from 'Buu' (one extra U) – pyeR_biz Jun 05 '18 at 17:44

1 Answers1

2

I think using counters is a good choice. Don't turn them into lists. I purposely returned [True, False, False] instead of [1, -1, -1], but you can change that easily.

Moreover: I used a list comprehension instead of map, beacause it is more current in python, but the semantic is the same.

from collections import Counter
word = 'ravenous'
candidates = ["raven", "mealwheel", "rasputin"]

def count_fn(mystr):
    return Counter(mystr)

def compare_fn (c1,c2):
    return all(c1[char] >= count for char, count in c2.items())

counter_word =  count_fn(word)
list_candidates = [count_fn(candidate) for candidate in candidates]
cmp_list = [compare_fn(counter_word, i) for i in list_candidates]
print(cmp_list)
Gelineau
  • 2,031
  • 4
  • 20
  • 30
  • 1
    You could alternatively define `compare_fn` as `c2 - c1 == {}`. – Aran-Fey Jun 05 '18 at 06:47
  • 1
    @Aran-Fey , I had used help(Counter) and came across c2-c1 in the documentation. from what I understand, if the counter element is an exact match, the result won't show. "difference stripping away all negative and zero counts" . So, if the words compared are anagrams, then it won't be in the result. I was earlier going to ask on SO, how to return zero count instances in c2-c1. Where am I going wrong? – pyeR_biz Jun 05 '18 at 18:00
  • @Aran-Fey from help (collections.Counter) - >>> Counter('bccd') - Counter('bccd') | Counter() // how does empty counter become True? – pyeR_biz Jun 05 '18 at 18:18
  • 1
    @Poppinyoshi `Counter` is a dict subclass, so comparing an empty counter to an empty dict returns `True`. But you have to use `==`, not `|`. – Aran-Fey Jun 05 '18 at 18:33