如何交叉两组不适合内存的long?

时间:2012-10-20 18:21:34

标签: java algorithm memory set intersection

我有一个有趣的问题。我想在Java中交叉两组Longs,每组有1B成员 - 每组4GB。这不适合我需要在服务器上运行的内存。

我想知道有什么有趣的方法来解决这个问题。

到目前为止,我想到的是从磁盘读取每个原始集的子集,这些子集足够小以适合内存,然后交叉每个子集,并临时写入磁盘。最后,我可以通过并交叉这些子集。我觉得这可能会变成地图缩减工作。

也许你会有更好的想法:)我怀疑我是第一个提出这个问题的人。

9 个答案:

答案 0 :(得分:8)

  1. 分别对AB两个集进行排序。

  2. 从集合A中取出并删除第一个元素,从集合B中删除第一个元素

  3. 如果它们相等,请添加到结果集。

  4. 如果一组中的项目较大,请从第二组中取下一项。

  5. 只要您没有达到任何一组的结尾,请转到2.

  6. 这种方法的优点是你永远不会在内存中保留2个以上的长度(排序除外)。可以在磁盘上有效地进行排序(合并排序)。

答案 1 :(得分:2)

对两个集合执行基于磁盘的合并排序。

完成后,您只需按顺序浏览已排序的文件,并将交叉点记录在新文件中。

答案 2 :(得分:2)

这是我认为可以做到的。显然是放在磁盘上。你必须对它们进行排序。

  1. 使用external sorting
  2. 对其进行排序
  3. 比较,

    if a. length < 1 or b.length < 1
      exit
    else if a[0] == b[0]
      addToIntersectionSet(a[0])
      remove a[0] from a
      remove b[0] from b
    else if a[0] < b[0]
       remove a[0]
    else 
      remove b[0]
    

答案 3 :(得分:2)

在内存中初始化2个大位映射到零(位图1和位图2)(如果可用)或磁盘。 对于set1中的每个值,将value-th bitmap1位置设置为1。 对于set2中的每个值,读取bitmap1位中的第th个位置,如果为1,则将第bit位置的bitmap2设置为1。 对于bitmap2中的每个设置位值,该位置的输出值。

编辑:Jessop在回复中指出了这个缺陷:它是Java 64位(8字节)长整数,而不是32位架构C编译器4字节长整数。 64位长的解决方案是不切实际的。

答案 4 :(得分:2)

然后排序的一种可能更有效的方法是使用散列,并将数据拆分成几个箱 - 并在每个箱上进行交叉。 想法是将问题分解为适合内存的子问题 - 然后你可以在RAM上有效地进行交集。

假设你想要找到R,S的交集:

for each element in R:
   write element in bucket_R[hash(element) % NUM_BUCKETS]
for each element in S:
   write element in bucket_S[hash(element) % NUM_BUCKETS]

//assuming each bucket from bucket_S or bucket_R now fits memory - proceed.
//if it doesn't, you can repeat the previous step with a new hash function.
for each i from 0 to NUM_BUCKETS:
  find bucket_S INTERSECTION bucket_R

重要:
bucket_S,bucket_R或DISK而不是RAM。

磁盘访问次数:

使用此方法的磁盘读取总数为3 * (|R|+|S|)

  1. 在迭代前两个循环时读取R和S中的每个元素
  2. 将每个元素写入哈希表
  3. 阅读所有水桶
  4. 虽然任何基于排序的算法最有可能需要多于1次读取+ 1次写入(以及对数据的额外遍历) - 这将产生更多,然后3 * (|R|+|S|)


    P.S。我正在学习文件系统考试(将在星期一进行),讲座说明这是大多数数据库系统中首选的解决方案,假设你有一个磁盘。

答案 5 :(得分:1)

这确实感觉就像地图减少作业,但您必须非常小心您选择的子集。如果希望交叉点起作用,则必须在相同点处剪切原始集合中的子集。例如,假设您有集合

A = {1 3 7 9}
B = {2 7 8 9}

你将它们分成两个子集:

A1 = {1 3} A2 = {7 9}
B1 = {2 7} B2 = {8 9}

然后你相交:

C1 = A1 inter B1 = {}
C2 = A2 inter B2 = {9}

然后你想:

C = A inter B = C1 union C2 = {9}

这显然是错误的。为了使map reduce工作,你必须使用一些常量值来剪切集合,例如A1和B1将包含值&lt; 5和A2和B2值&gt; = 5.

您还可以从磁盘中获取集合A和B的常规部分,然后以智能方式将它们相交,这意味着逐步查看已排序的元素,并在到达两个子集之一时停止。那时,你获取了一个额外的子集部分。

答案 6 :(得分:1)

一个显而易见的事情是从磁盘上的排序集开始。然后,您可以同时从两个文件中读取,找到匹配并将其写出。假设您从一个文件中读取204,您将从另一个文件读取,直到第一个数字> = 204.此时您知道此特定数字是否属于交叉点。

答案 7 :(得分:1)

你的第一步可能是对每一组进行排序;排序较小的块&amp;合并排序以构建排序文件。

排序后,您可以浏览两组。

答案 8 :(得分:1)

您可以轻松地打开512个文件,因此您可以将两个集合预先浏览到磁盘上的256个块,最多16M项目,每个大小为64 MB。您可以根据每个Long的最重要字节(从set.A.00到set.B.ff

)执行此操作

然后你可以加载每对相应的块(set.A.42包含Longs,从0x42开始对应set.B.42)并使用它们初始化一个16M Byte数组 - 你将它初始化为全0,当你从第一个块读取值i,你增加索引i-th)。然后你读入第二个块,但这次增加2。

完成后,您将对阵列进行扫描; 0表示该值不存在于任一块中,1表示它仅存在于第一组中,2表示仅存在于第二组中,3表示两者中。并将结果写入结果文件。

您不需要排序,即使结果文件将被排序(因为您将按顺序检查块,并按顺序执行最终扫描)。

这一切都在O(n)中运行(所有步骤都以线性时间运行)并且最多需要16M的RAM。

如果512个打开的文件太多,您可以使用前7个最高位,并使用256个打开文件和32M RAM。或者128个打开文件和64M RAM,依此类推。

也可以(并且可能更高效,如果文件系统缓存不太好)保留一系列256个“桶”,每个大小为16384 Longs(因此它再次为16M)。当存储桶接近满时,您在磁盘上打开相应的块文件,转储到目前为止找到的16384 Long,然后关闭该文件。然后为集合B执行相同操作。最终会有512个文件,其中包含0(不太可能)到1600万Long s,从未一次打开两个以上的文件。