【问题标题】:hash functions family generator in pythonpython中的哈希函数族生成器
【发布时间】:2011-01-16 08:53:08
【问题描述】:

我正在寻找一个散列函数族生成器,它可以在给定一组参数的情况下生成散列函数族。到目前为止,我还没有找到任何这样的生成器。 有没有办法用hashlib 包做到这一点?

例如,我想做这样的事情:

h1 = hash_function(1)
h2 = hash_function(2)
...

h1h2 将是不同的哈希函数。

对于那些可能知道它的人,我正在尝试在一个非常大的数据集上实现最小散列算法。

基本上,对于给定的文档,我有一组非常大的特征(1 亿到 10 亿),我需要为这组特征创建 1000 到 10000 个不同的随机排列。

我不想显式构建随机排列,所以我想在下面使用该技术:

  1. 生成一个哈希函数h 并考虑两个索引rs
  2. r 出现在 s 之前的排列 if h(r) < h(s) 并为 100 到 1000 个不同的哈希函数执行此操作。

有没有我可能错过的已知库?或者您可能知道的使用 python 生成散列函数系列的任何标准方法?

【问题讨论】:

    标签: python hash generator


    【解决方案1】:

    我会做类似的事情(如果你不需要线程安全——如果你确实需要线程安全,不难改变——假设是 32 位 Python 版本):

    import random
    
    _memomask = {}
    
    def hash_function(n):
      mask = _memomask.get(n)
      if mask is None:
        random.seed(n)
        mask = _memomask[n] = random.getrandbits(32)
      def myhash(x):
        return hash(x) ^ mask
      return myhash
    

    【讨论】:

    • 感谢您的回答。它似乎工作得很好。使用这些类型的哈希函数有什么特别之处吗?效率 ?在某种意义上会产生非常不同的近似排列?
    • 内置的hash 是体面且非常高效的——用一个取决于家庭索引的数字(但以一种足够混乱的方式)对它进行异或,这似乎是另一个体面/高效将一个哈希函数变成一个系列的方法。如果速度不是问题,你可以使用更强的(加密质量)散列,我猜 - 这可能会给你更高的质量(散列和随机都不是加密质量,因此它们也不是 XOR;-) 但速​​度影响确实大(数量级......)。
    • 谢谢。实际上,我相信速度对我来说是关键。我正在寻找的唯一“质量”是哈希函数将通过我在原始问题中描述的过程生成尽可能“不同”的随机排列(虽然我不确定如何量化它......)。再次感谢您的出色回答。
    • 这不起作用,并且对于几乎所有哈希族的使用来说都是一个非常糟糕的选择。如果您打算将其用于哈希表,在其中基于哈希(布谷鸟、2 路哈希等)探测多个位置,那么这是一个 非常 糟糕的选择,与使用单个函数没有什么不同.使用不同散列函数的全部意义在于会发生不同的冲突模式,当您将散列的输出与常量进行异或运算时,它根本不会改变冲突,在一个中发生冲突的相同键将在另一个中发生冲突。
    【解决方案2】:

    如上所述,您可以对 minhash 使用通用散列。 例如:

    import random
    
    
    
    def minhash():
        d1 = set(random.randint(0, 2000) for _ in range(1000))
        d2 = set(random.randint(0, 2000) for _ in range(1000))
        jacc_sim = len(d1.intersection(d2)) / len(d1.union(d2))
        print("jaccard similarity: {}".format(jacc_sim))
    
        N_HASHES = 200
        hash_funcs = []
        for i in range(N_HASHES):
            hash_funcs.append(universal_hashing())
    
        m1 = [min([h(e) for e in d1]) for h in hash_funcs]
        m2 = [min([h(e) for e in d2]) for h in hash_funcs]
        minhash_sim = sum(int(m1[i] == m2[i]) for i in range(N_HASHES)) / N_HASHES
        print("min-hash similarity: {}".format(minhash_sim))
    
    
    
    def universal_hashing():
        def rand_prime():
            while True:
                p = random.randrange(2 ** 32, 2 ** 34, 2)
                if all(p % n != 0 for n in range(3, int((p ** 0.5) + 1), 2)):
                    return p
        m = 2 ** 32 - 1
        p = rand_prime()
        a = random.randint(0, p)
        if a % 2 == 0:
            a += 1
        b = random.randint(0, p)
        def h(x):
            return ((a * x + b) % p) % m
        return h
    

    Reference

    【讨论】:

    • 尝试编辑您的答案,但它必须超过 6 个字符。有语法错误,更正为:'minhash_sim = sum([int(m1[i] == m2[i]) for i in range(N_HASHES)]) / N_HASHES'
    【解决方案3】:

    您应该考虑使用通用散列。我的答案和代码可以在这里找到:https://stackoverflow.com/a/25104050/207661

    【讨论】:

      【解决方案4】:

      @alex 的回答非常简洁明了,但它生成的散列函数并没有“彼此非常不同”。

      让我们看看 10000 个哈希值的 10000 个样本之间的 Pearson 相关性,这些样本将结果放入 100 个 bin 中

      %%time # 1min 14s
      n=10000
      hashes = [hash_function(i) for i in range(n)]
      median_pvalue(hashes, n=n)
      # 1.1614081043690444e-06
      

      即p_value 的中位数是1e-06,这远非随机。这是一个真正随机的例子:

      %%time # 4min 15s
      hashes = [lambda _ : random.randint(0,100) for _ in range(n)]
      median_pvalue(hashes, n=n)
      # 0.4979718236429698
      

      使用 Carter 和 Wegman 方法可以得到:

      %%time # 1min 43s
      hashes = HashFamily(100).draw_hashes(n)
      median_pvalue(hashes, n=n)
      # 0.841929288037321
      

      重现代码:

      
      from scipy.stats.stats import pearsonr 
      import numpy as np
      import random
      
      _memomask = {}
      
      def hash_function(n):
          mask = _memomask.get(n)
          if mask is None:
              random.seed(n)
              mask = _memomask[n] = random.getrandbits(32)
          def myhash(x):
              return hash(x) ^ mask
          return myhash
      
      class HashFamily():
          r"""Universal hash family as proposed by Carter and Wegman.
          .. math::
                  \begin{array}{ll}
                  h_{{a,b}}(x)=((ax+b)~{\bmod  ~}p)~{\bmod  ~}m \ \mid p > m\\
                  \end{array}
          Args:
              bins (int): Number of bins to hash to. Better if a prime number.
              moduler (int,optional): Temporary hashing. Has to be a prime number.
          """
          def __init__(self, bins, moduler=None):
              if moduler and moduler <= bins:
                  raise ValueError("p (moduler) should be >> m (buckets)")
      
              self.bins = bins
              self.moduler = moduler if moduler else self._next_prime(np.random.randint(self.bins + 1, 2**32))
      
              # do not allow same a and b, as it could mean shifted hashes
              self.sampled_a = set()
              self.sampled_b = set()
      
          def _is_prime(self, x):
              """Naive is prime test."""
              for i in range(2, int(np.sqrt(x))):
                  if x % i == 0:
                      return False
              return True
      
          def _next_prime(self, n):
              """Naively gets the next prime larger than n."""
              while not self._is_prime(n):
                  n += 1
      
              return n
      
          def draw_hash(self, a=None, b=None):
              """Draws a single hash function from the family."""
              if a is None:
                  while a is None or a in self.sampled_a:
                      a = np.random.randint(1, self.moduler - 1)
                      assert len(self.sampled_a) < self.moduler - 2, "please give a bigger moduler"
      
                  self.sampled_a.add(a)
              if b is None:
                  while b is None or b in self.sampled_b:
                      b = np.random.randint(0, self.moduler - 1)
                      assert len(self.sampled_b) < self.moduler - 1, "please give a bigger moduler"
      
                  self.sampled_b.add(b)
      
              return lambda x: ((a * x + b) % self.moduler) % self.bins
      
          def draw_hashes(self, n, **kwargs):
              """Draws n hash function from the family."""
              return [self.draw_hash() for i in range(n)]
      
      def median_pvalue(hashes, buckets=100, n=1000):
          p_values = []
          for j in range(n-1):
              a = [hashes[j](i) % buckets for i in range(n)]
              b = [hashes[j+1](i) % buckets for i in range(n)]
              p_values.append(pearsonr(a,b)[1])
          return np.median(p_values)
      

      请注意,我的实现是 Carter 和 Wegman 非常幼稚(例如生成素数)。它可以做得更短更快。

      【讨论】:

        【解决方案5】:

        universal hash family 是一组散列函数H,大小为m,这样当散列函数h 从集合中随机抽取时,任何两个(区域)输入最多与1/m 发生碰撞概率H.

        根据维基百科的公式,使用可以使用以下代码:

        import random
        
        def is_prime(n):
            if n==2 or n==3: return True
            if n%2==0 or n<2: return False
            for i in range(3, int(n**0.5)+1, 2):
                if n%i==0:
                    return False    
            return True
        
        # universal hash functions
        class UniversalHashFamily:
            def __init__(self, number_of_hash_functions, number_of_buckets, min_value_for_prime_number=2, bucket_value_offset=0):
                self.number_of_buckets = number_of_buckets
                self.bucket_value_offset = bucket_value_offset
                
                primes = []
                number_to_check = min_value_for_prime_number
                while len(primes) < number_of_hash_functions:
                    if is_prime(number_to_check):
                        primes.append(number_to_check)
                    number_to_check += random.randint(1, 1000)
        
                self.hash_function_attrs = []
                for i in range(number_of_hash_functions):
                    p = primes[i]
                    a = random.randint(1, p)
                    b = random.randint(0, p)
                    self.hash_function_attrs.append((a, b, p))
            
            def __call__(self, function_index, input_integer):
                a, b, p = self.hash_function_attrs[function_index]
                return (((a*input_integer + b)%p)%self.number_of_buckets) + self.bucket_value_offset
        

        使用示例:

        我们可以创建一个包含 20 个哈希函数的哈希族,每个哈希函数将输入映射到 100 个桶。

        hash_family = UniversalHashFamily(20, 100)
        

        并获得像这样的散列值:

        input_integer = 1234567890 # sample input
        
        hash_family(0, input_integer) # the output of the first hash function, i.e. h0(input_integer)
        hash_family(1, input_integer) # the output of the second hash function, i.e. h1(input_integer)
        # ...
        hash_family(19, input_integer) # the output of the last hash function, i.e. h19(input_integer)
        

        如果您对 string 输入的通用哈希系列感兴趣,可以使用以下代码。但请注意,此代码可能不是字符串哈希的优化解决方案。

        class UniversalStringHashFamily:
            def __init__(self, number_of_hash_functions, number_of_buckets, min_value_for_prime_number=2, bucket_value_offset=0):
                self.number_of_buckets = number_of_buckets
                self.bucket_value_offset = bucket_value_offset
                
                primes = []
                number_to_check = max(min_value_for_prime_number, number_of_buckets)
                while len(primes) < number_of_hash_functions:
                    if is_prime(number_to_check):
                        primes.append(number_to_check)
                    number_to_check += random.randint(1, 1000)
        
                self.hash_function_attrs = []
                for i in range(number_of_hash_functions):
                    p = primes[i]
                    a = random.randint(1, p)
                    a2 = random.randint(1, p)
                    b = random.randint(0, p)
                    self.hash_function_attrs.append((a, b, p, a2))
            
            def hash_int(self, int_to_hash, a, b, p):
                return (((a*int_to_hash + b)%p)%self.number_of_buckets) + self.bucket_value_offset
                
            def hash_str(self, str_to_hash, a, b, p, a2):
                str_to_hash = "1" + str_to_hash # this will ensure that universality is not affected, see wikipedia for more detail
                l = len(str_to_hash)-1
                
                int_to_hash = 0
                for i in range(l+1):
                    int_to_hash += ord(str_to_hash[i]) * (a2 ** (l-i))
                int_to_hash = int_to_hash % p
                return self.hash_int(int_to_hash, a, b, p)
            
            def __call__(self, function_index, str_to_hash):
                a, b, p, a2 = self.hash_function_attrs[function_index]
                return self.hash_str(str_to_hash, a, b, p, a2)
        

        【讨论】:

          猜你喜欢
          • 2011-03-10
          • 1970-01-01
          • 1970-01-01
          • 2016-04-30
          • 2012-11-19
          • 2019-01-18
          • 2022-12-05
          • 2010-10-13
          • 2016-10-07
          相关资源
          最近更新 更多