うわー、@wildplasserの答えは実際には非常にスマートです。少し拡張するには:
任意の数は、一意の方法で素数に分解できます(これは算術の基本定理として知られています)。彼の答えは、入力配列が素因数分解の表現である数値を構築することによって、それに依存しています。乗算は交換可能であるため、配列内の要素の正確な順序は重要ではありませんが、指定された数は要素の 1 つの (そして 1 つのみの) シーケンスに関連付けられます。
彼のソリューションは、Python などで任意のサイズに拡張できます。
import operator
import itertools
import math
class primes(object):
def __init__(self):
self.primes = [2,3,5,7,11]
self.stream = itertools.count(13, 2)
def __getitem__(self, i):
sq = int(math.sqrt(i))
while i >= len(self.primes):
n = self.stream.next()
while any(n % p == 0 for p in self.primes if p <= sq):
n = self.stream.next()
self.primes.append(n)
return self.primes[i]
def prod(itr):
return reduce(operator.mul, itr, 1)
p = primes()
def hash(array):
return prod(p[i] for i in array)
期待される結果:
>>> hash([1,2,2,3,5])
6825
>>> hash([5,3,2,2,1])
6825
ここで6825 = 3^1 x 5^2 x 7^1 x 13^1
は3
、'1' 素数 (0 インデックス)、5
'2' など...
>>> 3**1 * 5**2 * 7**1 * 13**1
6825
最終結果が使用しているドメイン内にある限り、数値自体の構築は O(n) 乗算int
です (残念ながら、すぐに手に負えなくなる可能性があると思います)。私が行ったように、エラトステネスのふるいで一連の素数を構築すると、漸近的に O(N * log log N) になります。ここで、N は m 番目に大きい素数です。漸近的に、N ~ m log m、これは O(n + m * log m * loglog (m * log m)) の全体的な複雑さを与えます。
同様のアプローチを使用して、素数の分解を行う代わりに、配列を基数の分解の表現と見なすこともできます。一貫性を保つために、このベースは多数の同様の要素よりも大きくなければなりません (たとえば、 の場合[5, 3, 3, 2, 1]
、 が 2 つあるため、ベースは > 2 でなければなりません3
)。安全のために、次のように書くことができます。
def hash2(array):
n = len(array)
return sum(n**i for i in array)
>>> hash2([1,5,3,2,2])
8070
>>> hash2([2,1,5,2,3])
8070
最初に配列内の類似要素の最大数を計算することでこれを改善できますが、関数は同じ基底hash2
で使用された場合にのみ実数ハッシュになるため、可変長の配列を使用して作業する場合、素数分解はおそらく安全です。数字のバッグごとに常に同じ一意の整数を返すためです。