20

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#?

Howard Shaw
  • 1,061
  • 1
  • 10
  • 18
  • 3
    your sequence is always ordered ascending? – tsionyx Sep 25 '12 at 09:04
  • possible duplicate of [OrderBy descending in Lambda expression?](http://stackoverflow.com/questions/1635497/orderby-descending-in-lambda-expression) – BugFinder Sep 25 '12 at 09:06
  • I missed the fact it was to a number and back, Im dyslexic (as my description says) I read it as descending – BugFinder Sep 25 '12 at 09:08
  • You can do some magic with an modulo operator (a % b) to calculate your index in your case it would be the following: i = ((i + 2) % 6) + 1 – TGlatzer Sep 25 '12 at 09:15
  • In your example, is 4 the index from which to start, or the element to start from? In the example sequence, both coincide. -- Oops, I misread - actually `4` has index 3. Never mind. – waldrumpus Sep 25 '12 at 09:15

11 Answers11

28
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();
L.B
  • 114,136
  • 19
  • 178
  • 224
11
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.

Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
  • @KSaini: Tuple implements `IComparable`, where compares T1 and T2 in sequence using their default comparers. It's as simple as its definition in msdn. – Cheng Chen Sep 25 '12 at 09:22
4

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);
specificityy
  • 580
  • 1
  • 5
  • 24
3

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;
    }
} 
RB.
  • 36,301
  • 12
  • 91
  • 131
  • If `x == start` and `y < start`, this will say `y < x` when it should be `y > x`. – Rawling Sep 25 '12 at 09:26
  • @Rawling Thanks. As I said, the code is not tested so there are likely to be edge cases (which unit-tests would catch ;)). I've updated the code to use a `>=` check on start. – RB. Sep 25 '12 at 09:28
  • I think you've nailed it now :) – Rawling Sep 25 '12 at 09:32
  • 1
    Actually I think your `1` and `-1` (in the `if` and first `else if`) are the wrong way around. But other than that I like your logic so much that I've stolen it. Muahaha. – Rawling Sep 25 '12 at 09:52
2
 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();
cuongle
  • 74,024
  • 28
  • 151
  • 206
2

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))
Rawling
  • 49,248
  • 7
  • 89
  • 127
  • I've compressed the code to shorten the post, but it _works_ in the general case. Don't downvote because it's _ugly_. – Rawling Sep 25 '12 at 09:30
  • Yeah, I noticed the code was bust too *eyeroll* I'm working on changing the wall of `if`s, not because of teh ugly but because it makes more comparisons than it should need to. – Rawling Sep 25 '12 at 09:47
1

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).

Community
  • 1
  • 1
Matthias Meid
  • 12,455
  • 7
  • 45
  • 79
  • 1
    Rather than using a magic number `1000`, you could use `seq.Max() + 1` instead. Naturally, you still have to worry about integer overflows. – RB. Sep 25 '12 at 09:18
1
        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);
horgh
  • 17,918
  • 22
  • 68
  • 123
1

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.

usr
  • 168,620
  • 35
  • 240
  • 369
  • The query posted by LB translates to "Take all the numbers greater than `n`, then concatenate all the numbers less than `n`". Surely that is a very straightforward way of expressing the programmers intent? – RB. Sep 25 '12 at 09:21
1

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
Svish
  • 152,914
  • 173
  • 462
  • 620
0

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.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939