Linq sort with a twist - c#

I have the following records
The last 2 are children of record 3 and 4, I would like to be able to sort the records by amount but it should be that the non interest(parents) ones are sorted first then their children should show up after so for example it would be like this
2000
2000
20001
99.84 (child of the above)
50000
249.58 (child of the above)
Basically I would like my sort by amount to disregard the one with "IsInterest" set to true but make them show up after their parent.
I can do this by first taking all the parents into a new collection.. then go through the parent to see if there is any children then insert them after the parent in the new collection but I feel this is not efficient and dirty code so I thought I would ask maybe someone knows black magic.
The sort should also be aware of asc/desc on the amount.
I can post my code of ripping the collection apart and putting it together if it helps but I am trying not to use that code if possible.
My sort method takes a string for "ascending" or "descending" if that helps
Thank you
UPDATE2
I will point out that there is only ever going to be 2 levels, and that the children will ever only have one parent (no grand parents) and that each parent will have a maximum of 1 child
UPDATE code as requested (fields name may differ from the db fields..)
switch (sortMember.ToUpper())
{
case "AMOUNT":
{
//check to see if any imputed interests exist
if (contributions.Any(x => x.IsImputedInterest))
{
var children = contributions.Where(x => x.IsImputedInterest);
var sortedColl = contributions.Where(x => x.IsImputedInterest == false).OrderByWithDirection(x => x.ContributionAmount, sortDirection.ToUpper() == "DESCENDING").ToList();
foreach (var child in children )
{
//find the parent
var parentIndex = sortedColl.FindIndex(x => x.ContributionId == child.ParentContirbutionId);
sortedColl.Insert(parentIndex+1, child);
}
}
else
{
contributions = contributions.OrderByWithDirection(x => x.ContributionAmount, sortDirection.ToUpper() == "DESCENDING");
}
break;
}
}
.................
public static IOrderedEnumerable<TSource> OrderByWithDirection<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, bool descending)
{
return descending ? source.OrderByDescending(keySelector)
: source.OrderBy(keySelector);
}
public static IOrderedQueryable<TSource> OrderByWithDirection<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, bool descending)
{
return descending ? source.OrderByDescending(keySelector)
: source.OrderBy(keySelector);
}

Here's a single statement Linq solution:
var desc = order == "descending";
var result = list
//group parents with it's children
.GroupBy(x => x.ParentId ?? x.Id)
//move the parent to the first position in each group
.Select(g => g.OrderBy(x => x.ParentId.HasValue).ThenBy(x => desc ? -x.Amount : x.Amount))
//sort the groups by parents' amounts
.OrderBy(g => desc ? -g.First().Amount : g.First().Amount)
//retrieve the items from each group
.SelectMany(g => g);
Some performance hints:
You can drop the ThenBy(...) if there's always going to be at most one child or you don't care about children order
Use an if statement to check the order and have two versions of the statement - the second one using OrderByDescending/ThenByDescending, and drop the ternary operator (desc ? ... : ...) - otherwise it will be evaluated for each item
I'm not giving any guarantees on performance in relation to your current solution - it might as well turn out to be slower.

You can use the following generic method (not limited by levels or number of parent/children):
public static class Extensions
{
public static IEnumerable<T> ThenByHierarchy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> keySelector, Func<T, TKey> parentKeySelector)
{
var itemByKey = source.ToDictionary(keySelector);
var processSet = new HashSet<T>();
var stack = new Stack<T>();
foreach (var item in itemByKey.Values)
{
for (var next = item; processSet.Add(next); )
{
stack.Push(next);
if (!itemByKey.TryGetValue(parentKeySelector(next), out next)) break;
}
while (stack.Count != 0)
yield return stack.Pop();
}
}
}
Just append it at the end of your OrderBy sequence like this
var result = contributions
.OrderByWithDirection(x => x.ContributionAmount, sortDirection.ToUpper() == "DESCENDING")
.ThenByHierarchy(x => x.ContributionId, x => x.ParentContirbutionId);
UPDATE: It turns out that it's not so simple. While the method above provides a correct order for the leaf elements as well for the element to its parent, it does not order correctly the parents. The correct one is as follows (using another reusable method from here How to flatten tree via LINQ?, so if we don't count that it isn't really much bigger than the previous):
public static class Extensions
{
public static IEnumerable<T> HierarchicalOrder<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector, Func<T, TKey> parentKeySelector, Func<IEnumerable<T>, IOrderedEnumerable<T>> order)
{
// Collect parent/child relation info
var itemById = source.ToDictionary(keySelector);
var childListById = new Dictionary<TKey, List<T>>();
var rootList = new List<T>();
foreach (var item in itemById.Values)
{
var parentKey = parentKeySelector(item);
List<T> childList;
if (parentKey == null || !itemById.ContainsKey(parentKey))
childList = rootList;
else if (!childListById.TryGetValue(parentKey, out childList))
childListById.Add(parentKey, childList = new List<T>());
childList.Add(item);
}
// Traverse the tree using in-order DFT and applying the sort on each sublist
return order(rootList).Expand(item =>
{
List<T> childList;
return childListById.TryGetValue(keySelector(item), out childList) ? order(childList) : null;
});
}
public static IEnumerable<T> Expand<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
{
var stack = new Stack<IEnumerator<T>>();
var e = source.GetEnumerator();
try
{
while (true)
{
while (e.MoveNext())
{
var item = e.Current;
yield return item;
var elements = elementSelector(item);
if (elements == null) continue;
stack.Push(e);
e = elements.GetEnumerator();
}
if (stack.Count == 0) break;
e.Dispose();
e = stack.Pop();
}
}
finally
{
e.Dispose();
while (stack.Count != 0) stack.Pop().Dispose();
}
}
}
and the usage in your case is simple
var result = contributions
.HierarchicalOrder(x => x.ContributionId, x => x.ParentContirbutionId, c =>
.OrderByWithDirection(x => x.ContributionAmount, sortDirection.ToUpper() == "DESCENDING"));

Related

C# & LINQ, Select two (consecutive) items at once [duplicate]

This question already has answers here:
Linq to Objects - return pairs of numbers from list of numbers
(12 answers)
Closed 7 years ago.
Using LINQ on an ordered set (array, list), is there a way to select or otherwise use two consecutive items? I am imagining the syntax:
list.SelectTwo((x, y) => ...)
Where x and y are the items at index i and i + 1 in the list/array.
There may be no way to do this, which I accept as a possibility, but I would at least like to say I tried to find an answer.
I am aware that I could use something other and LINQ to achieve this.
Thank you in advance.
Another answer presents a nice and clean solution using LINQ's Skip and Zip.
It is absolutely correct, but I'd like to point out that it enumerates the source twice. That may or may not matter, depending on each individual use case. If it matters for your case, here's a longer alternative that is functionally equivalent but enumerates the source once:
static class EnumerableUtilities
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (selector == null) throw new ArgumentNullException(nameof(selector));
return SelectTwoImpl(source, selector);
}
private static IEnumerable<TResult> SelectTwoImpl<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
using (var iterator = source.GetEnumerator())
{
var item2 = default(TSource);
var i = 0;
while (iterator.MoveNext())
{
var item1 = item2;
item2 = iterator.Current;
i++;
if (i >= 2)
{
yield return selector(item1, item2);
}
}
}
}
}
Example:
var seq = new[] {"A", "B", "C", "D"}.SelectTwo((a, b) => a + b);
The resulting sequence contains "AB", "BC", "CD".
System.Linq.Enumerable.Zip combines two IEnumerables by pairing up the i-th element for each i. So you just need to Zip your list with a shifted version of it.
As a nice extension method:
using System.Collections.Generic;
using System.Linq;
static class ExtMethods
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
return Enumerable.Zip(source, source.Skip(1), selector);
}
}
Example:
Enumerable.Range(1,5).SelectTwo((a,b) => $"({a},{b})");
Results in:
(1,2) (2,3) (3,4) (4,5)
You can do
list.Skip(i).Take(2)
This will return an IEnumerable<T> with only the two consecutive items.
I think you can select item and next item data in an ordered list like this:
var theList = new List<T>();
theList
.Select((item, index) => new { CurrIndex = index, item.Prop1, item.Prop2, theList[index + 1].Prop1 })
.Where(newItem => {some condition on the item});
However, index of the selected items should be less than list size - 1.
If the source sequence has an indexer, i.e. at minimum is IReadOnlyList<T> (array, list as mentioned in the question), and the idea is to split the sequence on consecutive pairs (which is not quite clear from the question), then it can be done simply like this
var pairs = Enumerable.Range(0, list.Count / 2)
.Select(i => Tuple.Create(list[2 * i], list[2 * i + 1]));
You can use a special overload of Select that allows you to use the index of the item, and the GroupBy method to split the list into groups. Each group would have two items. Here is an extension method that does that:
public static class ExtensionMethods
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
return source.Select((item, index) => new {item, index})
.GroupBy(x => x.index/2)
.Select(g => g.Select(i => i.item).ToArray())
.Select(x => selector(x[0], x[1]));
}
}
And you can use it like this:
var list = new[] {1, 2, 3, 4, 5, 6};
var result = list.SelectTwo((x, y) => x + y).ToList();
This would return {3,7,11}
Please note that the above method groups the data in memory before starting to yield results. If you have large data sets, you might want to have a streaming approach (yield data as the data from the source is being enumerated), here is an example:
public static class ExtensionMethods
{
public static IEnumerable<TResult> SelectTwo<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
bool first_item_got = false;
TSource first_item = default(TSource);
foreach (var item in source)
{
if (first_item_got)
{
yield return selector(first_item, item);
}
else
{
first_item = item;
}
first_item_got = !first_item_got;
}
}
}

Find item in Enumerable with the maximum property value

I'm writing an entry for an AI competition in C#, and I'm looking for a more elegant way to search for items. (I'm much more familiar with embedded C programming, but I prefer C# for an AI contest.)
The contest server is using dmcs to compile entries, which is .Net framework 4.0; I'm using Visual Studio Express 2013 for my testing.
I'm trying to search for an item in a list with the maximum value of a parameter that also meets a certain prerequisite. I don't want the maximum value, though, I want the item that has said maximum value.
Here's my original code that does what I want using a foreach loop:
List<Region> myList = new List<Region>();
// ...
// myList gets populated with elements
// ...
Region biggest = null;
int biggestSize = -1;
foreach (Region r in myList)
{
// We only want elements that are eligible for expansion
if (r.EligibleForExpansion())
{
if (r.Size > biggestSize)
{
biggest = r;
biggestSize = r.Size;
}
}
}
return biggest; // I want the biggest Region, not the Size of the biggest region.
I'm trying to find a more elegant way to do this so I don't have foreach loops all over my code. I tried this:
return myList.Max(delegate(Region r) { if (r.EligibleForExpansion()) return r.Size; else return -1; });
However, that returns the Size value of the largest region, not the largest Region itself (which is what I need).
I know that my foreach code will return null if no Region meets the requirement while the Max code will give -1 (or any Region that doesn't meet the requirement); I can deal with either way.
I don't think I can just make Region IComparable, though; I have many searches for Region objects, and I need to sort by different parameters at different times, so the comparison function would be different in different searches.
I could just wrap my foreach code in a static function and call that wherever I need to search, but it seems like there should be a more elegant way to do this in C#.
Use MaxBy from moreLINQ library:
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> selector)
{
return source.MaxBy(selector, Comparer<TKey>.Default);
}
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
if (comparer == null) throw new ArgumentNullException("comparer");
using (var sourceIterator = source.GetEnumerator())
{
if (!sourceIterator.MoveNext())
{
throw new InvalidOperationException("Sequence contains no elements");
}
var max = sourceIterator.Current;
var maxKey = selector(max);
while (sourceIterator.MoveNext())
{
var candidate = sourceIterator.Current;
var candidateProjected = selector(candidate);
if (comparer.Compare(candidateProjected, maxKey) > 0)
{
max = candidate;
maxKey = candidateProjected;
}
}
return max;
}
}
like that:
var item = myList.Where(x => x.EligibleForExpansion())
.MaxBy(x => x.Size);
How about this?
myList.Where(r => r.EligibleForExpansion).OrderBy(r => r.Size).LastOrDefault()
You can use Aggregate out of the box for this purpose:
var item = myList
.Where(r => r.EligibleForExpansion())
.Aggregate((Region)null, (max, cur) => (max == null ? cur : cur.Size > max.Size ? cur : max));
If Region were a value type (which it isn't) you could wrap the initial value in a nullable, and get a null value for an empty list:
var item = myList
.Where(r => r.EligibleForExpansion())
.Aggregate((Region?)null, (max, cur) => (max == null ? cur : cur.Size > max.Value.Size ? cur : max));

How do I select everything after the last instance of?

I asked yesterday how to select everything after the first instance of a flag in a collection and the answer was to use SkipWhile which work great. But now the logic has changed, and I need a way to Select the last instance of and everything after it.
A bit more detail:
The list contains an ordered list with a number of configurations, and each has a flag called IsTop. What I need to do is find the last instance of IsTop == true, grab that and everything after it.
Can this be done in LINQ or do I have to ToArray() it and do it by hand, so to speak?
You can use Reverse to handle this, and swap out SkipWhile for TakeWhile.
var query = sequence.Reverse()
.TakeWhile(item => !item.IsTop)
.Reverse(); //to get back in the original order; remove if not needed
Unfortunately, the above method doesn't include the last item where IsTop is true, to do so is a tad more complex, and the "easiest" methods of doing so would involve iterating the sequence several times, as such it should really only be used on a List, Array, or other data structure that can access items by index (i.e., an IList). Here is a method that would be able to handle it:
public static IEnumerable<T> Foo<T>(IList<T> data, Func<T, bool> isDivisor)
{
int itemsToTake = data.Reverse()
.TakeWhile(isDivisor)
.Count() + 1;
return data.Skip(data.Count - itemsToTake);
}
Another approach that is more "proper", relies on a helper method. This method will group items while a predicate indicates it should be. If the predicate returns true it is added to the "current group", if it's false, the previous group is "done" and a new group is started. This helper method is as follows:
public static IEnumerable<IEnumerable<T>> GroupWhile<T>(
this IEnumerable<T> source, Func<T, bool> predicate)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
List<T> list = new List<T>() { iterator.Current };
while (iterator.MoveNext())
{
if (predicate(iterator.Current))
{
list.Add(iterator.Current);
}
else
{
yield return list;
list = new List<T>() { iterator.Current };
}
}
yield return list;
}
}
Using this it's actually rather straightforward:
var query = sequence.GroupWhile(item => !item.IsTop)
.Last();
Conceptually this models what we're doing the best. We're creating groups in which each group goes from one IsTop item to the next, and then we just want the last group (or the first group, for your other question).
You can write your own simple extension method to do this:
// takes items until the first one where predicate is true;
// includes the first item where predicate is true
public static IEnumerable<TSource> TakeUntil<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
{
foreach (var item in source)
{
yield return item;
if (predicate(item))
break;
}
}
public static IEnumerable<TSource> TakeLastUntil<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
{
return source.Reverse().TakeUntil(predicate).Reverse();
}
Use like:
var myList = new[]
{
new { IsTop = false, S = 'a' },
new { IsTop = true, S = 'b' },
new { IsTop = false, S = 'c' },
new { IsTop = true, S = 'd' },
new { IsTop = false, S = 'e' },
}.ToList();
myList.TakeLastUntil(x => x.IsTop); // has d and e
This might iterate the list more than is necessary. If that's a problem (e.g. because you have a very long list) and you are working with some sort of IList<T> instead of just an IEnumerable<T>, it should be possible to write these methods more efficiently for IList<T>.

linq way to insert element in order

i have a collection of elements sorted by the elements' Name property. i need to insert a new element into the collection while maintaining the order. i am looking for a concise LINQ way to do this. my code is below. "this.Children" is the collection, and "d" is the new element that i need to insert. it takes two passes over the collection to find the insertion point. is there a way to get the index from the First() extension method? (please do not suggest using foreach, i know that :), i am learning LINQ).
thanks!
konstantin
var v = this.Children.FirstOrDefault(x => string.Compare(x.Name, d.Name) > 0);
int index = this.Children.IndexOf(v);
if (index < 0)
{
this.children.Add(d);
}
else
{
this.Children.Insert(index, d);
}
Yes, using the overload of Select which includes the index as well as the value:
var pair = this.Children
.Select((value, index) => new { value, index })
.FirstOrDefault(x => string.Compare(x.value.Name, d.Name) > 0);
if (pair == null)
{
Children.Add(d);
}
else
{
Children.Insert(pair.index, d);
}
Note that this is still inefficient though - if you already know the values are sorted, you can use a binary chop to find out the insertion index. It's hard to give sample code for that without knowing the type of Children though... there's already List<T>.BinarySearch and Array.BinarySearch.
Learning LINQ is admirable - but it's also important to learn when using LINQ isn't the best way to go :)
Assuming that this.Children is a List<T>, you can use List<T>.BinarySearch with a custom comparer to efficiently find the position to insert the new element at:
IComparer<Foo> comparer = AnonymousComparer.Create<Foo>(
(x, y) => string.Compare(x.Name, y.Name));
int index = this.Children.BinarySearch(d, comparer);
if (index < 0) index = ~index;
this.Children.Insert(index, d);
with
static class AnonymousComparer
{
public static IComparer<T> Create<T>(Func<T, T, int> comparer)
{
if (comparer == null) { throw new ArgumentNullException("comparer"); }
return new TheComparer<T>(comparer);
}
private class TheComparer<T> : IComparer<T>
{
private readonly Func<T, T, int> c;
public TheComparer(Func<T, T, int> c) { this.c = c; }
int IComparer<T>.Compare(T x, T y) { return this.c(x, y); }
}
}
I created my own Extension method for adding a new item in the correct order:
public static class ListExtension
{
public static void InsertOrderedBy<TSource, TKey>(this IList<TSource> source, TSource item, Func<TSource, TKey> keySelector) where TKey : IComparable<TKey>
{
var i = source.Select((Value, Index) => new { Value, Index }).FirstOrDefault(x => keySelector(x.Value).CompareTo(keySelector(item)) > 0);
if (i == null)
{
source.Add(item);
}
else
{
source.Insert(i.Index, item);
}
}
}
I use it like this:
List<Item> ItemList = new List<Item>();
ItemList.InsertOrderedBy(item, x => x.Duration);
It's almost the same like the answer from Jon Skeet, but I can pass the sort argument as second parameter, e.g. a Duration (type TimeSpan).
Well, you could always just use OrderBy after adding the new element...
var v = this.Children.Union(new List<TypeOfChildren>() { d }).OrderBy<TypeOfChildren, string>(x => x.Name).ToList<TypeOfChildren>();

Question about length of an object in LINQ

I have a List. I want to print the longest address.
Pseudocode
foreach (var i in listAddr)
{
// access listAddr.Address.Length
// print longest address
// print shortest address
}
Very roughly, with no foreach:
var sortedAddr = listAddr.OrderBy(x => x.Address.Length);
var longestAddr = sortedAddr.Last();
var shortedAddr = sortedAddr.First();
As Jon said, this has O(n log n) complexity.
But could be reasonable if you don't have extreme performance need.
EDIT:
If you have a lot of same-length addresses you can do this:
var sortedGroups = listAddr.GroupBy(x => x.Address.Length).OrderBy(x => x.Key);
var longestAddresses = sortedGroups.Last();
var shortestAddresses = sortedGroups.First();
// just print iterating over longestAddresses and shortestAddresses ...
It sounds like you want MaxBy and MinBy functionality, e.g.
var maxEntry = listAddr.MaxBy(entry => entry.Address.Length);
Console.WriteLine(maxEntry.Address);
var minEntry = listAddr.MinBy(entry => entry.Address.Length);
Console.WriteLine(minEntry.Address);
Unfortunately there's nothing like this in plain LINQ to Objects, but we have an implementation in MoreLINQ and I believe Reactive Extensions has one in System.Interactive too.
Obviously you can sort by address size descending and then take the first result... that will be O(n log n) instead of O(n) complexity... that may well be fine in most cases. It feels inelegant to me though :)
Code from the MoreLINQ implementation of MaxBy (without comments :)
public static TSource MaxBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> selector)
{
return source.MaxBy(selector, Comparer<TKey>.Default);
}
public static TSource MaxBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
source.ThrowIfNull("source");
selector.ThrowIfNull("selector");
comparer.ThrowIfNull("comparer");
using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
{
if (!sourceIterator.MoveNext())
{
throw new InvalidOperationException("Sequence was empty");
}
TSource max = sourceIterator.Current;
TKey maxKey = selector(max);
while (sourceIterator.MoveNext())
{
TSource candidate = sourceIterator.Current;
TKey candidateProjected = selector(candidate);
if (comparer.Compare(candidateProjected, maxKey) > 0)
{
max = candidate;
maxKey = candidateProjected;
}
}
return max;
}
}
}
This should work list.Max(x => x.Address) and list.Min(x => x.Address)
For ex, if you have a list like that
List<int> myList = new List<int>();
you are able to use myList.Max() and myList.Min() to get max and min values

Categories