CSV ファイルから生成された2 つの DataTables と がA
あります。に存在しないB
行を確認できるようにする必要があります。B
A
さまざまな行を表示するためにある種のクエリを実行する方法はありますか、または各 DataTable の各行を反復処理して同じかどうかを確認する必要がありますか? 後者のオプションは、テーブルが大きくなると非常に集中的になるようです。
適切なタイプの ID 列があると仮定します (つまり、ハッシュコードを提供し、等価性を実装します)。この例では文字列です。これは、DataTable に詳しくなく、すべてを調べる時間がないため、少し疑似コードになっています。ちょうど今:)
IEnumerable<string> idsInA = tableA.AsEnumerable().Select(row => (string)row["ID"]);
IEnumerable<string> idsInB = tableB.AsEnumerable().Select(row => (string)row["ID"]);
IEnumerable<string> bNotA = idsInB.Except(idsInA);
同じかどうかを確認するために、各 DataTable の各行を反復処理する必要がありますか。
CSV ファイルからデータをロードしたので、インデックスなどは何もないので、ある時点で、コードであろうとライブラリであろうと、すべての行を反復処理する必要があります。 、または何でも。
とにかく、これは私の専門ではないアルゴリズムの質問ですが、私の素朴なアプローチは次のようになります。
1: データの特性を利用できますか? 各テーブルのすべての行は一意ですか? 同じ基準で両方を並べ替えることができますか? もしそうなら、あなたはこれを行うことができます:
これにより、(ソート時間 * 2) + 1 回のパスで実行できるため、私の Big-O 表記が正しければ、(whatever-sort-time) + O(m+n) となります。 .
(改訂: これは、ΤΖΩΤΖΙΟΥ が説明するアプローチです)
2: データの大きさに応じて、多かれ少なかれ効率的な別のアプローチ:
私よりもアルゴリズムの知識が豊富な人がこれについて何を考え出すのか、本当に興味があります:-)
これを行うには、DataTable で Merge メソッドと GetChanges メソッドを使用できます。
A.Merge(B); // this will add to A any records that are in B but not A
return A.GetChanges(); // returns records originally only in B
これまでの回答は、重複した主キーを探しているだけだと仮定しています。これは非常に簡単な問題です。たとえば、Merge() メソッドを使用できます。
しかし、あなたの質問は、重複する DataRows を探しているという意味だと理解しています。(問題の説明から、両方のテーブルがCSVファイルからインポートされているため、元の行には主キー値がなく、インポート中にオートナンバーを介して主キーが割り当てられているとさえ思います。)
単純な実装 (A の各行について、その ItemArray を B の各行の ItemArray と比較する) は、実際に計算コストが高くなります。
これを行うためのはるかに安価な方法は、ハッシュ アルゴリズムを使用することです。DataRow ごとに、その列の文字列値を 1 つの文字列に連結し、その文字列に対して GetHashCode() を呼び出して int 値を取得します。Dictionary<int, DataRow>
DataTable B の各 DataRow について、ハッシュ コードをキーとするエントリを含む を作成します。次に、DataTable A の各 DataRow について、ハッシュ コードを計算し、それがディクショナリに含まれているかどうかを確認します。そうでない場合は、DataRow が DataTable B に存在しないことがわかります。
このアプローチには 2 つの弱点があり、どちらも 2 つの文字列が等しくなくても同じハッシュ コードを生成するという事実から生じます。ハッシュがディクショナリにある A の行が見つかった場合は、ディクショナリの DataRow をチェックして、2 つの行が本当に等しいことを確認する必要があります。
2 番目の弱点はより深刻です。B の 2 つの異なる DataRows が同じキー値にハッシュされる可能性は低いですが、可能です。このため、ディクショナリは実際には である必要がありDictionary<int, List<DataRow>>
、前の段落で説明したチェックをリスト内の各 DataRow に対して実行する必要があります。
これを機能させるにはかなりの作業が必要ですが、これは O(m+n) アルゴリズムであり、これは可能な限り優れたものになると思います。
これを解決する簡単な方法を見つけました。以前の「except メソッド」の回答とは異なり、except メソッドを 2 回使用します。これにより、どの行が削除されたかだけでなく、どの行が追加されたかがわかります。except メソッドを 1 つだけ使用すると、両方ではなく 1 つの違いだけがわかります。このコードはテスト済みで動作します。下記参照
//Pass in your two datatables into your method
//build the queries based on id.
var qry1 = datatable1.AsEnumerable().Select(a => new { ID = a["ID"].ToString() });
var qry2 = datatable2.AsEnumerable().Select(b => new { ID = b["ID"].ToString() });
//detect row deletes - a row is in datatable1 except missing from datatable2
var exceptAB = qry1.Except(qry2);
//detect row inserts - a row is in datatable2 except missing from datatable1
var exceptAB2 = qry2.Except(qry1);
次に、結果に対してコードを実行します
if (exceptAB.Any())
{
foreach (var id in exceptAB)
{
//execute code here
}
}
if (exceptAB2.Any())
{
foreach (var id in exceptAB2)
{
//execute code here
}
}
すべてのフィードバックに感謝します。
残念ながらインデックスはありません。私の状況についてもう少し情報を提供します。
EU 内の 7 つのサーバーにインストールされているレポート プログラム (Crystal レポートに代わるもの) があります。これらのサーバーには多くのレポートがあります (国ごとにすべて同じというわけではありません)。これらは、構成に XML ファイルを使用するコマンドライン アプリケーションによって呼び出されます。したがって、1 つの XML ファイルで複数のレポートを呼び出すことができます。
コマンドライン アプリケーションは、夜通しのプロセスによってスケジュールされ、制御されます。したがって、XML ファイルは複数の場所から呼び出すことができます。
CSV の目的は、使用されているすべてのレポートとそれらが呼び出されている場所のリストを作成することです。
すべての参照について XML ファイルを調べ、スケジューリング プログラムにクエリを実行して、すべてのレポートのリストを作成しています。(これはそれほど悪くはありません)。
私が抱えている問題は、本番環境から削除された可能性のあるすべてのレポートのリストを保持しなければならないことです。そのため、古い CSV と新しいデータを比較する必要があります。このためには、それを DataTables に入れて情報を比較するのが最善だと考えました (これは間違ったアプローチである可能性があります。それを保持するオブジェクトを作成し、違いを比較して、それらを反復処理することができると思います)。
各レポートについて私が持っているデータは次のとおりです。
String - タスク名 String - アクション名 Int - ActionID (1 つのアクションで多数のレポート、つまり XML ファイルを呼び出すことができるため、Action ID は複数のレコードに含めることができます)。文字列 - 呼び出された XML ファイル 文字列 - レポート名
MusiGenesis から提供された Merge のアイデアを試してみます (ありがとうございます)。(マージが機能するかどうかわからないいくつかの投稿を読み直しますが、これまで聞いたことがないので、新しいことを学ぶ価値があります)。
HashCode のアイデアも面白そうです。
すべてのアドバイスをありがとう。
参考までに:
アルゴリズムについて一般的に言えば、2 つのソート可能なセット (通常は id のように) を比較することは O(M*N/2) 操作ではなく、2 つのセットが順序付けられている場合は O(M+N) です。したがって、一方のテーブルをもう一方の開始点へのポインターでスキャンすると、次のようになります。
other_item= A.first()
only_in_B= empty_list()
for item in B:
while other_item > item:
other_item= A.next()
if A.eof():
only_in_B.add( all the remaining B items)
return only_in_B
if item < other_item:
empty_list.append(item)
return only_in_B
上記のコードは明らかに疑似コードですが、自分でコーディングすることにした場合は、一般的な要点がわかるはずです。
public DataTable compareDataTables(DataTable First, DataTable Second)
{
First.TableName = "FirstTable";
Second.TableName = "SecondTable";
//Create Empty Table
DataTable table = new DataTable("Difference");
DataTable table1 = new DataTable();
try
{
//Must use a Dataset to make use of a DataRelation object
using (DataSet ds4 = new DataSet())
{
//Add tables
ds4.Tables.AddRange(new DataTable[] { First.Copy(), Second.Copy() });
//Get Columns for DataRelation
DataColumn[] firstcolumns = new DataColumn[ds4.Tables[0].Columns.Count];
for (int i = 0; i < firstcolumns.Length; i++)
{
firstcolumns[i] = ds4.Tables[0].Columns[i];
}
DataColumn[] secondcolumns = new DataColumn[ds4.Tables[1].Columns.Count];
for (int i = 0; i < secondcolumns.Length; i++)
{
secondcolumns[i] = ds4.Tables[1].Columns[i];
}
//Create DataRelation
DataRelation r = new DataRelation(string.Empty, firstcolumns, secondcolumns, false);
ds4.Relations.Add(r);
//Create columns for return table
for (int i = 0; i < First.Columns.Count; i++)
{
table.Columns.Add(First.Columns[i].ColumnName, First.Columns[i].DataType);
}
//If First Row not in Second, Add to return table.
table.BeginLoadData();
foreach (DataRow parentrow in ds4.Tables[0].Rows)
{
DataRow[] childrows = parentrow.GetChildRows(r);
if (childrows == null || childrows.Length == 0)
table.LoadDataRow(parentrow.ItemArray, true);
table1.LoadDataRow(childrows, false);
}
table.EndLoadData();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return table;
}
CSV ファイルを DataTable にロードする前に単純に比較することはできませんか?
string[] a = System.IO.File.ReadAllLines(@"cvs_a.txt");
string[] b = System.IO.File.ReadAllLines(@"csv_b.txt");
// get the lines from b that are not in a
IEnumerable<string> diff = b.Except(a);
//... parse b into DataTable ...
linq を使用するだけで実現できます。
private DataTable CompareDT(DataTable TableA, DataTable TableB)
{
DataTable TableC = new DataTable();
try
{
var idsNotInB = TableA.AsEnumerable().Select(r => r.Field<string>(Keyfield))
.Except(TableB.AsEnumerable().Select(r => r.Field<string>(Keyfield)));
TableC = (from row in TableA.AsEnumerable()
join id in idsNotInB
on row.Field<string>(ddlColumn.SelectedItem.ToString()) equals id
select row).CopyToDataTable();
}
catch (Exception ex)
{
lblresult.Text = ex.Message;
ex = null;
}
return TableC;
}
私はtzotのアイデアを続けています...
並べ替え可能なセットが 2 つある場合は、以下を使用できます:</p>
List<string> diffList = new List<string>(sortedListA.Except(sortedListB));
より複雑なオブジェクトが必要な場合は、自分でコンパレータを定義して、引き続き使用できます。
try
{
if (ds.Tables[0].Columns.Count == ds1.Tables[0].Columns.Count)
{
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
for (int j = 0; j < ds.Tables[0].Columns.Count; j++)
{
if (ds.Tables[0].Rows[i][j].ToString() == ds1.Tables[0].Rows[i][j].ToString())
{
}
else
{
MessageBox.Show(i.ToString() + "," + j.ToString());
}
}
}
}
else
{
MessageBox.Show("Table has different columns ");
}
}
catch (Exception)
{
MessageBox.Show("Please select The Table");
}