0

レコードタイプと、そのレコードタイプで構成される動的配列があります。私はそれをマージソートルーチンに渡し、ブール値であるが効果がないように見えるフィールドプロパティの1つを設定しようとします。

他の方法でレコードの配列を並べ替えることを検討しました(customrecord配列についてはこのクイックソートを参照してください:http://en.wikibooks.org/wiki/Algorithm_Implementation/Sorting/Quicksort#Delphi)またはここ:配列を並べ替える最良の方法(主にコマーリング関数を作成するために、これらの提案のいずれもここから機能しないようにします)。この質問:配列をアルファベット順に並べ替えますか?役に立ち、機能しましたが、この並べ替えは非常に遅いです。

コード:

type    
       TCustomRecord = Record
        fLine     : AnsiString; //full line
        fsubLine     : AnsiString; // part of full line
        isDuplicate : boolean;  //is that subline duplicate in another line
        isRefrence     : boolean; // is this line from a refrence file or the one being deduped
        fIndex    : Cardinal; // original order line was loaded
       end;
      TCustomRecordArray = array of TCustomRecord; 

function Merge2(var Vals: array of TCustomRecord ):Integer;
var
  AVals: array of TCustomRecord;

   //returns index of the last valid element
  function Merge(I0, I1, J0, J1: Integer):Integer;
  var
    i, j, k, LC:Integer;
  begin
    LC := I1 - I0;
    for i := 0 to LC do
      AVals[i]:=Vals[i + I0];
      //copy lower half or Vals into temporary array AVals

    k := I0;
    i := 0;
    j := J0;
    while ((i <= LC) and (j <= J1)) do
    if (AVals[i].fsubLine < Vals[j].fsubLine) then
    begin
      Vals[k] := AVals[i];
      if Vals[k].isRefrence = False then
        Vals[k].isDuplicate := False;
      inc(i);
      inc(k);
    end
    else if (AVals[i].fsubLine > Vals[j].fsubLine) then
    begin
      Vals[k]:=Vals[j];
      if Vals[k].isRefrence = False then
        Vals[k].isDuplicate := False;
      inc(k);
      inc(j);
    end else
    begin //duplicate
      Vals[k] := AVals[i];
      if Vals[k].isRefrence = False then
        Vals[k].isDuplicate := True;
      inc(i);
      inc(j);
      inc(k);
    end;

    //copy the rest
    while i <= LC do begin
      Vals[k] := AVals[i];
      inc(i);
      inc(k);
    end;

    if k <> j then
      while j <= J1 do begin
        Vals[k]:=Vals[j];
        inc(k);
        inc(j);
      end;

    Result := k - 1;
  end;

 //returns index of the last valid element

  function PerformMergeSort(ALo, AHi:Integer): Integer; //returns
  var
    AMid, I1, J1:Integer;
  begin

  //It would be wise to use Insertion Sort when (AHi - ALo) is small (about 32-100)
    if (ALo < AHi) then
    begin
      AMid:=(ALo + AHi) shr 1;
      I1 := PerformMergeSort(ALo, AMid);
      J1 := PerformMergeSort(AMid + 1, AHi);
      Result := Merge(ALo, I1, AMid + 1, J1);
    end else
      Result := ALo;
  end;

begin
  //SetLength(AVals, Length(Vals) + 1 div 2);
  SetLength(AVals, Length(Vals) div 2 + 1);
  Result := 1 + PerformMergeSort(0, High(Vals));
end;

質問:できればmergesortを使用して、このレコードの配列を効率的に並べ替え、その並べ替えに従ってプロパティの一部を設定するにはどうすればよいですか?ありがとうございました。

更新:ポインター型を追加し、ポインターの配列に対して変更されたマージソートを実行しました。これは、レコードの配列をソートする非常に高速な方法であることが判明しました。必要なフラグを追加する比較ルーチンも追加しました。私ができない唯一の部分は、それらがファイルAまたは参照ファイルに属しているかどうかに基づいて重複のフラグを追加することです。

コード:

    type    
          PCustomRecord = ^TCustomRecord; 
          TCustomRecord = Record
            fLine     : AnsiString; //full line
            fsubLine  : AnsiString; // part of full line
            isDuplicate : boolean;  //is that subline duplicate in another line
            isRefrence     : boolean; // line from a refrence file or the one being deduped
            isUnique  : boolean; //flag to set if not refrence and not dupe
            fIndex    : Cardinal; // original order line was loaded
           end;
          TCustomRecordArray = array of TCustomRecord;
          PCustomRecordList = ^TCustomRecordArray;

//set up actual array
//set up pointer array to point at actual array
//sort by mergesort first
// then call compare function - this can be a procedure obviously

function Compare(var PRecords: array of PCustomRecord; iLength: int64): Integer;
var
  i : Integer;
begin
  for i := 0 to High(PRecords) do
  begin
    Result := AnsiCompareStr(PRecords[i]^.fsubline, PRecords[i+1]^.fsubline);
    if Result=0 then
    begin
      if (PRecords[i].isrefrence = False) then
        PRecords[i].isduplicate := True
      else if (PRecords[i+1].isrefrence = False) then
        PRecords[i+1].isduplicate := True;
    end;
  end;
end; 

procedure MergeSort(var Vals:array of PCustomRecord;ACount:Integer);
var AVals:array of PCustomRecord;

  procedure Merge(ALo,AMid,AHi:Integer);
  var i,j,k,m:Integer;
  begin
    i:=0;
    for j:=ALo to AMid do
    begin
      AVals[i]:=Vals[j];
      inc(i);
      //copy lower half or Vals into temporary array AVals
    end;

    i:=0;j:=AMid + 1;k:=ALo;//j could be undefined after the for loop!
    while ((k < j) and (j <= AHi)) do
    if (AVals[i].fsubline) <= (Vals[j].fsubline) then
    begin
      Vals[k]:=AVals[i];
      inc(i);inc(k);
    end
    else if (AVals[i].fsubline) > (Vals[j].fsubline) then
    begin
      Vals[k]:=Vals[j];
      inc(k);inc(j);
    end;

    {locate next greatest value in Vals or AVals and copy it to the
     right position.}

    for m:=k to j - 1 do
    begin
      Vals[m]:=AVals[i];
      inc(i);
    end;
    //copy back any remaining, unsorted, elements
  end;

  procedure PerformMergeSort(ALo,AHi:Integer);
  var AMid:Integer;
  begin
    if (ALo < AHi) then
    begin
      AMid:=(ALo + AHi) shr 1;
      PerformMergeSort(ALo,AMid);
      PerformMergeSort(AMid + 1,AHi);
      Merge(ALo,AMid,AHi);
    end;
  end;

begin
  SetLength(AVals, ACount div 2 + 1);
  PerformMergeSort(0,ACount - 1);
end;

これはすべて、1秒未満かかる小さなファイルでは非常に高速です。ただし、参照フラグではなく重複フラグを持つ配列内のアイテムの重複排除は非常に困難です。マージソートは安定したソートであるため、ブールフラグを使用して再試行しましたが、期待したものが得られませんでした。TStringlist以前のフラグが正しく設定されていて、完全に機能するかどうかを確認するためにを使用しました。時間は1秒から6秒に増加しました。なしisUniqueでフラグをマークする簡単な方法が必要であることを私は知っています。 TStringlist

これが私が試したことです:

function DeDupe(var PRecords: array of PCustomRecord; iLength: int64): Integer;
var
  i : Integer;
begin
  for i := 0 to High(PRecords) do
  begin
    if (PRecords[i]^.isrefrence = False) and (PRecords[i+1]^.isrefrence = false)then
    begin
      Result := AnsiCompareStr(PRecords[i]^.isduplicate, PRecords[i+1]^.isduplicate);
      if Result = 0 then PRecords[i]^.isUnique := True;
    end
    else
    begin
      Continue;
    end;
  end;
end;

これはすべての値を取得するわけではなく、まだ多くの重複が見られるため、違いは見られませんでした。論理が間違っていると思います。

助けてくれたすべての偉大な魂に感謝します。配列に焦点を当てるために、を導出するTObject方法と使用する方法をすでに知っているかもしれないという利点を私に許可してください。TStringList

質問:上記のような関数または手順を実行して、繰り返されるアイテムに次のマークを付けるのを手伝ってください: isRefrence=falseおよびisDuplicate=True and unique

編集3:ブールフラグを使用することで重複を排除することができました。これは、アレイのサイズを変更せずにアレイを安定させるのに役立ちました。TList子孫やを使用するよりもはるかに高速だと思いますTStringList。配列などの基本的なコンテナの使用には、コーディングのしやすさに制限がありますが、非常に効率的であるため、私はそれを渡しません。ポインタで並べ替えが簡単になりました。通常の配列を使用しているのとまったく同じようにポインター配列を使用したときに、配列にポインターを設定した後、どのようにすればよいかわかりません。そして、私がそれをデレファレンスしたかどうかにかかわらず、それは違いがありませんでした。ポインタ配列を次のように設定します。

  iLength := Length(Custom_array); //get length of actual array
  SetLength(pcustomRecords, iLength); // make pointer array equal + 1

  for M := Low(Custom_array) to High(Custom_array) do //set up pointers
  begin
    pcustomRecords[M] := @Custom_array[M]; 
  end;

並べ替える実際のデータと並べ替えをできるだけ分けてみましたが、改善できると思います。

///////////////////////////////////////////////////////////////////
function Comparesubstring(Item1, Item2: PCustomRecord): Integer;
begin
  Result := AnsiCompareStr(item1^.fsubline, item2^.fsubline);
end;
///////////////////////////////////////////////////////////////////
function CompareLine(Item1, Item2: PCustomRecord): Integer;
begin
  Result := AnsiCompareStr(item1^.fLine, item2^.fLine);
end;
///////////////////////////////////////////////////////////////////
function Compare(var PRecords: array of PCustomRecord; iLength: int64): Integer;
var
  M, i : Integer;
begin
  M := Length(PRecords);
  for i := 1 to M-1 do
  begin
    Result := Comparesubstring(PRecords[i-1], PRecords[i]);
    if Result=0 then
    begin
      if (PRecords[i-1].isRefrence = False) then
        PRecords[i-1].isduplicate := True
      else if (PRecords[i].isRefrence = False) then
        PRecords[i].isduplicate := True;
    end;
  end;
end;
///////////////////////////////////////////////////////////////////
4

3 に答える 3

4

1)データをコピーしないでください!ポインタを操作します。代わりに、それらのデータレコードへのポインタのリスト/配列を作成し、ポインタを並べ替える必要があります。並べ替えが完了したら、ポインタ配列に基づいてデータの新しい配列を作成するだけです。ポインタの移動は単一のCPUコマンドです。SizeOf(レコード)は>> SizeOf(ポインター)であり、移動すると非常に遅くなります。

2)マージソートは、メモリに収まらない膨大なデータ量に影響を与えます。10ギガバイトのデータがある場合、Win32プログラムで許可されている2GBのメモリでそれらを並べ替えることはできません。したがって、ディスク上にある間にそれらをソートする必要があります。それがマージソートのニッチです。すべてのデータがメモリ内にある場合は、代わりに準備ができたQuickSortルーチンを使用してみませんか?

したがって、TListを作成し、それをtype PCustomRecord = ^TCustomRecord;ポインターで埋め、適切な比較関数を実装し、TList.Sortメソッドによってチェックされたクイックソートを呼び出します。

http://docwiki.embarcadero.com/CodeExamples/XE2/en/TListSort_(Delphi)

リストがソートされた後-データの新しい配列を作成して入力します。その新しいアレイが作成されたら、リストを解放し、古いソースアレイを削除します。


可能であれば、データがメモリに収まるかどうかを確認してください。メモリが十分でない場合にのみ、ディスク上の検索に常駐します。それはもっと遅く、ずっと遅くなりました。


私は学校でそれをしました...マージソートは再帰的ではありません。非常に基本的なループです。その単純さのために私はそれを実装しました。比較するために、私はまだQuickSortの腸の機能を持っていません。

擬似コードでは、次のようになります

FrameSize := 1;
Loop start:
  Phase 1: splitting
     Loop until not empty TempMergedDataFile:
        Read record by record from TempMergedDataFile 
            and write each of them into TempSplitDataFile-1
            up to FrameSize times
        Read record by record from TempMergedDataFile 
            and write each of them into TempSplitDataFile-2
            up to FrameSize times
     Loop end
     Delete TempMergedDataFile 
  Phase 2: sorting-merging
     Loop until not empty TempSplitDataFile-1 and TempSplitDataFile-2:
        Read record by record from both TempSplitDataFile-1 and TempSplitDataFile-2
          up to FrameSize each (2xFrameSize in total in each iteration)
          write them sorted into TempMergedDataFile
     end loop
     delete TempSplitDataFile-1 and TempSplitDataFile-2
  Phase 3: update expectations
     FrameSize := FrameSize * 2
     if FrameSize > actual number of records - then exit loop, sort complete
End loop

フェーズ2の実装には注意してください。いずれかのファイルがフレームを超えた場合は、実際の値またはnilと比較します。まあ、アイデアは明白で、おそらくどこかでデモされています。この部分では衒学者になってください。おそらくFSMの実装は簡単かもしれません。

明らかな最適化:

  1. すべてのファイルを異なる物理専用HDDに配置して、各HDDが線形読み取り/書き込みモードになるようにします
  2. フェーズ1とフェーズ2をマージします。TempMergedDataFileを仮想化します。実際にはTempSplitDataFile-3とTempSplitDataFile-4で構成されます。書き込み中にデータを次のサイズのフレームに分割します。
  3. SSDまたはフラッシュカードがストレージに使用されている場合、データのコピーはハードウェアを使い果たします。実際の並べ替えには、ある種の「ポインタ」または「インデックス」を並べ替える方がよいでしょう。また、完全なデータフレームがRAMを超える場合でも、単なる「インデックスの配列」が収まる可能性はわずかです。ただし、テストを行わない実際のHDDでは、単純な「コピーしてコピーしてもう一度コピーする」アプローチを使用することをお勧めします。
于 2012-10-11T13:01:46.987 に答える
3

最初のコメントは、基本的なデザインが非常に弱いということです。並べ替えコードと比較/交換コードをすべて一緒に混合しました。別のデータを並べ替える必要がある場合は、最初からやり直す必要があります。データを理解するコードからソートコードを切り離す必要があります。

そのデカップリングを実現する方法は、データについて何も知らない汎用ソートルーチンを実装することです。代わりに、知っておく必要があるのは、2つの要素を比較する方法と、2つの要素を交換する方法だけです。一般的なメモリ内の並べ替えルーチンはすべて、この方法で効率的に実装できます。

あなたが抱えているもう1つの問題は、コードがデータのコピーに多くの時間を費やすことだと思います。それを行う代わりに、間接参照のレイヤーを使用します。つまり、元の配列を変更しようとしないでください。代わりに、インデックスの配列をデータ配列に作成し、データの配列ではなくインデックスの配列を並べ替えます。

そのことを理解するために、次のように表示されます。

var
  Data: array of TData;
  Indices: array of Integer;

function CompareIndices(Index1, Index2: Integer): Integer;
begin
  Result := CompareData(Data[Indices[Index1]], Data[Indices[Index2]]);
end;

procedure SwapIndices(Index1, Index2: Integer);
var
  Temp: Integer;
begin
  Temp := Indices[Index1];
  Indices[Index1] := Indices[Index2];
  Indices[Index2] := Temp;
end;

次に、配列を並べ替えるには、次のようにします。

N := Length(Data);
SetLength(Indices, N);
for i := 0 to high(Indices) do 
  Indices[i] := i;
Sort(CompareIndices, SwapIndices, N);

または、さらに別の方法として、インデックスの配列の代わりに、データ配列の要素へのポインタの配列を使用します。

ここでは、説明をわかりやすくするために、グローバル変数を使用しました。実際には、これをクラスにまとめるか、少なくともコンペアアンドスワップ関数をオブジェクトのメソッドにする必要があります。これが、Delphi6コードベースで行った方法です。インターフェイスは次のようになりました。

type
  TCompareIndicesFunction = function(Index1, Index2: Integer): Integer of object;
  TExchangeIndicesProcedure = procedure(Index1, Index2: Integer) of object;

procedure QuickSort(Compare: TCompareIndicesFunction; 
  Exchange: TExchangeIndicesProcedure; const Count: Integer);

ソートアルゴリズムをデータから分離するという概念を理解したら、ある程度の進歩が見られます。その後、ある並べ替えアルゴリズムを別の並べ替えアルゴリズムに交換するのは簡単になります。それらを簡単に比較できます。間接アプローチが価値があるかどうかを簡単に測定できます。等々。

ですから、私の絶対的なアドバイスは、質問のコードを破棄し、自然の意図どおりに並べ替えとデータ処理を分離することです。

于 2012-10-11T13:26:15.297 に答える
0

私が知っている質問に直接答えることはありませんが、データがメモリに収まる場合は、配列を使用しているように見えます。

私はそれらすべてをダンプし、いくつかのオブジェクトを作成し、それらをTObjectListに入れます。TObjectList.Sort(@myComparefunction)を使用して、独自の比較を使用して並べ替えます。複数のソートルーチンを宣言することができます。並べ替え機能では、他のオブジェクトのプロパティを自由に設定できます。それはかなり速く、あなたが苦しんでいるように見える多くの痛みを救うでしょう:)

于 2012-10-11T17:27:08.570 に答える