微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在多组数字之间找到交集的最有效方法

如何解决在多组数字之间找到交集的最有效方法

我正在尝试有效地压缩看起来像这样的数字集(每行一组):

19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 179 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205
19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 179 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205 206
19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205 206 45387
19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205 206 45387 45392
19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205 206 45387
19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205 206 45392
19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205 206 45392 144554
19 20 23 24 27 29 32 35 69 97 99 119 122 129 130 134 136 137 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205
19 20 23 24 27 29 32 35 69 97 99 119 122 129 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205 45392 144554
19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205
19 20 23 24 27 29 32 35 69 97 99 119 122 129 132 134 136 137 139 141 147 148 152 157 158 160 170 173 174 175 176 178 182 183 185 186 188 189 190 192 194 195 197 198 199 200 201 202 203 204 205 45392 144554

您可以轻松拥有约 10K 个集合,每个集合包含约 10K 个条目。但是,从示例数据中可以看出,集合中的大部分数据都是冗余的——每个新集合都有一些删除和一些添加。 (偶尔会有很大的变化,但这种情况很少见)。

我想将其压缩为:

  • 占用少量存储空间
  • 解压时使用最少的 cpu随机访问)
  • 最好以增量方式压缩(但回想起来也可以压缩它)。

为了在扩展时实现最少的 cpu,我试图从一组常见的子集中构建每个集合 - 即分解出最常见的重复数据子集,深度为一个级别(即无递归)。

为了确定要分解的公共子集,我尝试逐行考虑集合,并查看添加了哪些项目以及删除了哪些项目。这些添加被视为新的子集,随着时间的推移,它们会逐渐增加,大小相等的子集会成对地合并成新的子集。例如,对于第 N 个集合是整数 0 到 N 的简单情况,您得到:

({0}),({0,1}),({2}),1,2,3}),({4}),({4,5}),({6}),3,4,5,6,7}),({8}),({8,9}),({10}),9,10,11}),({12}),({12,13}),({14}),7,8,11,12,13,14,15}),

然后,如果您跟踪每个子集的“父”组件,当一个项目被移除时,您可以将给定的子集分解成它的组件(随着时间的推移,这些组件随后会再次合并)。例如,删除第 4 项会产生如下结果:

({0,({5,

...然后会合并为...

({0,

根据经验,这相当有效(磁盘空间提高了大约 5 倍),但我担心我缺少一种更明显的方法来发现可以在整个数据集中最有效地分解出哪些子集。

我还尝试构建一个前缀树来跟踪哪些前缀最常出现,然后将它们分解 - 除了这会使用大量存储空间,并且无助于压缩不是前缀的子集。它也没有利用集合是无序的这一事实。

我也试过查看签名树 (https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.6.7315&rep=rep1&type=pdf),但是当您的数据集很大而不那么稀疏时,这些树似乎使用了大量的磁盘存储空间。

我也可以进行 O(N^2) 搜索来比较每个集合的交集,并跟踪哪些子集重复出现最多的直方图,但 O(N^2) 对于大型数据集来说会很痛苦,并且在比较交叉点以发现潜在的公共子集时如何调出噪声并不明显。

TL;DR:在大量集合中发现结构相似性以排除重复子集的最佳方法是什么?

编辑:已阐明解压缩时需要随机访问。此外,我已将真实数据集发布到 http://matrix.org/~matthew/expanded.out.xz。警告:这个 2MB .xz 扩展到 4.9GB 的实际数据......这很好地说明了这个问题,以及为什么我到目前为止还没有找到比 5 倍压缩更好的方法令人沮丧:/

解决方法

我们可以结合三个简单的想法:

  1. 对连续集合之间的对称差异进行编码(我认为这就是 Mark 的建议)。

  2. 这有利于编码,但很难随机解码。要修复它,请定期发出整个集合。一种启发式方法是,每当我们在增量中发出与整个集合一样多的量时,就执行此操作 - 理论上,这只会在存储中增加一个常数因子,同时将我们扫描的增量的总大小限制为一个常数因子超过集合的大小。

  3. 对 varint 使用增量编码。这是发布列表的常用编码,因此应该有优化的实现。

Python 3 编码器,可将给定的输入压缩到小于 5 MB。我们还需要一个索引,但这不会增加太多。

import fileinput
import re
import sys

output = open("output","wb")


def emit_varint(n):
    buffer = []
    mask = 127
    while n > mask:
        buffer.append(128 | (n & mask))
        n >>= 7
    buffer.append(n)
    output.write(bytes(buffer))


def emit_indices(delta):
    emit_varint(len(delta))
    prev = 0
    for x in sorted(delta):
        emit_varint(x - prev)
        prev = x


delta_counter = 0
delta_from = 0
previous_indices = set()
for i,line in enumerate(fileinput.input()):
    if i % 1000 == 0:
        print(i,file=sys.stderr)
    m = re.match(r"[^{}]*\{(\d+(,\d+)*)\}",line)
    if not m:
        continue
    indices = set(map(int,re.findall("\d+",m.group(1))))
    delta = indices ^ previous_indices
    delta_counter += len(delta)
    if delta_counter + len(delta) > 2 * len(indices):
        emit_indices(indices)
        delta_counter = 0
        delta_from = i
    else:
        emit_indices(delta)
    previous_indices = indices

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。