10

私の許しは許しますが、私はいくつかのガイダンスが必要であり、これに答える別の質問を見つけることができません。かなり大きなcsvファイル(〜300k行)があり、特定の入力について、csvの行がその入力で始まるかどうかを判断する必要があります。csvをアルファベット順に並べ替えましたが、わかりません。

1)csvの行を処理する方法-リスト/コレクションとして読み込む必要がありますか、それともOLEDB、組み込みデータベースなどを使用する必要がありますか?

2)アルファベット順のリストから効率的に何かを見つける方法(リスト全体を検索するのではなく、物事をスピードアップするためにソートされているという事実を使用して)

4

10 に答える 10

9

具体的な答えを出すのに十分な詳細を提供していませんが...


CSV ファイルが頻繁に変更される場合は、OLEDB を使用し、入力に基づいて SQL クエリを変更するだけです。

string sql = @"SELECT * FROM [" + fileName + "] WHERE Column1 LIKE 'blah%'";
using(OleDbConnection connection = new OleDbConnection(
          @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + fileDirectoryPath + 
          ";Extended Properties=\"Text;HDR=" + hasHeaderRow + "\""))

CSV ファイルが頻繁に変更されず、それに対して多くの「クエリ」を実行する場合は、一度メモリにロードし、毎回すばやく検索します。

検索を列で完全に一致させたい場合は、キーが一致する列で、値が行データであるディクショナリを使用します。

Dictionary<long, string> Rows = new Dictionar<long, string>();
...
if(Rows.ContainsKey(search)) ...

検索を StartsWith のような部分一致にしたい場合は、検索可能なデータ (つまり、最初の列) を含む 1 つの配列と、行データを含む別のリストまたは配列を用意します。次に、C# の組み込みバイナリ検索を使用しますhttp://msdn.microsoft.com/en-us/library/2cy9f6wb.aspx

string[] SortedSearchables = new string[];
List<string> SortedRows = new List<string>();
...
string result = null;
int foundIdx = Array.BinarySearch<string>(SortedSearchables, searchTerm);
if(foundIdx < 0) {
    foundIdx = ~foundIdx;
    if(foundIdx < SortedRows.Count && SortedSearchables[foundIdx].StartsWith(searchTerm)) {
        result = SortedRows[foundIdx];
    }
} else {
    result = SortedRows[foundIdx];
}

: コードはブラウザー ウィンドウ内で記述されており、テストされていないため、構文エラーが含まれている可能性があります。

于 2013-01-15T17:13:48.357 に答える
5

プログラムの実行ごとに 1 回だけ実行している場合、これはかなり高速に思えます。(以下のコメントに基づいて、FileStream の代わりに StreamReader を使用するように更新されました)

    static string FindRecordBinary(string search, string fileName)
    {
        using (StreamReader fs = new StreamReader(fileName))
        {
            long min = 0; // TODO: What about header row?
            long max = fs.BaseStream.Length;
            while (min <= max)
            {
                long mid = (min + max) / 2;
                fs.BaseStream.Position = mid;

                fs.DiscardBufferedData();
                if (mid != 0) fs.ReadLine();
                string line = fs.ReadLine();
                if (line == null) { min = mid+1; continue; }

                int compareResult;
                if (line.Length > search.Length)
                    compareResult = String.Compare(
                        line, 0, search, 0, search.Length, false );
                else
                    compareResult = String.Compare(line, search);

                if (0 == compareResult) return line;
                else if (compareResult > 0) max = mid-1;
                else min = mid+1;
            }
        }
        return null;
    }

これは、50 MB の 600,000 レコードのテスト ファイルに対して 0.007 秒で実行されます。比較すると、ファイル スキャンは、レコードがどこにあるかにもよりますが、平均で 0.5 秒以上かかります。(100倍の差)

明らかに、複数回実行すると、キャッシングによって速度が向上します。部分的なキャッシュを行う簡単な方法の 1 つは、StreamReader を開いたままにして再利用し、毎回最小値と最大値をリセットすることです。これにより、メモリに常に 50 メガを保存することができます。

編集: knaki02 の提案された修正を追加しました。

于 2013-01-15T19:49:37.283 に答える
5

データをメモリにキャッシュでき、1 つの主キー列でリストを検索するだけでよい場合は、データをDictionaryオブジェクトとしてメモリに格納することをお勧めします。このDictionaryクラスは、データをキーと値のペアとしてハッシュ テーブルに格納します。主キー列をディクショナリのキーとして使用し、残りの列をディクショナリの値として使用できます。ハッシュ テーブル内のキーによる項目の検索は、通常、非常に高速です。

たとえば、次のようにデータをディクショナリにロードできます。

Dictionary<string, string[]> data = new Dictionary<string, string[]>();
using (TextFieldParser parser = new TextFieldParser("C:\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData)
    {
        try
        {
            string[] fields = parser.ReadFields();
            data[fields[0]] = fields;
        }
        catch (MalformedLineException ex)
        {
            // ...
        }
    }
}

そして、次のように、任意のアイテムのデータを取得できます。

string fields[] = data["key I'm looking for"];
于 2013-01-15T16:56:16.457 に答える
3

CSV がソートされている場合 - 全体をメモリにロードできる場合 (必要な処理が各行の .StartsWith() だけである場合) -バイナリ検索を使用して、非常に高速な検索を行うことができます。

多分このようなもの(テストされていません!):

var csv = File.ReadAllLines(@"c:\file.csv").ToList();
var exists = csv.BinarySearch("StringToFind", new StartsWithComparer());

...

public class StartsWithComparer: IComparer<string>
{
    public int Compare(string x, string y)
    {
        if(x.StartsWith(y))
            return 0;
        else
            return x.CompareTo(y);
    }
}
于 2013-01-15T16:53:18.327 に答える
2

私は仕事のためにこれを急いで書きましたが、改善される可能性があります...

列番号を定義します。

private enum CsvCols
{
    PupilReference = 0,
    PupilName = 1,
    PupilSurname = 2,
    PupilHouse = 3,
    PupilYear = 4,
}

モデルを定義する

public class ImportModel
{
    public string PupilReference { get; set; }
    public string PupilName { get; set; }
    public string PupilSurname { get; set; }
    public string PupilHouse { get; set; }
    public string PupilYear { get; set; }
}

モデルのリストをインポートして入力します。

  var rows = File.ReadLines(csvfilePath).Select(p => p.Split(',')).Skip(1).ToArray();

    var pupils = rows.Select(x => new ImportModel
    {
        PupilReference = x[(int) CsvCols.PupilReference],
        PupilName = x[(int) CsvCols.PupilName],
        PupilSurname = x[(int) CsvCols.PupilSurname],
        PupilHouse = x[(int) CsvCols.PupilHouse],
        PupilYear = x[(int) CsvCols.PupilYear],

    }).ToList();

厳密に型指定されたオブジェクトのリストを返します

于 2015-06-25T19:34:38.087 に答える
1

ファイルがメモリ内にあり(たとえば、並べ替えを行ったため)、ファイルを文字列(行)の配列として保持している場合は、単純な二分検索方法を使用できます。CodeReviewでこの質問のコードから始めることができます。比較子を変更して、各行の先頭のみをチェックするstring代わりに、使用するようにします。int

ファイルが変更されたり、別のプログラムによって保存/ソートされたりする可能性があるため、毎回ファイルを再読み込みする必要がある場合は、最も単純なアルゴリズムが最適です。

using (var stream = File.OpenText(path))
{
    // Replace this with you comparison, CSV splitting
    if (stream.ReadLine().StartsWith("..."))
    {
        // The file contains the line with required input
    }
}

もちろん、毎回メモリ内のファイル全体を読み取ることもできますが(LINQまたはを使用するため)、これは最適とは言えず(数行だけを調べる必要がある場合でもすべてを読み取ることができます)、ファイル自体が大きすぎる可能性もあります。 。List<T>.BinarySearch()

本当にもっと何かが必要で、ソートのためにファイルがメモリにない場合(ただし、要件と比較して実際のパフォーマンスをプロファイリングする必要があります)、 Boyer-Mooreアルゴリズムなどのより優れた検索アルゴリズムを実装する必要があります。

于 2013-01-15T16:56:42.320 に答える
1

OPは、実際には行に基づいて検索する必要があると述べています。

問題は、行をメモリに保持するかどうかです。

行 1 k の場合、300 MB のメモリ。
1 行が 1 MB の場合、300 GB のメモリ。

Stream.Readline はメモリ プロファイルが低くなります
。並べ替えられているため、それより大きい場合は検索を停止できます。

あなたがそれを記憶に保持しているなら、単純な

List<String> 

LINQで動作します。
LINQ はこの並べ替えを利用できるほどスマートではありませんが、300K に対しては依然としてかなり高速です。

BinarySearch はソートを利用します。

于 2013-01-15T17:23:17.863 に答える
0

通常、専用のCSVパーサー( thisまたはthisなど)を見つけることをお勧めします。しかし、私はあなたの質問でこの行に気づきました:

特定の入力について、csvの行がその入力で始まるかどうかを判断する必要があります。

これは、これが決定される前にコンピュータがCSVデータの解析に費やす時間が無駄になっていることを示しています。必要なのは、テキストとテキストを単純に一致させるコードだけです。これは、他の方法と同じように、文字列の比較によって簡単に行うことができます。

さらに、データが並べ替えられているとのことです。これにより、処理速度が大幅に向上します...ただし、これを利用するには、低レベルのファイルストリームでシーク呼び出しを行うための独自のコードを作成する必要があることに注意する必要があります。これは、これまでで最高のパフォーマンス結果になりますが、最も初期の作業とメンテナンスも必要になります。

パフォーマンスの目標を設定し、比較的単純なものを構築し、その目標に対して結果を測定する、エンジニアリングベースのアプローチをお勧めします。特に、上記の2番目のリンクから始めます。そこでのCSVリーダーは、一度に1つのレコードのみをメモリにロードするため、十分に機能するはずであり、簡単に開始できます。そのリーダーを使用するものを作成し、結果を測定します。彼らがあなたの目標を達成したら、そこで止まります。

目標を達成できない場合は、リンクのコードを調整して、各行を読み取るときに最初に文字列の比較を行い(csvデータの解析を行う前に)、次の行のcsvを解析する作業のみを行うようにします。マッチ。これはパフォーマンスが向上するはずですが、最初のオプションが目標を満たさない場合にのみ作業を行ってください。準備ができたら、パフォーマンスを再度測定します。

最後に、それでもパフォーマンスの目標を達成できない場合は、seek呼び出しを使用してファイルストリームでバイナリ検索を実行するための低レベルのコードを作成する領域に入ります。これは、パフォーマンスの面で実行できる最善の方法ですが、記述が非常に面倒でバグが発生しやすいため、前の手順の目標を完全に達成できない場合にのみここに移動します。 。

パフォーマンスは機能であり、他の機能と同様に、実際の設計目標と比較して、その機能をどのように構築するかを評価する必要があることを忘れないでください。「できるだけ速く」は、合理的な設計目標ではありません。「0.25秒以内にユーザー検索に応答する」のようなものは実際の設計目標であり、より単純で遅いコードがそれでもその目標を達成する場合は、そこで停止する必要があります。

于 2013-01-16T17:32:46.347 に答える
0

これが私のVB.netコードです。Quote Qualified CSV 用なので、通常の CSV の場合Let n = P.Split(New Char() {""","""})Let n = P.Split(New Char() {","})

Dim path as String = "C:\linqpad\Patient.txt"
Dim pat = System.IO.File.ReadAllLines(path)
Dim Patz = From P in pat _
    Let n = P.Split(New Char() {""","""}) _
    Order by n(5) _
    Select New With {
        .Doc =n(1), _
        .Loc = n(3), _
        .Chart = n(5), _
        .PatientID= n(31), _
        .Title = n(13), _
        .FirstName = n(9), _
        .MiddleName = n(11), _
        .LastName = n(7), 
        .StatusID = n(41) _
        }
Patz.dump
于 2013-01-15T17:04:46.533 に答える
0

無料のCSV リーダーをお試しください。車輪を何度も発明する必要はありません;)

1) 結果を保存する必要がない場合は、CSV を反復処理します。各行を処理して忘れてください。すべての行を何度も処理する必要がある場合は、それらを List または Dictionary に保存します (もちろん適切なキーを使用して)

2)このような一般的な拡張メソッドを試してください

var list = new List<string>() { "a", "b", "c" };
string oneA = list.FirstOrDefault(entry => !string.IsNullOrEmpty(entry) && entry.ToLowerInvariant().StartsWidth("a"));
IEnumerable<string> allAs = list.Where(entry => !string.IsNullOrEmpty(entry) && entry.ToLowerInvariant().StartsWidth("a"));
于 2013-01-15T16:55:12.387 に答える