【问题标题】:Shuffle Text File Delphi Source or anything else随机播放文本文件 Delphi 源或其他任何东西
【发布时间】:2010-12-08 16:07:49
【问题描述】:

我有一个包含 10,000 个条目的字符串列表。我有一个随机播放程序,但访问任何项目都需要很多时间。浏览所有 10k 项需要花费大量时间。

我想将它保存到磁盘上,然后使用另一种方法对文件进行随机播放。

有什么建议吗?

【问题讨论】:

    标签: performance delphi text-files tstringlist


    【解决方案1】:

    您的 shuffle-routine 是如何实现的?特别是交换程序?如果您已经编写了自己的代码,请遵循以下原则:

    vTempSrting := vStringList[I]; 
    vStringList.Delete(I); 
    vStringList.Insert(J,vTempString);
    

    它会很慢。在字符串列表上使用交换方法。

    这段代码在我的平均(3 岁)计算机上花费了 78 毫秒:

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    uses
      SysUtils,Classes,uIntegerList,Windows,Math;
    
    procedure Shuffle(aSL : TStringList);
    var I,J : integer;
    begin
      for I := 0 to aSL.Count-1 do
      begin
        J := randomrange(I,aSL.Count);
        aSL.Exchange(I,J);
      end;
    end;
    
    procedure CreateTestFile;
    var
      vSL : TStringList;
      I : integer;
    begin
      vSL := TStringList.Create;
      try
        for I := 1 to 100000 do vSL.Add('Sample text #'+inttostr(I));
        vSL.SaveToFile('c:\test.txt');
      finally
        vSL.Free;
      end;
    end;
    
    function TestShuffle : longword;
    var
      vSL : TStringList;
      vTick0 : longword;
    begin
      vSL := TStringList.Create;
      try
        vTick0 := gettickcount;
        vSL.LoadFromFile('c:\test.txt');
        Shuffle(vSL);
        vSL.SaveToFile('c:\test.txt');
        Result := gettickcount - vTick0;
      finally
        vSL.Free;
      end;
    end;
    
    begin
      CreateTestFile;
      writeln(TestShuffle,' ms');
      readln;
    end.
    

    【讨论】:

    • 哇,谢谢你的代码!我确实使用交换,但我刚刚意识到我的问题 - 字符串从备忘录传递给我的洗牌程序 - 显然每次更新时,备忘录必须更新 10 000 个视觉项目!我现在使用中间 AstrinList,对其进行排序,然后将其分配回备忘录:aStringLIst:= TStringList.Create; aStringList.Assign(mNumbers.Lines);随机播放(aStringList); mNumbers.Lines.Assign(aStringList);一个StringList.Free;这是即时的!非常感谢
    • 哇,cmets 真的把我的代码搞砸了,很抱歉,希望你能成功。
    • 为了避免视觉更新使用 BeginUpdate 和 EndUpdate: mNumbers.Lines.BeginUpdate;随机播放(mNumbers.Lines); mNumbers.Lines.EndUpdate;
    【解决方案2】:

    在内存中重新排列一个字符串列表很慢,所以我会打乱一个索引列表作为初始优化。

    我猜您选择 stringlist 是为了方便从磁盘加载和保存到磁盘。一种更快的方法是对索引进行洗牌。制作一个包含 10,000 个整数的数组,将它们打乱,然后使用临时字符串变量来保存交换元素,并使用打乱后的索引值从上到下重新排列字符串列表。

    主要的重写将提供更大的改进,但如果您的字符串不太大,这可能会有所帮助。

    【讨论】:

    • 我确实使用了 TStringList,但问题是访问其中的任何内容都需要很长时间,所以从上到下扫描需要几分钟。它只是一个数字列表,所以我可以将它加载到一个数组中并随机播放。不过我喜欢 TStringList 的功能,是否有索引的 TStringList 之类的?
    【解决方案3】:

    一种简单的方法是生成一个随机数列表,对其进行排序,然后再对数据进行成对交换。排序可以使用 o(n*log(n)) 算法完成,而交换始终是 o(n) 算法,因此要快得多。

    以防万一您还没有想到,请考虑将数据保持原样,并保存一个额外的随机索引。

    【讨论】:

      【解决方案4】:

      我之前问过一个关于创建混洗范围的问题 - 我想要一个能够迭代地返回混洗数字列表的函数,而不是生成一个数字列表然后对其进行混洗,而无需 O(n) 内存成本:

      Generating shuffled range using a PRNG rather than shuffling

      如果您为磁盘上的文件创建某种索引,那么您可以创建一个 shuffled 版本而无需支付内存成本,这对于非常大的文件可能很重要。对于索引,我建议使用一些简单的方法,例如每行开头的位置(作为 32 位或 64 位整数)的平面流。这样,要从文本文件中提取第 N 行,您可以简单地在索引流中查找 N*4(或 N*8 对于 64 位索引)以发现行开始的偏移量,然后查找文本文件流中的那个位置并读出一行。

      使用这种方法,您可以随机播放非常大的文件,而无需支付内存成本。当然,洗牌意味着从源文件中随机提取行,除非文件非常小(几乎在第一次访问时适合缓存)或非常大(在这种情况下内存抖动),否则它不会像内存排序那样有效会比随机搜索更糟糕),或者如果您没有使用机械硬盘驱动器(例如 SSD)。

      对于您的情况,10K 确实不是一个大数字。大约 1000 万行的内容,可能会变成几 GB 的文本(当然取决于行长),这将更具挑战性,这就是在 32 位中需要这种方法(或类似方法)的地方。

      【讨论】:

      • 谢谢巴里,虽然我发现我的错误是使用随机播放功能对控件进行视觉更新。有趣的方法!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-11
      • 1970-01-01
      • 2019-04-19
      • 2010-12-15
      • 1970-01-01
      • 2016-10-09
      相关资源
      最近更新 更多