コレクションの循環スライス、シフト、および回転

コレクションと配列を操作する際の典型的なタスクの1つは、 i番目のインデックスから始まるn個の要素の選択、またはオプションとしてi番目からj番目のインデックスまでの要素の選択です。 C#では、 Linqライブラリの拡張メソッドを使用して、非常に簡単に解決されます。



var selectedItems = items.Skip(i).Take(n).ToArray(); var selectedItems = items.Skip(i).Take(j - i).ToArray();
      
      





文字列のための特別なSubstingメソッドがあります



 var target = source.Substing(i, n); var target = source.Substing(i, j - i); var target = source.Substing(i); // from i to end
      
      





多くの場合、初心者の開発者は知識不足のためにそのような目的で自転車を発明します。そのため、技術面接担当者は面接中にこのトピックに関する質問をすることがあります。 一部のプログラミング言語には、関連する非常に興味深いスライスの概念もあります。これについては、例を挙げて詳細に説明します。



画像



アルファベット順に文字の配列が与えられます。 各文字は、左から右(頭から)で計算される通常の正のインデックスだけでなく、右から左(尾から)で計算される負のインデックスにも対応します。



 var bodyLetters = new[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I'}; var headIndexes = new[] { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 }; var tailIndexes = new[] {-8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 };
      
      





更新

最初は、Pythonのインデックス付け方法が基礎として採用されました(メソッドの2番目のパラメーターはテールインデックスです)が、議論の中でこれは最適なソリューションではないことが判明しました(2番目のパラメーターとして長さ[負を含む]を使用するとより有利になります)更新しました。



Python言語にインデックスを付けるSliceメソッドのバリアント(2番目のパラメーターはテールインデックスです)
4番目から7番目までの要素の選択は、いくつかの方法で実行できます。



 //      bodyLetters.Slice(3, 7); // 'D', 'E', 'F', 'G' bodyLetters.Slice(-5, 7); // 'D', 'E', 'F', 'G' bodyLetters.Slice(3, -1); // 'D', 'E', 'F', 'G' bodyLetters.Slice(-5, -1); // 'D', 'E', 'F', 'G'
      
      





サンプルの長さは、1を追加することなくインデックスによって簡単に計算され、混乱を引き起こさないため(5-2 + 1 = 4の代わりに5-2 = 3)、テールを含めないのが便利です。



しかし、インデックス8の要素がないため、元のコレクションの最後の要素を結果の選択に含めるには疑問が生じます(負の一致は見つかりません(整数が-0で+ 0と区別できない場合を除く)。 いくつかの矛盾があります。 もちろん、例えばPythonで行われたように、尾を含めて長さの計算を複雑にするか、配列の外側で人為的にインデックスを使用することができます。



最良の決定を下すために、他のポイントを見てみましょう。テールインデックスがヘッドインデックスよりも早い場合、メソッドはどのように動作する必要がありますか?



 bodyLetters.Slice(7, 3); // ??? bodyLetters.Slice(-1, -5); // ???
      
      





最も基本的な決定は、空のサンプルをスローするか、例外をスローするか、テールとヘッドのインデックスを許容範囲内でスワップしてから、処理を続行することです。おそらく、結果のサンプルを逆の順序で出力しますか?



そして、尾と頭のインデックスが一致したらどうしますか? 規則に従って、頭は含めますが尾は除外しますが、この場合、尾と頭はまったく同じです! 論理的な矛盾が生じます。 テールケースに戻りますか?



考えてみましょう... 閉じて、配列自体をループしたらどうなるでしょうか? つまり、最後のインデックス7の直後が再び0になり、たとえばパラメーター62をメソッドに渡すと、次の結果'G'、 'I'、 'A'、 'B'を出力すると仮定します。 次に、元のコレクションの最後の要素をサンプルに含めるには、 Slice(6、0)// 'G' 'I'を書き込むだけで十分です。Slice(5、5)を書き込むと、元のコレクションが左に5インデックスずつ循環的にシフトします。 「F」、「G」、「I」、「A」、「B」、「C」、「D」、「E」 -これは奇跡です!



1つの要素を取得するには、 Slice(5、6)を記述する必要があり、さらに負のインデックスの問題はありません。配列の境界を突然超えると、予期される例外が発生します。 なんと美しいことでしょう! カットしたサンプルを逆の順序で突然出力する必要がある場合は、 Reverseメソッドが役立ちます。 矛盾のない非常に自然な2つのクラスの問題の一般的な解決策を一度に見つけました。



 //      bodyLetters.Slice(6, 2); // 'G', 'I', 'A', 'B' bodyLetters.Slice(5, 5); // 'F', 'G', 'I', 'A', 'B', 'C', 'D', 'E' bodyLetters.Slice(5, 5, SliceOptions.Lazy); // {empty} bodyLetters.Slice(5, 6); // 'F' bodyLetters.Slice(5, 0); // 'F', 'G', 'I' bodyLetters.Slice(5); // 'F', 'G', 'I'
      
      





まあ、それを実現するために残っています!



  [Flags] public enum SliceOptions { None = 0, Lazy = 1, } public static IEnumerable<T> Slice<T>( this IEnumerable<T> collection, int head, int tail = 0, SliceOptions options = SliceOptions.None) { var items = collection as T[] ?? collection.ToArray(); var count = items.Count(); head = head < 0 ? count + head : head; tail = tail < 0 ? count + tail : tail; if (head < 0 || count - 1 < head) throw new ArgumentOutOfRangeException("head"); if (tail < 0 || count - 1 < tail) throw new ArgumentOutOfRangeException("tail"); if (head == tail && (options & SliceOptions.Lazy) == SliceOptions.Lazy) { yield break; } if (head < tail) { foreach (var item in items.Skip(head).Take(tail - head)) { yield return item; } } else { foreach (var item in items.Skip(head)) { yield return item; } foreach (var item in items.Skip(0).Take(tail)) { yield return item; } } }
      
      







更新2
  public static IEnumerable<T> Turn<T>(this IList<T> items, int skip, int turnsCount = 0) { var reverse = skip < 0; var count = items.Count; skip = reverse ? count + skip : skip; var take = turnsCount == 0 ? reverse ? -skip - 1 : count - skip : count*turnsCount; return items.Ring(skip, take); } public static IEnumerable<T> Ring<T>(this IList<T> items, int skip, int take) { var reverse = take < 0; var count = items.Count; skip = skip < 0 ? count + skip : skip; skip = skip < count ? skip : skip%count; take = reverse ? -take : take; for (var i = 0; i < take; i++) { var j = i < count ? i : i%count; var index = reverse ? skip - j : skip + j; index = index < 0 ? count + index : index; index = index < count ? index : index%count; yield return items[index]; } } private static void Main(string[] args) { var bodyLetters = new[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I'}; var headIndexes = new[] { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 }; var tailIndexes = new[] {-8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 }; // 'C', 'D', 'E', 'F', 'G', 'I', 'A', 'B', bodyLetters.Ring(2, 8).ToList().ForEach(Console.Write); Console.WriteLine(); // 'C', 'B', 'A', 'I', 'G', 'F', 'E', 'D', bodyLetters.Ring(2, -8).ToList().ForEach(Console.Write); Console.WriteLine(); // 'D', 'E', 'F', 'G' bodyLetters.Ring(3, 4).ToList().ForEach(Console.Write); Console.WriteLine(); // 'D', 'E', 'F', 'G' bodyLetters.Ring(-5, 4).ToList().ForEach(Console.Write); Console.WriteLine(); // 'D', 'C', 'B', 'A' bodyLetters.Ring(-5, -4).ToList().ForEach(Console.Write); Console.WriteLine(); // 'D', 'E', 'F', 'G', 'I', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I', 'A', 'B', 'C' bodyLetters.Ring(3, 16).ToList().ForEach(Console.Write); Console.WriteLine(); // 'D', 'C', 'B', 'A', 'I', 'G', 'F', 'E', 'D', 'C', 'B', 'A', 'I', 'G', 'F', 'E' bodyLetters.Ring(-5, -16).ToList().ForEach(Console.Write); Console.WriteLine(); // 'D', 'E', 'F', 'G', 'I' bodyLetters.Turn(3).ToList().ForEach(Console.Write); Console.WriteLine(); // 'D', 'C', 'B', 'A' bodyLetters.Turn(-5).ToList().ForEach(Console.Write); Console.WriteLine(); Console.ReadKey(); }
      
      







最終更新

  private static void Main(string[] args) { var bodyLetters = new[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I'}; var headIndexes = new[] { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 }; var tailIndexes = new[] {-8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 }; // CDEFGICDEF bodyLetters.SkipByRing(18).TakeByRing(10).ToList().ForEach(Console.Write); Console.WriteLine(); // FEDCBAFEDC bodyLetters.SkipByRing(-18).TakeByRing(10).ToList().ForEach(Console.Write); Console.WriteLine(); // IGFEDCIGFE bodyLetters.SkipByRing(18).TakeByRing(-10).ToList().ForEach(Console.Write); Console.WriteLine(); // ABCDEFABCD bodyLetters.SkipByRing(-18).TakeByRing(-10).ToList().ForEach(Console.Write); Console.WriteLine(); Console.WriteLine(); // CDEFGIABCD bodyLetters.SliceByRing(18, 10).ToList().ForEach(Console.Write); Console.WriteLine(); // GIABCDEFGI bodyLetters.SliceByRing(-18, 10).ToList().ForEach(Console.Write); Console.WriteLine(); // BAIGFEDCBA bodyLetters.SliceByRing(18, -10).ToList().ForEach(Console.Write); Console.WriteLine(); // FEDCBAIGFE bodyLetters.SliceByRing(-18, -10).ToList().ForEach(Console.Write); Console.WriteLine(); Console.ReadKey(); }
      
      





実装

  // ReSharper disable PossibleMultipleEnumeration // ReSharper disable LoopCanBePartlyConvertedToQuery public static IEnumerable<T> SkipByRing<T>(this IEnumerable<T> source, int count) { var originalCount = 0; var reverse = count < 0; count = reverse ? -count : count; source = reverse ? source.Reverse() : source; while (true) { if (originalCount > 0) count %= originalCount; foreach (var item in source) { originalCount++; if (count > 0) { count--; continue; } yield return item; } if (count == 0) yield break; } } public static IEnumerable<T> TakeByRing<T>(this IEnumerable<T> source, int count) { var reverse = count < 0; count = reverse ? -count : count; source = reverse ? source.Reverse() : source; while (true) { foreach (var item in source) { if (count > 0) { count--; yield return item; } } if (count == 0) yield break; } } public static IEnumerable<T> SliceByRing<T>(this IEnumerable<T> source, int skipCount, int takeCount) { var originalCount = 0; var skipReverse = skipCount < 0; var takeReverse = takeCount < 0; skipCount = skipReverse ? -skipCount : skipCount; takeCount = takeReverse ? -takeCount : takeCount; source = takeReverse ? source.Reverse() : source; if (skipReverse ^ takeReverse) { var count = source.Count(); skipCount = count - skipCount % count; } while (true) { if (originalCount > 0) skipCount %= originalCount; foreach (var item in source) { originalCount++; if (skipCount > 0) { skipCount--; continue; } if (takeCount > 0) { takeCount--; yield return item; } } if (takeCount == 0) yield break; } }
      
      







そのため、利回りが役立ちました。 そして、かなりの量のコードが判明しました。 私はそれが好きで、あなたは?



PSそして、それだけではありません。この記事は意図的に書かれています。 Sliceメソッドは、強力で機能的なAeroフレームワークライブラリの構文糖のごく一部であり、高度なレベルの抽象化と概念を非常に細かく調整しているため、研究の適切な忍耐により、このハーモニーはあなたを驚かせるでしょう...幸運なことに、今は人生で書いている。 そして、 循環スライスとシフトに関するこの記事単なる誘惑です! Aeroフレームワークを探索してください。間違いなく感謝します。



All Articles