代码之家  ›  专栏  ›  技术社区  ›  landings

如何散列整数的环形缓冲区?

  •  0
  • landings  · 技术社区  · 3 年前

    我正在使用Python。我有一些包含整数的列表,但它们实际上是环形缓冲区。

    以下是示例规则:

    • 我们不添加新元素或修改任何元素。这些环是不可变的。

    • 环中没有重复元素。

    • 如果两个列表的长度不同,则它们不是同一个环。

    • 在两个相同长度的列表之间,如果是一个列表,则在 环形移位 逆转 ,可以与另一个相同,两个环相等。例如 [1, 7, 9, 2, 5] [7, 9, 2, 5, 1] (环移位)相等, [1,7,9,2,5] [1, 5, 2, 9, 7] (环移位和反转)相等,但是 [1,7,9,2,5] [7, 1, 9, 2, 5] 不平等。

    我想快速确定两个环是否相等。

    一种方法是比较它们的元素,另一种方法则是找到一种好的哈希方法。我试着将两个列表切换到它们的正常状态,并比较它们的元素是否相同(或者反转后是否相同),但速度太慢了。

    我认为哈希是一个更好的选择。那么,对于这种环形缓冲区,什么哈希方法是好的呢?

    以下是我目前拥有的:

    import random
    from time import perf_counter
    from typing import List, Tuple
    
    class Ring:
        def __init__(self, ids:List[int]) -> None:
            self.ids = ids
    
        def get_shifted(self, n:int) -> 'Ring':
            result_list = self.ids.copy()
            for i in range(n):
                head = result_list[0]
                result_list.remove(head)
                result_list.append(head)
            return Ring(result_list)
        
        def get_normalized(self) -> 'Ring':
            min_i = self.ids.index(min(self.ids))
            shifted = self.get_shifted(min_i)
            return shifted
        
        def get_reversed(self) -> 'Ring':
            result_list = self.ids.copy()
            result_list.reverse()
            return Ring(result_list)
    
        def __eq__(self, other: 'Ring') -> bool:
            if len(self.ids) != len(other.ids):
                return False
            normalized1 = tuple(self.get_normalized().ids)
            normalized2 = tuple(self.get_reversed().get_normalized().ids)
            normalized_other = tuple(other.get_normalized().ids)
            return normalized1 == normalized_other or normalized2 == normalized_other
        
        @staticmethod
        def Random() -> 'Ring':
            unduplicated = set()
            while len(unduplicated) < ring_capacity:
                unduplicated.add(random.randint(0, 20))
            return Ring(list(unduplicated))
        
    
    if __name__ == '__main__':
        random.seed(1)
        ring_capacity = 5
        num_rings = 2000
    
        ring_set = []
        random_rings = [Ring.Random() for _ in range(num_rings)]
    
        start = perf_counter()
        for ring in random_rings:
            if ring not in ring_set:
                ring_set.append(ring)
        end = perf_counter()
    
        print(end - start)
        print(f'{len(ring_set)} out of {num_rings} unduplicated rings')
    
    
    3 回复  |  直到 3 年前
        1
  •  3
  •   Matt Timmermans    3 年前

    与其为等价列表提供相同哈希代码的特殊哈希,不如将每个环转换为规范形式更简单。

    例如,在所有可能的移位/反转中,您可以选择词汇上最小的一个。

    在转换为规范形式后,等价环是相同的,所以如果你想散列,那么你可以使用你想要的任何散列,如果你需要比较,你可以做一个简单的线性比较。

        2
  •  2
  •   Kelly Bundy    3 年前

    在构造时只计算一个规范化的形式会更快、更简单:

    class Ring:
        def __init__(self, ids:List[int]) -> None:
            self.ids = ids
            i = ids.index(min(ids))
            self.normed = min(
                ids[i:] + ids[:i],
                ids[i::-1] + ids[:i:-1]
            )
    
        def __eq__(self, other: 'Ring') -> bool:
            return self.normed == other.normed
    

    输出与您的:

    15.41106627508998
    1896 out of 2000 unduplicated rings
    

    矿山产量:

    0.38525977171957493
    1896 out of 2000 unduplicated rings
    

    输出如下( Attempt This Online! ):

    0.0007639830000698566
    1896 out of 2000 unduplicated rings
    

    (嗯,我刚刚意识到,通过将规范化转移到构建中,我将其从时间上移开了 random_rings = [Ring.Random() for _ in range(num_rings)] 在计时方面,整个过程仍然只需要0.03秒左右。)

    最后一个是修改您的 使用 使您 ring_set 一个实际的集合,而不是一个命名错误的列表:

        start = perf_counter()
        ring_set = set(random_rings)
        end = perf_counter()
    

    使用 Ring 类提供有意义的散列:

    class Ring:
        def __init__(self, ids:List[int]) -> None:
            self.ids = ids
            i = ids.index(min(ids))
            self.normed = min(
                ids[i:] + ids[:i],
                ids[i::-1] + ids[:i:-1]
            )
            self.hash = hash(tuple(self.normed))
    
        def __eq__(self, other: 'Ring') -> bool:
            return self.normed == other.normed
        
        def __hash__(self):
            return self.hash
    
        3
  •  1
  •   Jens    3 年前

    你的问题相当宽泛,所以任何建议都可能相当模糊。听起来你正在处理大量的数据——否则比较肯定比哈希更快。
    不能使用标准加密哈希函数,因为它们不是独立于位置的。而是寻找像Nilsimsa哈希这样的位置保留哈希函数(LP哈希)。这个想法是让相似的输入创建相似的输出,这里的相似意味着起点是任意的,并且集合仍然会创建相同的哈希。顺便说一句,用于查找候选者的非常简单的散列将是传统的(SAE J1708)校验和。