【问题标题】:Sort big quantity of strings with same lengths对大量相同长度的字符串进行排序
【发布时间】:2011-07-09 18:22:40
【问题描述】:

我有一个非常大的字符串序列。每个字符串的长度为 50。每个字符串仅包含来自英语 ABC 的字符。对该序列进行排序的最佳(最快)方法是什么?

【问题讨论】:

  • 是不是内置的Sort方法不够快?
  • 不,如果序列大小为 500 gb - 这还不够 :) 我听说这种情况有特殊算法
  • Mmh...我认为您指的是Radix-sort,不确定它是否适用于此,但您必须从头开始实现它...
  • 哦,对不起,我的意思是 500 mb :) 我只有 [ABC]。
  • 我做了一个简单的基准测试。我在 RAM 中创建了 2.5 亿 / 50 个 C# 随机字符串(所以是 OP 目标的一半)。它占用了 670mb 的内存(C# char 是 2 个字节,加上每个字符串的开销加上列表的开销)。在我的慢速笔记本电脑上花了 12 秒。在内存中对它们进行排序(序数排序)又花了 12 秒。排序不是问题:-)

标签: c# algorithm sorting


【解决方案1】:

如果我必须编写代码,我可能会根据前几个字符左右将输入拆分为多个输出文件;目标是使每个输出文件足够小以适合主内存。然后我会按顺序打开每个文件,在内存中对其进行排序,并将其附加到输出中。第一遍是 O(n),第二遍是或多或少 O(n log n),每条记录必须执行四次磁盘 I/O。使用一些神秘的算法可能会做得更好,但可能不会很多,而且这很容易理解和编码。

如果系统限制您一次可以打开多少个文件,您可能必须拆分第一遍。如果字符串分布不均,一些中间文件可能会太大。

在伪代码中:

open input file (r)
for i in ['aa', 'ab', 'ac', ..., 'zz']:
    open output file[i] (w)
for record in input file:
    write record to output file[record[0:2]]
close all files
open main output file (w)
for i in ['aa', 'ab', 'ac', ..., 'zz']:
    open input file[i] (r)
    slurp whole file into memory
    close input file
    sort data
    append whole sorted file to main output file

编辑:等等,你的意思是记录只包含字符 A、B 和 C?没有其他字母?在这种情况下,您可能必须拆分大于 2 的初始子字符串。拆分前 3 个字符会将其分成 27 个文件,每个文件平均大小为 370 MB。

【讨论】:

  • 注意:以上解决方案是针对 500-GB 的问题。 500 MB 很容易,任何现代机器都需要一次 RAM。
【解决方案2】:

这样的?

List<string> list = new List<string>();
/* fill the list */
list.Sort();

Sort() 方法具有不同的重载,允许您自定义执行字符串比较的方式。

编辑哦,“大”是指价值 500GB 的字符串,那么这可能不会减少它。

【讨论】:

    【解决方案3】:

    您正在寻找的算法可能是合并排序

    http://en.wikipedia.org/wiki/Merge_sort

    还有这个

    http://en.wikipedia.org/wiki/External_sorting

    但在您的具体情况下,请阅读以下内容:

    Need a way to sort a 100 GB log file by date

    它可以为你工作!

    【讨论】:

    • 我已经用过外部排序了,但是我的排序很慢(我用的是Array.Sort)。
    • @miksayer “非常慢”是什么意思?读取和写入 500 GB 数据本来就很慢,你知道吗?对于顺序读取/写入,基于盘片的磁盘的读取或写入速度约为 100mb/秒(我正在使用此数据maximumpc.com/article/reviews/western_digital_caviar_black_2tb)。对于合并排序,您需要 2 次读取和 2 次写入,因此读取和写入之间需要 2 tb。嗯.. 350 分钟?我希望你没有使用 USB 驱动器来存储数据:-)
    • @miksayer 作为附录,如果您有两个磁盘,并且两个磁盘之一仅用于临时文件,则可以加快外部合并排序(几乎将其速度提高一倍)。从技术上讲,到 350 分钟,您需要为排序添加内存时间,但我认为排序 500mb 的字符串不会很长,如果您正确执行(使用 StringComparison.Ordinal 或 StringComparison.OrdinalIgnoreCase)
    【解决方案4】:

    由于 500 MB 不是很多数据,您可以简单地将整个文件加载到内存中,对其进行排序,然后将结果写回磁盘。

    我假设文件内容的布局是这样的:

    ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\r\n ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\r\n : : ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWX\r\n

    代码:

    // Load
    var data = File.ReadAllBytes("file.txt");
    var itemCount = data.Length / 52;
    var indexes = new int[itemCount];
    for (int i = 0; i < itemCount; i++)
    {
        indexes[i] = i;
    }
    
    // Sort
    Array.Sort<int>(indexes, (x, y) =>
    {
        for (int i = 0; i < 50; i++)
        {
            if (data[x * 52 + i] > data[y * 52 + i]) return 1;
            if (data[x * 52 + i] < data[y * 52 + i]) return -1;
        }
        return 0;
    });
    
    // Store
    using (var stream = new Stream("result.txt"))
    {
        for (int i = 0; i < itemCount; i++)
        {
            stream.Write(data, indexes[i] * 52, 52);
        }
    }
    

    【讨论】:

    • 有趣的是,虽然这可能是对(the/this)“家庭作业”问题的正确(也是最快)“响应”,但它与“现实世界”的响应相去甚远,至少,正如我所说,很有趣。问题抽象性的一个完美例子,可能:-)
    【解决方案5】:

    快速排序(如果使用正确)可以非常有效地对字符串进行排序。

    诀窍是修改分区方法。主要思想是在每个分区步骤中,特定分区中的键具有相同的前缀。再次分区时,不需要比较键的前缀。

    示例: 假设输入是{"hello", "world", "house", "homly" },第一个分区围绕关键“世界”

    你会得到:{"hello", "house", "homly"}, {"world"}

    当你想重新分区第一组时,你不必比较字符串的第一个字符,因为你已经知道第一个字符在所有字符串中都是相同的!

    一般来说,一个集合中公共前缀的长度是为到达该集合而进行分区的次数。

    如果您有兴趣深入了解,请阅读Fast Algorithms for sorting and searching strings

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-26
      • 1970-01-01
      • 1970-01-01
      • 2016-04-23
      • 2017-06-21
      • 1970-01-01
      • 2018-02-23
      相关资源
      最近更新 更多