特定の番号から始めて並べ替えたい一連のデータがあり、最大の番号に達したら最小の番号に戻ってインクリメントを続けます。
たとえば、シーケンス (1,2,3,4,5,6) の場合、特定の数字が 4 の場合、順序は (4,5,6,1,2,3) になります。
それはlinqとc#で可能ですか?
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6 };
int num = 4;
var newList = list.SkipWhile(x=>x!=num)
.Concat(list.TakeWhile(x=>x!=num))
.ToList();
int specific = 4;
var numbers = Enumerable.Range(1, 9);
var result = numbers.OrderBy(n => Tuple.Create(n < speficic, n)).ToList();
ここではちょっとしたトリックを使っTuple<bool, int>
て、比較子として使用していますfalse < true
。別の方法は次のとおりです。
var result = numbers.OrderBy(n => n < speficic).ThenBy(n => n).ToList();
ベンチマーク後に編集.OrderBy .ThenBy
すると、2番目のソリューションがソリューションよりもはるかに高速であることがわかりましたTuple
。これは、FCL がコンペアラーComparer<T>.Default
として使用するため、構築に時間がかかるためだと思います。
OrderBy()
はそれ自体で非常に強力であり、その範囲を拡大するThenBy()
ためにそうです。私の意見では、これを行うためのよりクリーンな方法は次のとおりです。
var list = new[] {1, 2, 3, 4, 5, 6};
var pivot = 4;
var order = list.OrderBy(x => x == pivot ? 0 : 1).ThenBy(y => y < pivot ? 1: 0);
カスタムIComparerを実装できます。
次のようなもの (コードはテストされていないことに注意してください!):
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6 };
list.OrderBy(n => n, new IntComparer(4));
public class IntComparer : IComparer<int>
{
int start;
public IntComparer (int start)
{
this.start = start;
}
// Compares by Height, Length, and Width.
public int Compare(int x, int y)
{
if (x >= start && y < start)
// X is greater than Y
return 1;
else if (x < start && y >= start)
// Y is greater than X
return -1;
else if (x == y)
return 0;
else
return x > y ? 1 : -1;
}
}
一般的なケースでは、以下はどのクラスでもIComparer
これに必要な習慣です。
public class StartWithComparer<T> : IComparer<T>
{
private T startWith;
private IComparer<T> baseComparer = Comparer<T>.Default;
public StartWithComparer(T startWith, IComparer<T> baseComparer = null)
{
this.startWith = startWith;
if (baseComparer != null) this.baseComparer = baseComparer;
}
public int Compare(T x, T y)
{
int xToS = baseComparer.Compare(x, startWith);
int yToS = baseComparer.Compare(y, startWith);
if (xToS >= 0 && yToS < 0)
return -1;
else if (xToS < 0 && yToS >= 0)
return 1;
else
return baseComparer.Compare(x, y);
}
}
によって呼び出されます
new[] { 1, 2, 3, 4, 5, 6 }.OrderBy(i => i, new StartWithComparer<int>(4))
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6 };
item = 4;
var index = input.IndexOf(item);
var firstList = input.Take(index);
return input.Except(firstList)
.Concat(firstList)
.ToList();
特定のアイテムから開始するようにシーケンスをシフトする拡張メソッド。また、これは元のシーケンスを1回だけ実行しますが、これは重要な場合と重要でない場合があります。これはまた、シフトを除いて、シーケンスがすでに希望どおりにソートされていることを前提としています。
public static IEnumerable<T> Shift<T>(this IEnumerable<T> subject, T shouldBeFirst)
{
return subject.Shift(shouldBeFirst, EqualityComparer<T>.Default);
}
public static IEnumerable<T> Shift<T>(this IEnumerable<T> subject, T shouldBeFirst, IEqualityComparer<T> comparer)
{
var found = false;
var queue = new Queue<T>();
foreach (var item in subject)
{
if(!found)
found = comparer.Equals(item, shouldBeFirst);
if(found)
yield return item;
else
queue.Enqueue(item);
}
while(queue.Count > 0)
yield return queue.Dequeue();
}
使用法
var list = new List<int>() { 1, 2, 3, 4, 5, 6 };
foreach (var i in list.Shift(4))
Console.WriteLine(i);
プリント
4
5
6
1
2
3
List<int> list = new List<int>()
{
1,2,3,4,5,6
};
int number = 4;
int max = list.Max();
var result = list.OrderBy(i => i >= number ? i : max + i);
これを達成するために単純な減算を使用できます (または乱用することは認めます)。
var seq = Enumerable.Range(0, 10);
int n = 4;
int m = seq.Max() + 1; // or a magic number like 1000, thanks RB.
var ordered = seq.OrderBy(x => x >= n ? x - m : x);
foreach(int i in ordered)
Console.WriteLine(i);
さらに、数値が大きくなる場合は、整数オーバーフローに注意してください。ただし、単純なケースでは問題ない場合があります。
これがより良い解決策です(他の回答に触発されました):
var seq = Enumerable.Range(0, 10);
int n = 4;
var ordered = seq.Where(x => x >= n).OrderBy(x => x)
.Concat(seq.Where(x => x < n).OrderBy(x => x));
foreach(int i in ordered)
Console.WriteLine(i);
各シーケンスをソートします。それらを連結する前に。T_12 はコメントで、それらが昇順でソートされているかどうかを尋ねました。もしそうなら、少なくとも(線形)の代わりに努力を吹き飛ばすので、私のものではなくLBの解決策を使ってください。OrderBy
O(n log n)
O(n)
標準の LINQ 演算子をまったく使用していないため、ここで異端的な解決策を提案します。
IEnumerable<int> GetSequence(IList<int> input, int index) {
for (var i = index; i < input.Count; i++) yield return input[i];
for (var i = 0; i < index; i++) yield return input[i];
}
これはかなり明確に意図を示していると思います。
標準の LINQ クエリ演算子 (Skip、Take、Concat の組み合わせ) を使用して実行する必要がある奇妙なゆがみは、読み取り可能または保守可能ではないと思います。この場合、それのためだけにそれらを使用するのは乱用になると思います。ループは問題ありません。
データがList<T>
これで機能する場合:
var sequence = new[] { 1, 2, 3, 4, 5, 6 }.ToList();
List<int> result;
int start = 4;
int index = sequence.IndexOf(start);
if (index == 0)
result = sequence;
else if (index > -1)
{
result = sequence.GetRange(index, sequence.Count - index);
var secondPart = sequence.GetRange(0, sequence.Count - index);
result.AddRange(secondPart);
}
これは実際には順序付けではなく、新しいリストを作成することです。