Sorting iEnumerable with external iEnumerable - c#

I've been trying to sort a iEnumerable using another iEnumerable as a point of reference.
My first iEnumerable "combinations" (the one which i'd like to sort) holds 67 items
but the only important property of these items is InventSizeName.
My 2nd iEnumerable "sizes" holds 5 items and the items looks like this
Id
Name
SortOrder
What I would like to do is to sort combinations by using sizes.SortOrder where sizes.Name == combinations.InventSizeName.
The closest I've been is
var sorted = combinations
.Zip(sizes, (c, s) => new { com = c, siz = s })
.OrderBy(v => v.siz.Order)
.Select(v => v.com)
.ToList();
This however doesn't compare the properties and set the correct order (obviously) and it gives me a List with only 5 entries.
I'm sorry that this question is badly written but I hope anyone of you guys could help me out here.

I think the easiest way is to join the two IEnumerables
var sorted = from c in combinations
join s in sizes on c.Name equals s.InventSizeName
orderby s.Order
select c;

Related

How to order list based on some id found in another list c#?

I know there are plenty of question about this topic but none of them seems to solve my answer (or at least from what i have found) pardon me if this is a duplicate question.
I have a list that i gather from SQL containing two properties SequenceId and Relevant:
var sequenceList = await context.SequenceDB.Where(c => c.PeopleId == peopleId).Select(c => {
SequenceId = c.SequenceId,
Relevant = c.Relevant
}).OrderBy(c => c.Relevant).ToListAsync();
Then i have another list like so:
var secondList = await context.Activity.ToListAsync();
FYI
the second list has multiple properties (hence column in the database) and one of them is SequenceId pointing to that SequenceId in SequenceDB.
What i want is to order the secondList based on the order of GUID's in the sequenceList.
BUT:
I just need to order them NOT exclude them from the list. And i don't want to exclude any of the elements from secondList
The result will be a list of Activity with as first elements the ones from sequenceList and then the rest
If you think this is a duplicate question please point me to the right one and i'll delete this one.
It seems simple even though is not for me.
You can join the lists using an outer join, so something like this should work.
First, number each row in secondList so we can retain the order for items which don't match those in the sequenceList.
var indexedSecondList = secondList.Select((e, index) => new { e, index });
(from r in indexedSecondList
join s in sequenceList on r.e.SequenceId equals s.SequenceId into tmp
from t in tmp.DefaultIfEmpty()
orderby t != null ? 0 : 1 , // Sort the entries that are in SequenceList first
t != null ? t.Relevant : (System.Guid?) null , // Then sort by Relevant
r.index // Finally sort by original order in secondList
select r.e).ToList();

c# IEnumerable<Object> LINQ GroupBy - Merge 2 Groups

I'm working on some LINQ GroupBy logic and I can't think how to elegantly and efficiently get this to work.
Basically, I have an IEnumerable<Thing> object (which is in the correct order!), where each Thing object has a RootId property. I want to group these objects on their RootId, which I have working:
IEnumerable<Thing> things; // Already has value assigned
var groups =
(from thing in things
group thing by thing.RootId into thingGroup
select thingGroup.ToArray())
.ToList();
groups is of type List<Thing[]>
Now here is the problem!
The above example is returning 5 items in the list. But, how would I merge 2 of the arrays into 1, leaving 4 items (again, keeping the order of course)??
The reason why is because 2 of the items has different RootId's but I want them to be treated the same i.e. grouped together.
I was going to concat and manipulate the arrays after the LINQ statement, but really it needs to be done as part of the group by/LINQ - any ideas?
Let me know if further examples or information is needed.
Thanks!
The merging criteria will be a manual process, so I was thinking of passing it into the groupby method like so:
var rootIdsToMerge = new List<Tuple<ID, ID>>
{
new Tuple<ID, ID>(rootIdOne, rootIdTwo),
new Tuple<ID, ID>(rootIdThree, rootIdFour)
};
So any group item with a RootId of rootIdOne will be merged with the group item with a RootId of rootIdTwo, and so on.
Since you are not using the grouping Key, you can associate the Item2 from the mapping to Item1 as a RootId key to group by:
var groups =
(from thing in things
group thing by rootIdsToMerge.FirstOrDefault(e => e.Item2 == thing.RootId)?.Item1 ?? thing.RootId
into thingGroup
select thingGroup.ToArray())
.ToList();
Or in pre C#6 (no .? operator):
var groups =
(from thing in things
let mergeWith = rootIdsToMerge.FirstOrDefault(e => e.Item2 == thing.RootId)
group thing by mergeWith != null ? mergeWith.Item1 : thing.RootId
into thingGroup
select thingGroup.ToArray())
.ToList();
Update: If you just want to consolidate a list of RootIds, then you can use a combination of Contains and First:
List<ID> rootIdsToMerge = ...;
var groups =
(from thing in things
group thing by rootIdsToMerge.Contains(thing.RootId) ? rootIdsToMerge.First() : thing.RootId
into thingGroup
select thingGroup.ToArray())
.ToList();
The variants with
List<List<ID>> rootIdsToMerge = ...;
are similar to the initial variant with tuples:
var groups =
(from thing in things
group thing by rootIdsToMerge.FirstOrDefault(ids => ids.Contains(thing.RootId))?.First() ?? thing.RootId
into thingGroup
select thingGroup.ToArray())
.ToList();
or
var groups =
(from thing in things
let mergeList = rootIdsToMerge.FirstOrDefault(ids => ids.Contains(thing.RootId))
group thing by mergeList != null ? mergeList.First() : thing.RootId
into thingGroup
select thingGroup.ToArray())
.ToList();

Get objects which are in both lists based on specific comparator [duplicate]

This question already has answers here:
find common items across multiple lists in C#
(8 answers)
Closed 6 years ago.
I have two List objects, listA and listB, which contain a collection of User objects.
Each User object has a property ID.
I want to get a list of users which are present in both listA and listB based on their ID property.
Here is my code so far:
listA.Where(a => listB.Any(b => b.ID == a.ID));
Is there a better way to do this? It feels like it could be inefficient, especially if listB is large.
The User object does not implement IEquatable.
If User implements IEquatable<User>, assumption is that two User are the same if ID are the same, then you can use LINQ Intersect. For example:
listA.Intersect(listB);
If User does not implement IEquatable<User> you can invoke Intersect with IEqualityComparer<User>. For Example:
listA.Intersect(listB, new UserEqualityComparer());
where
UserEqualityComparer : IEquatable<User> {...}
Another option is to Join on Id property of both collections..
var results = listA.Join(listB, a => a.Id, b=> b.Id, (a, b) => a);
This should do it:
var result = listA.Intersect(listB);
Assuming that your User class implements the IEquatable<User>
if this is not your case, then you can do something like that n order to intersect both lists based on the ID value:
var result = listA.Select(s1 => s1.ID).ToList().Intersect(listB.Select(s2 => s2.ID).ToList()).ToList();
This is creating two new lists of the ID values in both lists, and get the intersection between them.

Sort one list by another

I have 2 list objects, one is just a list of ints, the other is a list of objects but the objects has an ID property.
What i want to do is sort the list of objects by its ID in the same sort order as the list of ints.
Ive been playing around for a while now trying to get it working, so far no joy,
Here is what i have so far...
//**************************
//*** Randomize the list ***
//**************************
if (Session["SearchResultsOrder"] != null)
{
// save the session as a int list
List<int> IDList = new List<int>((List<int>)Session["SearchResultsOrder"]);
// the saved list session exists, make sure the list is orded by this
foreach(var i in IDList)
{
SearchData.ReturnedSearchedMembers.OrderBy(x => x.ID == i);
}
}
else
{
// before any sorts randomize the results - this mixes it up a bit as before it would order the results by member registration date
List<Member> RandomList = new List<Member>(SearchData.ReturnedSearchedMembers);
SearchData.ReturnedSearchedMembers = GloballyAvailableMethods.RandomizeGenericList<Member>(RandomList, RandomList.Count).ToList();
// save the order of these results so they can be restored back during postback
List<int> SearchResultsOrder = new List<int>();
SearchData.ReturnedSearchedMembers.ForEach(x => SearchResultsOrder.Add(x.ID));
Session["SearchResultsOrder"] = SearchResultsOrder;
}
The whole point of this is so when a user searches for members, initially they display in a random order, then if they click page 2, they remain in that order and the next 20 results display.
I have been reading about the ICompare i can use as a parameter in the Linq.OrderBy clause, but i can’t find any simple examples.
I’m hoping for an elegant, very simple LINQ style solution, well I can always hope.
Any help is most appreciated.
Another LINQ-approach:
var orderedByIDList = from i in ids
join o in objectsWithIDs
on i equals o.ID
select o;
One way of doing it:
List<int> order = ....;
List<Item> items = ....;
Dictionary<int,Item> d = items.ToDictionary(x => x.ID);
List<Item> ordered = order.Select(i => d[i]).ToList();
Not an answer to this exact question, but if you have two arrays, there is an overload of Array.Sort that takes the array to sort, and an array to use as the 'key'
https://msdn.microsoft.com/en-us/library/85y6y2d3.aspx
Array.Sort Method (Array, Array)
Sorts a pair of one-dimensional Array objects (one contains the keys
and the other contains the corresponding items) based on the keys in
the first Array using the IComparable implementation of each key.
Join is the best candidate if you want to match on the exact integer (if no match is found you get an empty sequence). If you want to merely get the sort order of the other list (and provided the number of elements in both lists are equal), you can use Zip.
var result = objects.Zip(ints, (o, i) => new { o, i})
.OrderBy(x => x.i)
.Select(x => x.o);
Pretty readable.
Here is an extension method which encapsulates Simon D.'s response for lists of any type.
public static IEnumerable<TResult> SortBy<TResult, TKey>(this IEnumerable<TResult> sortItems,
IEnumerable<TKey> sortKeys,
Func<TResult, TKey> matchFunc)
{
return sortKeys.Join(sortItems,
k => k,
matchFunc,
(k, i) => i);
}
Usage is something like:
var sorted = toSort.SortBy(sortKeys, i => i.Key);
One possible solution:
myList = myList.OrderBy(x => Ids.IndexOf(x.Id)).ToList();
Note: use this if you working with In-Memory lists, doesn't work for IQueryable type, as IQueryable does not contain a definition for IndexOf
docs = docs.OrderBy(d => docsIds.IndexOf(d.Id)).ToList();

LINQ group items. A single item may be in several groups

I have an IEnumerable of items that I would like to group by associated categories. The items are grouped by the categories that are associated with them - which is a List - so a single item can potentially be a part of multiple categories.
var categories = numbers.SelectMany(x => x.Categories).Distinct();
var query =
from cat in categories
select new {Key = cat,
Values = numbers.Where(n => n.Categories.Contains(cat))};
I use the above code, and it does in fact work, but I was wondering if there was a more efficient way of doing this because this operation will likely perform slowly when numbers contains thousands of values.
I am pretty much asking for a refactoring of the code to be more efficient.
You can use LINQ's built-in grouping capabilities, which should be faster than a contains lookup. However, as with any performance-related question, you should really write code to collect performance metrics before deciding how to rewrite code that you know works. It may turn out that there's no performance problem at all for the volumes you will be working with.
So, here's the code. This isn't tested, but something like it should work:
var result = from n in numbers
from c in n.Categories
select new {Key = c, n.Value}
into x group x by x.Key into g
select g;
Each group contains a key and a sequence of values that belong to that key:
foreach( var group in result )
{
Console.WriteLine( group.Key );
foreach( var value in group )
Console.WriteLine( value );
}

Categories