Howard Shaw
Howard Shaw

Reputation: 1071

Linq orderby, start with specific number, then return to lowest

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

Answers (11)

Tim Schmelter
Tim Schmelter

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

Svish
Svish

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

Cheng Chen
Cheng Chen

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

Rawling
Rawling

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

specificityy
specificityy

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

RB.
RB.

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

Matthias Meid
Matthias Meid

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

cuongle
cuongle

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

usr
usr

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

horgh
horgh

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

L.B
L.B

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

Related Questions