Arnis Lapsa
Arnis Lapsa

Reputation: 47677

Custom sorting with LINQ

It seems that i'm missing something trivial.

Anyway, here it goes:

var order = new[]{1,3,2};
var foos = new[]{new Foo{Id=1}, new Foo{Id=2}, new Foo{Id=3}};

How to sort foos by order array using Linq?

Desired result:

foos == new[]{new Foo{Id=1}, new Foo{Id=3}, new Foo{Id=2}};

Edit:
Order contains Foo ids. Sorry that i didn't mention that. Sometimes it's even harder to ask question properly than to answer it. :)

Upvotes: 2

Views: 1532

Answers (6)

Daniel Brückner
Daniel Brückner

Reputation: 59705

You can do this using a nested query, but it is quite inefficient with O(n²).

var result = order.Select(o => foos.Single(f => f.Id == o));

If 'order' may contain ids not present in 'foos', you should use SingleOrDefault(). If foos might contain duplicate ids, you should use First() or FirstOrDefault().

var result = order
    .Select(o => foos.FirstOrDefault(f => f.Id == o))
    .Select(f => f != null);

Maybe even a join will work, but I am not sure if it preserves the order.

var result = Enumerable.Join(order, foos, o => o, f => f.Id, (o, f) => f);

As Jon mentioned, the join will only work correctly if the input is well formed in the same way as required by my first suggestion.

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1503859

Okay, the question doesn't seem to be entirely clear to me, so I'll try to clarify what I think you're asking:

  • You have a sequence of IDs, in the desired order
  • You have a collection of objects, not in the right order, but with corresponding IDs
  • You want to get the collection of objects in the same order as the ID sequence

Correct?

That would be:

var orderedFoos = from orderedId in order
                  join foo in foos on orderedId equals foo.Id into groups
                  select groups.Single();

You need a join ... into to verify that you don't have any missing or duplicate IDs in foos. It won't, however, detect if you've got missing or duplicate IDs in order. If you know that everything will be correct (i.e. there will be exactly one entry in foos for every entry in order and vice versa) then a simple join is okay:

var orderedFoos = from orderedId in order
                  join foo in foos on orderedId equals foo.Id
                  select foo;

which can be expressed in dot notation as:

var orderedFoos = order.Join(foos, order => order, foo => foo.ID, (o, f) => f);

Upvotes: 3

tvanfosson
tvanfosson

Reputation: 532745

I'd probably use a Dictionary<int,int> of id/ordering pairs to make the order lookup O(1) if I had a lot of these to do. Note that you would also need to handle cases where your values are missing from the ordering -- I chose to move them to the end.

var order = new Dictionary<int,int>();
order.Add( 1, 1 );
order.Add( 2, 3 );
order.Add( 3, 2 );

var orderedFoos = foos.OrderBy( f => order.Contains(f.Id) ? order[f.Id] : int.MaxValue );

Upvotes: 1

Dale
Dale

Reputation: 13044

Is this what you are trying to do?

foos.OrderBy(f => order[f.Id-1]);

If you foreach now on the output of that, printing the ID, you get: 1,3,2

Upvotes: 4

gsharp
gsharp

Reputation: 27937

var order = new[] { 1, 3, 2 };
var foos = new[] { new Foo { Id = 1 }, new Foo { Id = 2 }, new Foo { Id = 3 } };

var query = from o in order
            join foo in foos on o equals foo.Id
            select foo;

var foos2 = query.ToArray();

Upvotes: 0

erikkallen
erikkallen

Reputation: 34421

from o in order.Select((o, i) => new { o, i })
join f in foos on o.o equals f.Id
orderby o.i
select f;

Upvotes: 3

Related Questions