【问题标题】:How to shuffle the lines in a file without reading the whole file in advance?如何在不提前读取整个文件的情况下打乱文件中的行?
【发布时间】:2010-07-29 23:34:55
【问题描述】:

有什么好的算法可以在不预先读取整个文件的情况下打乱文件中的行?

我猜它看起来像这样:从头开始逐行读取文件,在每个点存储该行并决定是否要打印到目前为止存储的行之一(然后从存储中删除)或者什么都不做,继续下一行。

有人可以验证/证明这一点和/或发布工作(perl、python 等)代码吗?

相关问题,但不关注内存效率算法:

【问题讨论】:

  • 所有行的大小都一样吗?
  • 不过,这不会产生非常随机的输出 - 它会保证“shuffled”文件的前几行都来自原始文件的前几行。是否要求真的在您阅读整个文件之前开始洗牌?还是您只是想避免一次将整个文件读入内存(因此只要您不一次将整个文件都保存在内存中,扫描文件就可以了)?
  • @torak:不,线条的长度不同。
  • @Dean:嗯,这取决于用于决定打印之前存储的一个或等到稍后再打印的标准。该概率必须使您提到的错误不会发生。是的,关键是避免一次将整个文件保存在内存中。 (如果算法确实必须在某些概率非常低的病理情况下将整个文件读入内存,我会说这是可以接受的。)
  • 在不知道行数的情况下,我认为它实际上是不可行的。如果没有这些知识,您如何确定一条线占据给定位置的概率。

标签: algorithm file random shuffle


【解决方案1】:

如果不以某种方式维护已写入内容的列表,我想不出一种方法来随机执行整个文件。我想如果我必须进行内存高效的洗牌,我会扫描文件,为新行建立一个偏移列表。一旦我有了这个新行偏移列表,我会随机选择其中一个,将其写入标准输出,然后将其从偏移列表中删除。

我不熟悉perl或python,但可以用php演示。

<?php
$offsets = array();

$f = fopen("file.txt", "r");
$offsets[] = ftell($f);
while (! feof($f))
{
  if (fgetc($f) == "\n") $offsets[] = ftell($f);
}

shuffle($offsets);
foreach ($offsets as $offset)
{
  fseek($f, $offset);
  echo fgets($f);
}
fclose($f);
?>

如果扫描文件中的新行是绝对不可接受的,我能想到的唯一其他选择是(我不会编写这个代码):

  1. 确定文件大小
  2. 创建已写入标准输出的偏移量和长度列表
  3. 循环直到 bytes_written == 文件大小
  4. 寻找一个尚未包含在已写入值列表中的随机偏移量
  5. 从该搜索备份到上一个换行符或文件开头
  6. 显示该行,并将其添加到写入的偏移和长度列表中
  7. 转到 3。

【讨论】:

    【解决方案2】:

    以下算法在输入文件的行数中是线性

    预处理:

    1. 通过扫描换行符(或其他)查​​找n(总行数),但存储表示每行开头和结尾的字符编号。因此,您将有 2 个向量,例如 se,大小为 n,其中输入文件中编号从 s[i]e[i] 的字符是第 i 行。在 C++ 中,我会使用 vector

    2. 随机排列从 1 到 n 的整数向量(在 C++ 中为 random_shuffle)并将其存储在向量中,例如 p(例如,1 2 3 4 变为 p = [3 1 4 2])。这意味着 新文件 的第 i 行现在是原始文件中的第 p[i] 行(即在上面的示例中,新文件的第 1 行是原始文件的第 3 行)。

    主要

    1. 创建一个新文件

    2. 通过读取原始文件中s[p[0]]e[p[0]] 之间的文本并将其附加到新文件中,在新文件中写入第一行。

    3. 对所有其他行按照步骤 2 继续。

    因此,如果您假设文件中的读/写和查找(增加文件指针)都是恒定时间操作,那么总体复杂性与行数成线性关系(因为 random_shuffle 是线性的)。

    【讨论】:

      【解决方案3】:

      您可以为 N 个字符串创建一个数组,并将文件的前 N ​​行读入该数组。剩下的你读一行,从数组中随机选择一行,然后用新读取的字符串替换这个字符串。您还可以将数组中的字符串写出到输出文件中。这样做的好处是您不需要对文件进行两次迭代。缺点是它不会创建一个非常随机的输出文件,尤其是当 N 较低时(例如该算法不能在输出中将最后一行移动超过 N 行。)

      编辑

      只是python中的一个例子:

      import sys
      import random
      
      CACHE_SIZE = 23
      
      lines = {}
      
      for l in sys.stdin: # you can replace sys.stdin with xrange(200) to get a test output
          i = random.randint(0, CACHE_SIZE-1)
          old = lines.get(i)
          if old:
              print old,
          lines[i] = l
      
      for ignored, p in lines.iteritems():
          print p,
      

      【讨论】:

        猜你喜欢
        • 2019-07-09
        • 1970-01-01
        • 2013-11-29
        • 2016-08-14
        • 2017-01-19
        • 2011-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多