【问题标题】:Is there a pure python implementation of MurmurHash?MurmurHash 有纯 python 实现吗?
【发布时间】:2023-03-31 11:33:01
【问题描述】:

我需要(并且找不到)MurmurHash 的纯 python(无 c++)实现,而且我自己写的太新手了。速度或内存使用对我的项目来说并不重要。

我找到了一个尝试here,但它仅限于 31 位哈希,我真的需要一个 64 位哈希。

注意:对于那些需要快速实现的人,有一个 MurmurHash2 库here 和一个 MurmurHash3 库here

【问题讨论】:

  • 为什么要纯 Python 实现?
  • 我需要一个纯 Python 实现,因为我的应用程序需要在无法执行非 Python 代码的平台(如 Google App Engine)上运行。
  • 也有这个,不过我觉得你找到的那个mmh3看起来比较好用。 code.google.com/p/pyfasthash
  • C/C++,不是纯 python...(“它提供了几种常见的哈希算法和 C/C++ 实现以提高性能”)

标签: python hash murmurhash


【解决方案1】:

这是未经测试的(对不起!),但这是我想出的一个版本。 Python 允许使用任意大的整数,因此我为前 8 个字节(或 64 位)创建了一个掩码,然后我将其(通过按位与)应用于所有可能产生大于 64 位整数的算术结果。 也许其他人可以评论一般方法和可能的字节序问题等。

def bytes_to_long(bytes):
    assert len(bytes) == 8
    return sum((b << (k * 8) for k, b in enumerate(bytes)))


def murmur(data, seed):

    m = 0xc6a4a7935bd1e995
    r = 47

    MASK = 2 ** 64 - 1

    data_as_bytes = bytearray(data)

    h = seed ^ ((m * len(data_as_bytes)) & MASK)

    for ll in range(0, len(data_as_bytes), 8):
        k = bytes_to_long(data_as_bytes[ll:ll + 8])
        k = (k * m) & MASK
        k = k ^ ((k >> r) & MASK)
        k = (k * m) & MASK
        h = (h ^ k)
        h = (h * m) & MASK

    l = len(data_as_bytes) & 7

    if l >= 7:
        h = (h ^ (data_as_bytes[6] << 48))

    if l >= 6:
        h = (h ^ (data_as_bytes[5] << 40))

    if l >= 5:
        h = (h ^ (data_as_bytes[4] << 32))

    if l >= 4:
        h = (h ^ (data_as_bytes[3] << 24))

    if l >= 3:
        h = (h ^ (data_as_bytes[4] << 16))

    if l >= 2:
        h = (h ^ (data_as_bytes[4] << 8))

    if l >= 1:
        h = (h ^ data_as_bytes[4])
        h = (h * m) & MASK

    h = h ^ ((h >> r) & MASK)
    h = (h * m) & MASK
    h = h ^ ((h >> r) & MASK)

    return h

【讨论】:

    【解决方案2】:

    修复 Nikolas 版本中的错误

    def bytes_to_long(bytes):
        assert len(bytes) == 8
        return sum((b << (k * 8) for k, b in enumerate(bytes)))
    
    
    def murmur64(data, seed = 19820125):
    
        m = 0xc6a4a7935bd1e995
        r = 47
    
        MASK = 2 ** 64 - 1
    
        data_as_bytes = bytearray(data)
    
        h = seed ^ ((m * len(data_as_bytes)) & MASK)
    
        off = len(data_as_bytes)/8*8
        for ll in range(0, off, 8):
            k = bytes_to_long(data_as_bytes[ll:ll + 8])
            k = (k * m) & MASK
            k = k ^ ((k >> r) & MASK)
            k = (k * m) & MASK
            h = (h ^ k)
            h = (h * m) & MASK
    
        l = len(data_as_bytes) & 7
    
        if l >= 7:
            h = (h ^ (data_as_bytes[off+6] << 48))
    
        if l >= 6:
            h = (h ^ (data_as_bytes[off+5] << 40))
    
        if l >= 5:
            h = (h ^ (data_as_bytes[off+4] << 32))
    
        if l >= 4:
            h = (h ^ (data_as_bytes[off+3] << 24))
    
        if l >= 3:
            h = (h ^ (data_as_bytes[off+2] << 16))
    
        if l >= 2:
            h = (h ^ (data_as_bytes[off+1] << 8))
    
        if l >= 1:
            h = (h ^ data_as_bytes[off])
            h = (h * m) & MASK
    
        h = h ^ ((h >> r) & MASK)
        h = (h * m) & MASK
        h = h ^ ((h >> r) & MASK)
    
        return h
    

    【讨论】:

    • 对于 Python3 将 off = len(data_as_bytes)/8*8 更改为 off = int(len(data_as_bytes)/8)*8
    • 为了恭维@zhuhongk的回答,Python版本返回无符号数,如Java版本返回有符号数,最后添加以下行:`` h = ctypes.c_long(h).value ` `
    【解决方案3】:

    MurmurHash3 的另一个纯 python 实现,它完全兼容并可被 mmh3 包装器替换,但仍仅限于 32 位 Murmur3: https://github.com/wc-duck/pymmh3

    【讨论】:

      【解决方案4】:

      这里是 MurmurHash3 32 的纯 Python 实现,它只对字符串进行哈希处理,但可以很容易地改用字节数组。这是算法的Java version 的一个端口。

      def murmur3_x86_32(data, seed = 0):
          c1 = 0xcc9e2d51
          c2 = 0x1b873593
      
          length = len(data)
          h1 = seed
          roundedEnd = (length & 0xfffffffc)  # round down to 4 byte block
          for i in range(0, roundedEnd, 4):
            # little endian load order
            k1 = (ord(data[i]) & 0xff) | ((ord(data[i + 1]) & 0xff) << 8) | \
                 ((ord(data[i + 2]) & 0xff) << 16) | (ord(data[i + 3]) << 24)
            k1 *= c1
            k1 = (k1 << 15) | ((k1 & 0xffffffff) >> 17) # ROTL32(k1,15)
            k1 *= c2
      
            h1 ^= k1
            h1 = (h1 << 13) | ((h1 & 0xffffffff) >> 19)  # ROTL32(h1,13)
            h1 = h1 * 5 + 0xe6546b64
      
          # tail
          k1 = 0
      
          val = length & 0x03
          if val == 3:
              k1 = (ord(data[roundedEnd + 2]) & 0xff) << 16
          # fallthrough
          if val in [2, 3]:
              k1 |= (ord(data[roundedEnd + 1]) & 0xff) << 8
          # fallthrough
          if val in [1, 2, 3]:
              k1 |= ord(data[roundedEnd]) & 0xff
              k1 *= c1
              k1 = (k1 << 15) | ((k1 & 0xffffffff) >> 17)  # ROTL32(k1,15)
              k1 *= c2
              h1 ^= k1
      
          # finalization
          h1 ^= length
      
          # fmix(h1)
          h1 ^= ((h1 & 0xffffffff) >> 16)
          h1 *= 0x85ebca6b
          h1 ^= ((h1 & 0xffffffff) >> 13)
          h1 *= 0xc2b2ae35
          h1 ^= ((h1 & 0xffffffff) >> 16)
      
          return h1 & 0xffffffff
      

      【讨论】:

      • 警告:该算法返回一个无符号 Python 整数 (>=0),而标准 Java 解决方案返回一个 32 位有符号整数(2 个补码)
      【解决方案5】:

      我建议更改 bytes_to_long 函数,以便可以使用少于 8 个字节的值:

      def bytes_to_long(bytes):
          length = len(bytes)
          if length < 8:
              extra = 8 - length
              bytes = b'\000' * extra + bytes
          assert len(bytes) == 8
         return sum((b << (k * 8) for k, b in enumerate(bytes)))
      

      这会用 Null 字节填充 bytearray,以便断言仍然可以按预期工作(您现在可以将其删除),但允许转换较小的值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-01
        • 2011-02-25
        • 1970-01-01
        • 2021-09-21
        • 2020-02-25
        • 1970-01-01
        • 2012-07-30
        • 2020-11-15
        相关资源
        最近更新 更多