Sorting a list of objects based on another
public class Product
{
public string Code { get; private set; }
public Product(string code)
{
Code = code;
}
}
List<Product> sourceProductsOrder =
new List<Product>() { new Product("BBB"), new Product("QQQ"),
new Product("FFF"), new Product("HHH"),
new Product("PPP"), new Product("ZZZ")};
List<Product> products =
new List<Product>() { new Product("ZZZ"), new Product("BBB"),
new Product("HHH")};
I have two product lists and I want to reorder the second with the same order as the first. How can I reorder the product list so that the result is: "BBB", "HHH", "ZZZ"?
EDIT: Changed code property to public as mentioned by @juharr
source to share
You would use IndexOf
:
var sourceCodes = sourceProductsOrder.Select(s => s.Code).ToList();
products = products.OrderBy(p => sourceCodes.IndexOf(p.Code));
The only catch in this case is if there is something in the second list not in the first list, then they will go to the beginning of the second list.
The MSDN post on IndexOf
can be found here .
source to share
You can try something like this
products.OrderBy(p => sourceProductsOrder.IndexOf(p))
if it's the same object Product
. Otherwise, you can try something like:
products.OrderBy(p => GetIndex(sourceProductsOrder, p))
and write a little helper method GetIndex
. Or create an extension method Index()
for List<>
which will give
products.OrderBy(p => sourceProductsOrder.Index(p))
The method is GetIndex
pretty simple, so I am omitting it here.
(I don't have a PC to run the code, so please excuse the small bugs)
source to share
Here's an efficient way to do it:
var lookup = sourceProductsOrder.Select((p, i) => new { p.Code, i })
.ToDictionary(x => x.Code, x => x.i);
products = products.OrderBy(p => lookup[p.Code]).ToList();
This should have a runtime complexity of O (N log N), whereas the approach using IndexOf()
would be O (N 2 ).
This assumes the following:
- in
sourceProductsOrder
no recurring product codes. -
sourceProductsOrder
contains all product codes in products - you are creating
Code
field / property not private
If necessary, you can create protection against the first bullet by replacing it with the following expression:
var lookup = sourceProductsOrder.GroupBy(p => p.Code)
.Select((g, i) => new { g.Key, i })
.ToDictionary(x => x.Key, x => x.i);
You can specify a second brand by replacing the second statement with the following:
products = products.OrderBy(p =>
lookup.ContainsKey(p.Code) ? lookup[p.Code] : Int32.MaxValue).ToList();
And you can use both if you need to. This will slow down the algorithm a bit, but it should continue to run at O โโ(N log N) even with these changes.
source to share
I would implement a compare function that searches the order of sourceProductsOrder
from using a hash table. The lookup table would look like
(key) : (value)
"BBB" : 1
"QQQ" : 2
"FFF" : 3
"HHH" : 4
"PPP" : 5
"ZZZ" : 6
Then you can compare the order of the two elements and make it simple <
(pseudocode):
int compareFunction(Product a, Product b){
return lookupTable[a] < lookupTable[b]
}
Building a hash table would be linear, and doing a view would normally be nlogn
source to share
Easy transition:
IEnumerable<Product> result =
products.OrderBy(p => sourceProductsOrder.IndexOf(sourceProductsOrder.FirstOrDefault(p2 => p2.Code == p.Code)));
This will give the desired result. Objects with ProductCodes that are not available in the source list will be placed at the beginning of the result set. This will work fine for a few hundred subjects I guess.
If you have to deal with thousands of objects than an answer like @ Jon's will likely work better. There you first create a search / grade view for each item and then use it to sort / order.
The approach I described is O (n2).
source to share