我有一个大集合(〜数千)整数序列。每个序列都具有以下属性:
请注意,属性2.和3.意味着序列实际上是 sets ,但它们存储为C数组,以便最大限度地提高访问速度。
我正在寻找一个好的C ++算法来检查集合中是否已存在新序列。如果不是,则将新序列添加到集合中。我想过使用哈希表(但请注意,我不能使用任何C ++ 11结构或外部库,例如Boost)。散列序列并将值存储在std::set
中也是一种选择,因为如果碰撞很少见,就可以忽略它们。任何其他建议也是受欢迎的。
我需要一个可交换的哈希函数,即一个不依赖于序列中元素顺序的函数。我想首先将序列缩减为某些规范形式(例如排序),然后使用标准散列函数(参见下面的参考文献),但我宁愿避免与复制相关的开销(我无法修改原始序列)和排序。据我所知,下面引用的函数都不是可交换的。理想情况下,散列函数还应该利用元素永不重复的事实。速度至关重要。
有什么建议吗?
答案 0 :(得分:5)
这是一个基本想法;随意修改它。
散列整数只是身份。
我们使用boost::hash_combine
中的公式来合并哈希值。
我们对数组进行排序以获得一个独特的代表。
代码:
#include <algorithm>
std::size_t array_hash(int (&array)[12])
{
int a[12];
std::copy(array, array + 12, a);
std::sort(a, a + 12);
std::size_t result = 0;
for (int * p = a; p != a + 12; ++p)
{
std::size_t const h = *p; // the "identity hash"
result ^= h + 0x9e3779b9 + (result << 6) + (result >> 2);
}
return result;
}
更新:抓住那个。您刚刚将问题编辑为完全不同的问题。
如果每个数字最多为300,那么您可以将排序后的数组分别压缩为9位,即108位。 “无序”属性只能为您节省额外的12个!,大约是29位,所以它并没有真正有所作为。
您可以查找128位无符号整数类型,并直接在其中存储已排序的打包整数集。或者,您可以将该范围拆分为两个64位整数,并按上述方式计算哈希值:
uint64_t hash = lower_part + 0x9e3779b9 + (upper_part << 6) + (upper_part >> 2);
(或者使用0x9E3779B97F4A7C15
作为幻数,即64位版本。)
答案 1 :(得分:4)
我只是使用sum函数作为哈希,看看你有多远。这不利用数据的非重复特性,也不利用它们全部&lt; 300.另一方面,它的速度非常快。
std::size_t hash(int (&arr)[12]) {
return std::accumulate(arr, arr + 12, 0);
}
由于函数需要不知道排序,我没有看到一种智能的方法来利用有限范围的输入值而不首先对它们进行排序。如果这是绝对必要的,碰撞方面,我会硬编码sorting network(即一些if
... else
语句)来对12个值进行排序(但是我不知道12个值的排序网络会是什么样子,或者即使它是实用的。)
编辑在评论中讨论之后,这是减少冲突的一种非常好的方法:在求和之前将数组中的每个值提升到某个整数幂。最简单的方法是通过transform
。这确实产生了一个副本,但可能仍然非常快:
struct pow2 {
int operator ()(int n) const { return n * n; }
};
std::size_t hash(int (&arr)[12]) {
int raised[12];
std::transform(arr, arr + 12, raised, pow2());
return std::accumulate(raised, raised + 12, 0);
}
答案 2 :(得分:4)
您可以在大小为300的位集中切换对应于12个整数中每个整数的位。然后使用boost :: hash_combine中的公式组合10个32位整数,实现此位集。
这给出了可交换的散列函数,不使用排序,并利用元素永不重复的事实。
如果我们选择任意位集大小并且如果我们为12个整数中的每一个设置或切换任意数量的位,则可以推广这种方法(300个值中的每一个的设置/切换位由散列函数确定)或使用预先计算的查找表)。这导致Bloom filter或相关结构。
我们可以选择大小为32或64位的Bloom过滤器。在这种情况下,不需要将多个大位向量组合成单个散列值。对于大小为32的布隆过滤器的经典实现,最佳散列函数数(或查找表的每个值的非零位)为2。
如果,而不是&#34;或&#34;经典布鲁姆过滤器的操作,我们选择&#34; xor&#34;并且对于查找表的每个值使用半个非零位,我们得到一个解决方案,由Jim Balter提到。
如果,而不是&#34;或&#34;操作,我们选择&#34; +&#34;并且对于查找表的每个值使用大约一半的非零位,我们得到一个类似于一个的解决方案,由Konrad Rudolph建议。
答案 3 :(得分:3)
以数字方式对序列的元素进行排序,然后将序列存储在trie中。 trie的每个级别都是一个数据结构,您可以在其中搜索该级别的元素...您可以根据其中的元素数量使用不同的数据结构...例如,链接列表,二叉搜索树,或排序的矢量。
如果你想使用哈希表而不是trie,那么你仍然可以用数字方式对元素进行排序,然后应用其中一个非交换哈希函数。您需要对元素进行排序以比较序列,您必须这样做,因为您将有哈希表冲突。如果你不需要排序,那么你可以将每个元素乘以一个常数因子,将它们涂抹在int的位上(有找到这样一个因子的理论,但你可以通过实验找到它),然后XOR结果。或者你可以在一个表中查找你的~300个值,将它们映射到通过XOR很好地混合的唯一值(每个值可以选择一个随机值,使得它具有相同数量的0和1位 - 每个XOR翻转一个随机的一半比特,这是最优的。)
答案 4 :(得分:2)
我接受Jim Balter's answer,因为他是最接近我最终编码的人,但所有答案都得到了我的+1帮助。
这是我最终得到的算法。我写了一个小的Python脚本,它生成300个64位整数,使得它们的二进制表示包含32个真正的32位和32个错误位。真位的位置是随机分布的。
import itertools
import random
import sys
def random_combination(iterable, r):
"Random selection from itertools.combinations(iterable, r)"
pool = tuple(iterable)
n = len(pool)
indices = sorted(random.sample(xrange(n), r))
return tuple(pool[i] for i in indices)
mask_size = 64
mask_size_over_2 = mask_size/2
nmasks = 300
suffix='UL'
print 'HashType mask[' + str(nmasks) + '] = {'
for i in range(nmasks):
combo = random_combination(xrange(mask_size),mask_size_over_2)
mask = 0;
for j in combo:
mask |= (1<<j);
if(i<nmasks-1):
print '\t' + str(mask) + suffix + ','
else:
print '\t' + str(mask) + suffix + ' };'
脚本生成的C ++数组使用如下:
typedef int_least64_t HashType;
const int maxTableSize = 300;
HashType mask[maxTableSize] = {
// generated array goes here
};
inline HashType xorrer(HashType const &l, HashType const &r) {
return l^mask[r];
}
HashType hashConfig(HashType *sequence, int n) {
return std::accumulate(sequence, sequence+n, (HashType)0, xorrer);
}
此算法是我尝试过的最快的算法(this,this使用多维数据集,this使用大小为300的位集。对于我的#34;典型&#34;整数序列,碰撞率小于1E-7,这完全可以接受我的目的。