Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sort based on ordering

Tags:

c#

.net

lambda

Lets say I have items

items : [{id:1,...}, {id:2,...}, {id:3,...}]

and have ordering : [2, 3, 1] to get an enumerable

items : [{id:2,...}, {id:3,...}, {id:1,...}]

I expect it to be something in the lines of

items.Select(o => new {key = ordering[i++], value = o})
     .OrderBy(k => k.key)
     .Select(o => o.value)

but is there a cleaner solution?


Following I have verified that work (HimBromBeere, Domysee, qxg)

var expectedOrder = ordering.Select(x => result.First(o => o.Id == x));
var expectedOrder = result.OrderBy(item => Array.FindIndex(ordering,i => i == item.Id));
var expectedOrder = result.OrderBy(item => ordering.ToList().FindIndex(i => i == item.Id));
var expectedOrder = 
  from o in ordering
  join i in result 
    on o equals i.Id
  select i;

Fwi, this was for verification test:

    [Test]
    [TestCase(1, 2, 3)]
    [TestCase(1, 3, 2)]
    [TestCase(2, 1, 3)]
    [TestCase(2, 3, 1)]
    [TestCase(3, 1, 2)]
    public void Test_Should_Fail_If_GetMessages_Does_Not_Return_Sorted_By_Sent_Then_By_Id_Result(params int[] ordering)
    {
        var questions = GetQuestionsData();                     
        Mock.Get(_questionService)
            .Setup(o => o.GetQuestions())
            .Returns(questions);
        var result = _mailboxService.GetMessages();    
        var expectedOrder = ordering.Select(x => result.First(o => o.Id == x));

        // Act
        Action sortOrder = () => expectedOrder.Should()
            .BeInDescendingOrder(o => o.Sent)
            .And.BeInDescendingOrder(o => o.Id);

        // Assert
        sortOrder.ShouldThrow<AssertionException>();
    }
like image 445
Margus Avatar asked Dec 11 '15 12:12

Margus


3 Answers

Something like this I guess:

var result = ordering.Select(x => items.First(y => y.id == x.id));

Working example:

var items = new[] { new { id = 1, name = "1" }, new { id = 2, name = "2" }, new { id = 3, name = "3" }, new { id = 4, name = "4" } };
var result = new[] { 2, 3, 1 }.Select(x => items.First(y => y.id == x));

This also filters those items out, whose index is not contained in ordering.

like image 139
MakePeaceGreatAgain Avatar answered Sep 30 '22 09:09

MakePeaceGreatAgain


If your IDs are consecutive you can rearrange the order array to contain for each index - the position the ID with the same index in the result array. This can be done like that:

int[] order = new[] {2, 3, 1};

order = Enumerable.Range(1, order.Length)
    .OrderBy(x => order[x - 1])
    .ToArray();

//Now order = { 3, 1, 2 } which are the one-based indices of each position in the original order array.

Now you can just order your Enumerable using that result array:

items = items.OrderBy(x => order[x.Id - 1]);

Note that if you would represent your order array this way in first place, the first Linq would be unnecessary, and this way you have no redundant iterations or sub-Linqs:

int[] order = new[] {3, 1, 2}; //1 is third, 2 is first, 3 is second.

items = items.OrderBy(x => order[x.Id - 1]);
like image 24
Tamir Vered Avatar answered Sep 30 '22 08:09

Tamir Vered


You could use the overload of Select that gives you the index instead

items.Select((o,i) => new {key = ordering[i+1], value = o})
     .OrderBy(k => k.key)
     .Select(o => o.value);

This will be better because it eliminates the captured i variable in your example.

like image 21
juharr Avatar answered Sep 30 '22 09:09

juharr