tags:

views:

316

answers:

6

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

+3  A: 
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;
erikkallen
Why are you ordering by `o.i`? It will already be in that order.
Jon Skeet
Damage from SQL, I guess.
erikkallen
To elaborate: If I don't order, I have to know which of the sequences (if any) is order-preserved. It might be well defined for this operator in LINQ to objects, but if it were LINQ to SQL, it wouldn't be. Also, if I misread the documentation, I could introduce a hard-to-find bug by omitting the orderby (if I only tested it with 3 items, I assume a 1/6 chance of accidentally passing since I don't know the implementation details). By adding the orderby I get rid of these problems without hurting anyone.
erikkallen
erikkallen: "...without hurting anyone." Except performance implication of performing an unnecessary sort.
Mehrdad Afshari
+2  A: 

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);
Jon Skeet
I don't think this resolves the problem. I think the OP basically wants to grab `Foo`s that have the specific **Id** in a specific position.
Mehrdad Afshari
Sorry Skeet, can't use .NET 4.0
Arnis L.
@Arnis: That's why I linked to MoreLINQ - but I'd misunderstood the problem.
Jon Skeet
(I've now rewritten the answer using a GroupJoin.)
Jon Skeet
Yeah - question is correct. That's what i'm trying to do.
Arnis L.
A: 
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();
gsharp
+1  A: 

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 );
tvanfosson
Performance isn't an issue here and values should match (exception is fine if they somehow do not) but thanks. :)
Arnis L.
+4  A: 

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

Dale Halliwell
Some of these answers based on joins will be a lot more expensive over large sets that this little LINQ using a comparator.
Dale Halliwell
I think your answer is doing a different thing to all the other answers. It's also *more* expensive than the joins, which should be O(n) - your answer is O(n log n).
Jon Skeet
In terms of the differences of answer... you're assuming that `foos` is already in order, and will have ID 1, 2, 3, 4... with no gaps.
Jon Skeet
Really?? Order should use quicksort or something better, so yes, O(n log n), but intuition tells me a join (an equijoin, in the case of LINQ) has to be around O(n^2).. where did you get O(n) from?
Dale Halliwell
Yes it is making a few assumptions, the OP needs clarification
Dale Halliwell
@Dale: It will create a hash on the "inner" part then use that to match. Assuming we have a 1-1 correspondence (i.e. we don't have N entries all with the same key) and the hash is reasonable (i.e. lookup is O(1), creation is O(1) per item) then the result is O(N).
Jon Skeet
Thanks Jon, I did not know that!
Dale Halliwell
There can be gaps and will be for sure (my bad - forgot to add this in question). But thanks for idea anyway.
Arnis L.
+1  A: 

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.

Daniel Brückner
Joins do preserve order in LINQ to Objects... but you could end up with duplicate or missing rows.
Jon Skeet
"var result = order.Select(o => foos.Single(f => f.Id == o));" this is what Mehrdid posted at start (and deleted). Actually - it's a valid approach in this case because performance is not an issue here at all. Anyway - thanks.
Arnis L.