处理下面的分组问题。我目前的解决方案是按单个字符对每个单词进行排序,然后将相同的排序值映射到字典中。
想知道是否有更好的想法,其算法时间复杂度更低?我正在考虑不进行排序的方法,比如哈希,但哈希也需要单词的顺序字符。
发布问题和我的代码,用Python 2.7编写。
问题,
列出像[老鼠,星星,艺术,cie,冰]这样的词汇,将相同的字谜组合成桶并输出。 [老鼠,明星,艺术] [cie,ice]
源代码,
from collections import defaultdict
def group_anagram(anagrams):
result = defaultdict(list)
for a in anagrams:
result[''.join(sorted(list(a)))].append(a)
return result
if __name__ == "__main__":
anagrams = ['rats', 'star', 'arts', 'cie', 'ice']
print group_anagram(anagrams)
答案 0 :(得分:3)
素数因子分解是唯一的,乘法顺序无关紧要。
您可以指定a = 2, b = 3, c = 5, d = 7
等。
然后dab = 7 * 2 * 3 = 42 = 3 * 2 * 7 =坏,所以你的哈希值是42。
另一种选择是hash(frozenset(collections.Counter(word).items()))
编辑:可能最快的是使用26位。对于单词中的每个字符,翻转与其对应的位。您可能会遇到一些冲突,在这种情况下,您可以重复查找
答案 1 :(得分:2)
您当前的方法可能是最好的。为了测试,我使用了你的方法,@ bigballer的优秀答案的方法和使用计数元组作为关键的第三种方法。为了对方法进行压力测试,我在大量(264,097个单词)单词列表yawl上使用它们,运行每个函数100次,并计算每个方法的平均时间:
from collections import defaultdict
import timeit
def group_anagram1(anagrams):
result = defaultdict(list)
for a in anagrams:
result[''.join(sorted(a))].append(a)
return result.values()
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101]
def group_anagram2(anagrams):
result = defaultdict(list)
for a in anagrams:
n = 1
for c in a:
n *= primes[ord(c) - ord('a')]
result[n].append(a)
return result.values()
def group_anagram3(anagrams):
result = defaultdict(list)
for a in anagrams:
counts = [0]*26
for c in a:
counts[ord(c) - ord('a')] += 1
result[tuple(counts)].append(a)
return result.values()
with open("yawl.txt") as f:
words = f.readlines()
words =[w.strip() for w in words]
print timeit.timeit("group_anagram1(words)", setup="from __main__ import group_anagram1,words",number = 100)/100.0
print timeit.timeit("group_anagram2(words)", setup="from __main__ import group_anagram2,words",number = 100)/100.0
print timeit.timeit("group_anagram3(words)", setup="from __main__ import group_anagram3,words",number = 100)/100.0
输出(在我的机器YMMV上):
0.486009083239
0.64333226691
0.797640375079
实际上,考虑到yawl
的大小,所有方法都非常快速,每个方法处理超过25万个单词的时间不到一秒。然而,你原来的方法是明显的赢家。此外,它不限于拉丁语'a'
到'z'
字母表。至于为何最好 - 键是由Python内置函数(运行优化的C代码)直接构造的,但其他方法使用解释的Python代码。很难打败内置插件。
在编辑:我使用此素数列表重新实现了第二种方法,进行了排序,以便为更频繁的字母(使用英语)分配较小的素数:
primes = [5,71,37,29,2,53,59,19,11,83,79,31,43,13,7,67,97,23,17,3,41,73,47,89,61,101]
它缩短了一小段时间,但不足以使它比第一种方法更快。
进一步修改:
我重复了上面的代码,并对第二种方法进行了以下调整(正如@bigballer所建议的那样):
primes = [5,71,37,29,2,53,59,19,11,83,79,31,43,13,7,67,97,23,17,3,41,73,47,89,61,101]
primes = {c:p for c,p in zip('abcdefghijklmnopqrstuvwxyz',primes)}
def group_anagram2(anagrams):
result = defaultdict(list)
for a in anagrams:
n = 1
for c in a:
n *= primes[c]
result[n].append(a)
return result.values()
使用这个版本,前两个方法降为虚拟平局,基于素数的方法在我的有限测试中稍微快一些(更快约8%)。尽管如此,我仍然认为第一种方法更可取,因为它不依赖于固定的字母表。