1

改行で区切られた単語を含む2つのテキストファイルがあります。それぞれが昇順でソートされ、それぞれ約60MBです。2番目のファイルに存在しないすべての単語を取得する必要があります(ある種のexcept操作)。2つのファイルの単語数は必ずしも同じではありません。

2つのファイルがソートされているという事実に基づいて何かをしようと思いましたが、実際には成功しませんでした。私はTPLを使用して作業を並行させました。私は何かから始めましたが、どのように終了するか、どのように作業を並行させるかがわかりません。

助けていただければ幸いです。

static StreamReader _streamReader1 = new StreamReader("file1.txt");
static StreamReader _streamReader2 = new StreamReader("file2.txt");
static IEnumerable<string> GetWordsFromFile1()
{
    while (!_streamReader1.EndOfStream)
    {
        yield return _streamReader1.ReadLine();
    }
}
static List<string> exceptedWords = new List<string>();
static void ExceptWords(string word)
{
     //Here I believe I should read a word from 2nd file and somehow to compare to <word>
     //   and continue reading until word < word2?
}
static void Main(string[] args)
{
    var words = GetWordsFromFile1();
    Parallel.ForEach(words, ExceptWords);
}
4

5 に答える 5

3

私見、KISSはこのようなものに勝ちます:

var wordsFromFile1 = File.ReadAllLines("file1.txt");
var wordsFromFile2 = File.ReadAllLines("file2.txt");
var file1ExceptFile2 = wordsFromFile1.Except(wordsFromFile2);

大文字と小文字を区別しない比較が必要な場合:

var wordsFromFile1 = File.ReadAllLines("file1.txt");
var wordsFromFile2 = File.ReadAllLines("file2.txt");
var file1ExceptFile2 = wordsFromFile1.Except(wordsFromFile2, StringComparer.OrdinalIgnoreCase);
于 2012-07-01T21:16:52.117 に答える
2

TPLおそらくこれはあなたの質問に直接答えることはできませんが、ファイルがソートされているという事実に依存して、またはを使用する簡単な方法がわかりません。私は重い物を持ち上げるLINQの方法を信頼します。Exceptファイルは天文学的に巨大ではないので、ファイルをメモリにロードすることは問題ではないはずです。

static IEnumerable<string> GetWordsFromFile(StreamReader _streamReader)
{
    while (!_streamReader.EndOfStream)
    {
        yield return _streamReader.ReadLine();
    }
}

static void Main(string[] args)
{
    using (var _streamReader1 = new StreamReader("file1.txt"))
    {
        using (var _streamReader2 = new StreamReader("file2.txt"))
        {
            var words = GetWordsFromFile(_streamReader1)
                .Except(GetWordsFromFile(_streamReader2));
        }
    }
}
于 2012-07-01T21:22:33.963 に答える
2

単純なケースを測定し、「十分に高速ではない」と判断するまで、このようなものは使用しませんが、ソートされた性質を活用する脳死の(並列ではない)アプローチを次に示します。これを書く他の/より良い方法がありますが、アイデアは、両方の「ストリーム」を開始してから、それらを前に進めて比較することができるということです。

エッジケースと開始/終了を無視して、2つの単語ストリームのそれぞれから現在の単語を比較し、「入力」の単語が少ない(保持する)か、一致する(スキップする)か、後で(移動する) 'ストリームフォワードを除く)。

各「ストリーム」などからの現在の単語などについてローカルを維持することもできますが、少なくとも実際のプロファイル測定が行われるまでは、この種のアプローチを無視してlinqExceptまたはSortedSet.ExceptWithを実行することをお勧めします。もっと複雑なものが必要であることを示します。:)

void Main()
{
    var input = new[] { "abc", "bcd", "xyz", "zzz", };
    var except = new[] { "abc", "xyz", };

    ExceptSortedInputs(input, except).Dump();
}

// Define other methods and classes here
public static IEnumerable<string> ExceptSortedInputs(IEnumerable<string> inputSequence, IEnumerable<string> exceptSequence)
{
    Contract.Requires<ArgumentNullException>(inputSequence != null);
    Contract.Requires<ArgumentNullException>(exceptSequence != null);

    var exceptEnumerator = exceptSequence.GetEnumerator();
    Contract.Assert(exceptEnumerator.MoveNext(), "except sequence was empty, silly");

    var inputEnumerator = inputSequence.GetEnumerator();
    while (inputEnumerator.MoveNext())
    {
        // need to move the except sequence forward to ensure it's at or later than the current input word
        while (String.Compare(inputEnumerator.Current, exceptEnumerator.Current) == 1)
        {
            if (exceptEnumerator.MoveNext() == false)
            {
                // stupid optimization - since there are no more except matches, we can just return the rest of the input
                do
                {
                    yield return inputEnumerator.Current;
                }
                while (inputEnumerator.MoveNext());
                yield break;
            }
        }

        // when we get here, we know the current 'except' word is equal to or later than the input one, so we can just check equality
        if (inputEnumerator.Current != exceptEnumerator.Current)
        {
            yield return inputEnumerator.Current;
        }
    }
}

典型的なマージ結合のインターリーブされた性質に少し似たバージョン(そして明確にするのに役立つかもしれないローカルを追加する)

void Main()
{
    var input = new[] { "abc", "bcd", "xyz", "zzz", };
    var except = new[] { "abc", "xyz", };

    ExceptSortedInputs(input, except).Dump();
}

// Define other methods and classes here
public static IEnumerable<string> ExceptSortedInputs(IEnumerable<string> inputSequence, IEnumerable<string> exceptSequence)
{
    var exceptEnumerator = exceptSequence.GetEnumerator();
    var exceptStillHasElements = exceptEnumerator.MoveNext();

    var inputEnumerator = inputSequence.GetEnumerator();
    var inputStillHasElements = inputEnumerator.MoveNext();

    while (inputStillHasElements)
    {
        if (exceptStillHasElements == false)
        {
            // since we exhausted the except sequence, we know we can safely return any input elements
            yield return inputEnumerator.Current;
            inputStillHasElements = inputEnumerator.MoveNext();
            continue;
        }

        // need to compare to see which operation to perform
        switch (String.Compare(inputEnumerator.Current, exceptEnumerator.Current))
        {
            case -1:
                // except sequence is already later, so we can safely return this
                yield return inputEnumerator.Current;
                inputStillHasElements = inputEnumerator.MoveNext();
                break;

            case 0:
                // except sequence has a match, so we can safely skip this
                inputStillHasElements = inputEnumerator.MoveNext();
                break;

            case 1:
                // except sequence is behind - we need to move it forward
                exceptStillHasElements = exceptEnumerator.MoveNext();
        }
    }
}
于 2012-07-01T22:27:58.463 に答える
1

あなたが探しているのはマージ結合と呼ばれます。このアルゴリズムをわずかに異なる形式で使用して、次のいずれかを計算できます。

  • 内部結合
  • アウタージョイン
  • それ外
  • 交差する
  • 連合
  • ユニオンオール

そして確かに他の人。その特定の名前を検索すると、たくさんの情報が見つかると思います。

于 2012-07-01T20:44:12.197 に答える
0

投稿された回答を見て、「アプローチの違いはどうだろう?」と思いました。

とにかく、2つの辞書ファイルをダウンロードしてタイミングコードを書き、投稿したコードをvs2010に貼り付けました。

出力は次のようになります。

> ManningsBaseCase1:   ElapsedTime: 0.1973, numOfIterations: 64
> ManningsBaseCase2:   ElapsedTime: 0.2036, numOfIterations: 64
> KevinsLINQ1:         ElapsedTime: 0.1803, numOfIterations: 64
> KevinsLINQ2:         ElapsedTime: 0.1773, numOfIterations: 64
> ManningsOldMerge:    ElapsedTime: 0.0797, numOfIterations: 128
> ManningsCleanMerge:  ElapsedTime: 0.0800, numOfIterations: 256

各人のコードは、10秒以上かかるのに十分な反復で実行され、その後、反復ごとの平均が取られました。

結果はわずかにずれている可能性がありますが、ループのオーバーヘッドを差し引くために、空のForループの長さを128回繰り返したくありませんでした(読者への演習として残しておきます)。

コードはまた、すべてのアプローチが同じ解決策を提供することを確認しました。

コードは次のとおりです。

class Program
{
    private static readonly string filename1 = "DictoFile1.txt";
    private static readonly string filename2 = "DictoFile2.txt";
    private static readonly int numOfTests = 6;
    private static readonly int MinTimingVal = 1000;

    private static string[] testNames = new string[] {            
        "ManningsBaseCase1:   ",
        "ManningsBaseCase2:   ",
        "KevinsLINQ1:         ",
        "KevinsLINQ2:         ",
        "ManningsOldMerge:    ",
        "ManningsCleanMerge:  "
        };

    private static string[] prev;
    private static string[] next;

    public static void Main(string[] args)
    {
        Console.WriteLine("Starting tests...");
        Debug.WriteLine("Starting tests...");

        Console.WriteLine("");
        Debug.WriteLine("");

        Action[] actionArray = new Action[numOfTests];

        actionArray[0] = ManningsBaseCase1;
        actionArray[1] = ManningsBaseCase2;
        actionArray[2] = KevinsLINQ1;
        actionArray[3] = KevinsLINQ2;
        actionArray[4] = ManningsOldInterleaved;
        actionArray[5] = ManningsCleanInterleaved;

        for( int i = 0; i < actionArray.Length; i++ )
        {
            Console.Write(testNames[i]);
            Debug.Write(testNames[i]);

            Action a = actionArray[i];
            DoTiming(a, i);

            if (i > 0)
            {
                if (!ValidateLists())
                {
                    Console.WriteLine(" --- Validation had an error.");
                    Debug.WriteLine(" --- Validation had an error.");
                }
            }

            prev = next;
        }

        Console.WriteLine("");
        Debug.WriteLine("");

        Console.WriteLine("Tests complete.");
        Debug.WriteLine("Tests complete.");

        Console.WriteLine("Press Enter to Close Console...");
        Debug.WriteLine("Press Enter to Close Console...");

        Console.ReadLine();
    }

    private static bool ValidateLists()
    {
        if (prev == null) return false;
        if (next == null) return false;
        if (prev.Length != next.Length) return false;

        for (int i = 0; i < prev.Length; i++)
        {
            if (prev[i] != next[i]) return false;
        }

        return true;
    }

    private static void DoTiming( Action a, int num )
    {
        a.Invoke();

        Stopwatch watch = new Stopwatch();
        Stopwatch loopWatch = new Stopwatch();

        bool shouldRetry = false;

        int numOfIterations = 2;

        do
        {
            watch.Start();

            for (int i = 0; i < numOfIterations; i++)
            {
                a.Invoke();
            }

            watch.Stop();

            shouldRetry = false;

            if (watch.ElapsedMilliseconds < MinTimingVal) //if the time was less than the minimum, increase load and re-time.
            {
                shouldRetry = true;
                numOfIterations *= 2;
                watch.Reset();
            }

        } while ( shouldRetry );

        long totalTime = watch.ElapsedMilliseconds;

        double avgTime = ((double)totalTime) / (double)numOfIterations;

        Console.WriteLine("ElapsedTime: {0:N4}, numOfIterations: " + numOfIterations, avgTime/1000.00);
        Debug.WriteLine("ElapsedTime: {0:N4}, numOfIterations: " + numOfIterations, avgTime / 1000.00);
    }

    private static void ManningsBaseCase1()
    {
        string[] wordsFromFile1 = File.ReadAllLines( filename1 );
        string[] wordsFromFile2 = File.ReadAllLines( filename2 );
        IEnumerable<string> file1ExceptFile2 = wordsFromFile1.Except(wordsFromFile2);
        string[] asArray = file1ExceptFile2.ToArray();
        next = asArray;
    }

    private static void ManningsBaseCase2()
    {
        string[] wordsFromFile1 = File.ReadAllLines( filename1 );
        string[] wordsFromFile2 = File.ReadAllLines( filename2 );
        IEnumerable<string> file1ExceptFile2 = wordsFromFile1.Except(wordsFromFile2, StringComparer.OrdinalIgnoreCase);
        string[] asArray = file1ExceptFile2.ToArray();
        next = asArray;
    }

    private static IEnumerable<string> GetWordsFromFile(StreamReader _streamReader)
    {
        while (!_streamReader.EndOfStream)
        {
            yield return _streamReader.ReadLine();
        }
    }

    private static void KevinsLINQ1()
    {
        using (StreamReader _streamReader1 = new StreamReader(filename1))
        {
            using (StreamReader _streamReader2 = new StreamReader(filename2))
            {
               IEnumerable<string> words = GetWordsFromFile(_streamReader1)
                    .Except(GetWordsFromFile(_streamReader2));
               string[] asArray = words.ToArray();
               next = asArray;
            }
        }
    }

    private static void KevinsLINQ2()
    {
        using (StreamReader _streamReader1 = new StreamReader(filename1))
        {
            using (StreamReader _streamReader2 = new StreamReader(filename2))
            {
                IEnumerable<string> words = GetWordsFromFile(_streamReader1)
                    .Except(GetWordsFromFile(_streamReader2).AsParallel());
                string[] asArray = words.ToArray();
                next = asArray;
            }
        }
    }

    // Define other methods and classes here
    public static IEnumerable<string> ExceptSortedInputsOld(IEnumerable<string> inputSequence, IEnumerable<string> exceptSequence)
    {
        IEnumerator<string> exceptEnumerator = exceptSequence.GetEnumerator();

        IEnumerator<string> inputEnumerator = inputSequence.GetEnumerator();
        while (inputEnumerator.MoveNext())
        {
            // need to move the except sequence forward to ensure it's at or later than the current input word
            while (String.Compare(inputEnumerator.Current, exceptEnumerator.Current) == 1)
            {
                if (exceptEnumerator.MoveNext() == false)
                {
                    // stupid optimization - since there are no more except matches, we can just return the rest of the input
                    do
                    {
                        yield return inputEnumerator.Current;
                    }
                    while (inputEnumerator.MoveNext());
                    yield break;
                }
            }

            // when we get here, we know the current 'except' word is equal to or later than the input one, so we can just check equality
            if (inputEnumerator.Current != exceptEnumerator.Current)
            {
                yield return inputEnumerator.Current;
            }
        }
    }

    private static void ManningsOldInterleaved()
    {
        IEnumerable<string> wordsFromFile1 = File.ReadLines(filename1);
        IEnumerable<string> wordsFromFile2 = File.ReadLines(filename2);
        IEnumerable<string> file1ExceptFile2 = ExceptSortedInputsOld(wordsFromFile1, wordsFromFile2);

        string[] asArray = file1ExceptFile2.ToArray();
        next = asArray;
    }


    private static IEnumerable<string> ExceptSortedInputsClean(IEnumerable<string> inputSequence, IEnumerable<string> exceptSequence)
    {
        IEnumerator<string> exceptEnumerator = exceptSequence.GetEnumerator();
        bool exceptStillHasElements = exceptEnumerator.MoveNext();

        IEnumerator<string> inputEnumerator = inputSequence.GetEnumerator();
        bool inputStillHasElements = inputEnumerator.MoveNext();

        while (inputStillHasElements)
        {
            if (exceptStillHasElements == false)
            {
                // since we exhausted the except sequence, we know we can safely return any input elements
                yield return inputEnumerator.Current;
                inputStillHasElements = inputEnumerator.MoveNext();
                continue;
            }

            // need to compare to see which operation to perform
            switch (String.Compare(inputEnumerator.Current, exceptEnumerator.Current))
            {
                case -1:
                    // except sequence is already later, so we can safely return this
                    yield return inputEnumerator.Current;
                    inputStillHasElements = inputEnumerator.MoveNext();
                    break;

                case 0:
                    // except sequence has a match, so we can safely skip this
                    inputEnumerator.MoveNext();
                    break;

                case 1:
                    // except sequence is behind - we need to move it forward
                    exceptStillHasElements = exceptEnumerator.MoveNext();
                    break;
            }
        }
    }


    private static void ManningsCleanInterleaved()
    {
        IEnumerable<string> wordsFromFile1 = File.ReadLines(filename1);
        IEnumerable<string> wordsFromFile2 = File.ReadLines(filename2);
        IEnumerable<string> file1ExceptFile2 = ExceptSortedInputsClean(wordsFromFile1, wordsFromFile2);

        string[] asArray = file1ExceptFile2.ToArray();
        next = asArray;
    }

}

コピーしてVS2010.Net4.0に貼り付け、txtファイルと使用法を追加するだけで、機能するはずです。

注:MinTimingValを10秒ではなく1秒に戻しました。

したがって、とにかく、マニングによるマージアプローチは残りの2倍を上回りました。

グッドワークマニング。

とはいえ、FileStreamクラスを使用してファイル入力を並列化することはまだ可能かもしれないと思います。同じファイルに2つの異なるFileStreamを作成し、1つを先頭から開始し、もう1つをSeek()にするか、その.Positionをファイルの中央に設定してそこから読み取ります。

私がそれに近づいたら、I/O操作の並列化が実際にスピードアップをもたらすかどうかを確認するためだけに試してみるかもしれません。

于 2012-07-02T07:47:00.067 に答える