Reputation: 1071
I have a set of data which I'd like to re-order starting with a specific number and then, when the highest number is reached, go back to the lowest and then carry on incrementing.
For example, for the sequence (1,2,3,4,5,6), if 4 was the specific number, the order would become (4,5,6,1,2,3).
Is that possible with linq & c#?
Upvotes: 20
Views: 4857
Reputation: 460098
If your data is a List<T>
this works:
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);
}
That's not really ordering but creating a new list.
Upvotes: 0
Reputation: 158021
Extension method to shift a sequence to start at a given item. This will also only go through the original sequence once, which may or may not be important. This also assumes the sequence is already sorted the way you want it, except for the shifting.
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();
}
Usage
var list = new List<int>() { 1, 2, 3, 4, 5, 6 };
foreach (var i in list.Shift(4))
Console.WriteLine(i);
Prints
4
5
6
1
2
3
Upvotes: 1
Reputation: 43523
int specific = 4;
var numbers = Enumerable.Range(1, 9);
var result = numbers.OrderBy(n => Tuple.Create(n < speficic, n)).ToList();
I use a little trick here, using Tuple<bool, int>
as the comparer, since false < true
. An alternative is:
var result = numbers.OrderBy(n => n < speficic).ThenBy(n => n).ToList();
EDIT after a benchmark I found the second solution .OrderBy .ThenBy
is much faster than the Tuple
solution. I believe it's because the FCL uses Comparer<T>.Default
as the comparer, which costs time in constructing.
Upvotes: 11
Reputation: 50114
For the general case, the following is a custom IComparer
that should to this for any class.
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);
}
}
Called by
new[] { 1, 2, 3, 4, 5, 6 }.OrderBy(i => i, new StartWithComparer<int>(4))
Upvotes: 2
Reputation: 580
OrderBy()
is pretty powerful by itself, and for expanding its scope there's ThenBy()
so, in my opinion the cleaner way to do this is the following:
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);
Upvotes: 4
Reputation: 37192
You could implement a custom IComparer.
Something like the following (note code is not tested!):
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;
}
}
Upvotes: 3
Reputation: 12513
You could use (or abuse, I admit) a simple subtraction to accomplish this:
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);
Moreover, if numbers get bigger, be aware of integer overflows. For simple cases it may be fine though.
Here is a better solution (inspired by other answers):
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);
It sorts each sequence. before concatenating them. T_12 asked in a comment whether they are sorted ascending. If they are, go with L.B.'s solution rather than mine, since the OrderBy
blows the effort to at least O(n log n)
instead of O(n)
(linear).
Upvotes: 1
Reputation: 75306
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();
Upvotes: 2
Reputation: 171178
I'll propose a heretic solution here because it is not using the standard LINQ operators at all:
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];
}
I think this shows the intent quite clearly.
I don't think that the strange contortions you have to perform with the standard LINQ query operators (combos of Skip, Take, Concat) are readable or maintainable. I think it would be an abuse to use them in this case just for the sake of it. Loops are fine.
Upvotes: 1
Reputation: 18534
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);
Upvotes: 1
Reputation: 116108
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();
Upvotes: 28