How do I select everything after the last instance of? - c#

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

Related

Linq sort with a twist

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"));

How can I extract part of an IEnumerable based on known elements of the collection?

I have a collection, specifically an IList<T>. I know two elements within the collection, startElement and endElement.
Is there a LINQ query that will return the enumerable from startElement to endElement, inclusive?
I thought about using sequence.SkipWhile(p=>p!=startElement).TakeWhile(q=>q!=endElement) but that misses out the last element...
This doesn't use LINQ, but it's probably the most straightforward/readable approach.
int startIndex = sequence.IndexOf(startElement),
endIndex = sequence.IndexOf(endElement);
var range = sequence.GetRange(
startIndex,
// +1 to account for zero-based indexing
1 + endIndex - startIndex
);
Note that this is technically less efficient than alternatives, but if you already have an IList in memory, the differences will likely be less than a millisecond which is a small sacrifice to make for readable code.
I'd recommend wrapping the code block with a Stopwatch to test against your specific situation to be sure, however.
This will be the most efficient, as it doesn't create any unnecessary enumerator objects and only traverses the list one time.
var result = new List<T>();
var inSequence = false;
for (var i = 0; i < list.Length; i++)
{
var current = list[i];
if (current == startElement) inSequence = true;
if (!inSequence) continue;
result.add(current);
if (current == endElement) break;
}
This won't handle the case where endElement is missing, but you could do that pretty easily by assigning result = null as the last line of the for loop where i = list.Length - 1
George wrote a more flexible extension, you can find it in here:
https://stackoverflow.com/a/31940000/5106041
Old version:
public static class MyExtensions
{
public static IEnumerable <TData> InBetween <TData> (this IEnumerable <TData> Target, TData StartItem, TData EndItem)
{
var Comparer = EqualityComparer <TData>.Default;
var FetchData = false;
var StopIt = false;
foreach (var Item in Target) {
if (StopIt)
break;
if (Comparer.Equals (Item, StartItem))
FetchData = true;
if (Comparer.Equals (Item, EndItem))
StopIt = true;
if (FetchData)
yield return Item;
}
yield break;
}
}
So, now you can use it like this:
sequence.InBetween (startElement, endElement);
And it wont iterate the entire sequence.
Be aware that the are a lot of read made extensions in here http://linqlib.codeplex.com/
I assume you don't want to use extra memory and don't want to exceed the algorithmic complexity of the underlying iteration method, so ToList, GroupBy, IndexOf are not alowed in my proposed implementations.
Also, not to place constraints on the element type, i'm using predicates.
public static class EnumerableExtensions
{
/// <summary>
/// This one works using existing linq methods.
/// </summary>
public static IEnumerable<T> GetRange<T>(this IEnumerable<T> source, Func<T, bool> isStart, Func<T, bool> isStop)
{
var provideExtraItem = new[] { true, false };
return source
.SkipWhile(i => !isStart(i))
.SelectMany(i => provideExtraItem, (item, useThisOne) => new {item, useThisOne })
.TakeWhile(i => i.useThisOne || !isStop(i.item))
.Where(i => i.useThisOne)
.Select(i => i.item);
}
/// <summary>
/// This one is probably a bit faster.
/// </summary>
public static IEnumerable<T> GetRangeUsingIterator<T>(this IEnumerable<T> source, Func<T, bool> isStart, Func<T, bool> isStop)
{
using (var iterator = source.GetEnumerator())
{
while (iterator.MoveNext())
{
if (isStart(iterator.Current))
{
yield return iterator.Current;
break;
}
}
while (iterator.MoveNext())
{
yield return iterator.Current;
if (isStop(iterator.Current))
break;
}
}
}
}
These methods can be used as extension methods:
new[]{"apple", "orange", "banana", "pineapple"}.GetRange(i => i == "orange", i => i == "banana")
The best I can think of is:
var subSection = TestData.SkipWhile(p => p != startElement).ToList();
var result = subSection.Take(subSection.IndexOf(endElement) + 1);

LINQ Aggregate special attention for last element of list

I would like to pass in a different function to Aggregate for the last element in the collection.
A use for this would be:
List<string> listString = new List{"1", "2", "3"};
string joined = listString.Aggregate(new StringBuilder(),
(sb,s) => sb.Append(s).Append(", "),
(sb,s) => sb.Append(s)).ToString();
//joined => "1, 2, 3"
What would be a custom implementation if no other exists?
P.S. I would like to do this w/ composable functions iterating once through the collection. In other words, I do not want to do a Select wrapped in a String.Join
Aggregate does not allow that in natural way.
You can carry previous element and do you final handling after Aggregate. Also I think your best bet would be to write custom method that does that custom handling for last (and possibly first) element.
Some approximate code to special case last item with Aggregate (does not handle most special case like empty/short list):
var firstLast = seq.Aggregate(
Tuple.Create(new StringBuilder(), default(string)),
(sum, cur) =>
{
if (sum.Item2 != null)
{
sum.Item1.Append(",");
sum.Item1.Append(sum.Item2);
}
return Tuple.Create(sum.Item1, cur);
});
firstLast.Item1.Append(SpecialProcessingForLast(sum.Item2));
return firstLast.Item1.ToString();
Aggregate with special case for "last". Sample is ready to copy/paste to LinqPad/console app, uncomment "this" when making extension function. Main shows aggregating array with summing all but last element, last one is subtracted from result:
void Main()
{
Console.WriteLine(AggregateWithLast(new[] {1,1,1,-3}, 0, (s,c)=>s+c, (s,c)=>s-c));
Console.WriteLine(AggregateWithLast(new[] {1,1,1,+3}, 0, (s,c)=>s+c, (s,c)=>s-c));
}
public static TAccumulate AggregateWithLast<TSource, TAccumulate>(
/*this */ IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> funcAll,
Func<TAccumulate, TSource, TAccumulate> funcLast)
{
using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
{
if (!sourceIterator.MoveNext())
{
return seed;
}
TSource last = sourceIterator.Current;
TAccumulate total = seed;
while (sourceIterator.MoveNext())
{
total = funcAll(total, last);
last = sourceIterator.Current;
}
return funcLast(total, last);
}
}
Note: if you need just String.Join than one in .Net 4.0+ takes IEnumerable<T> - so it will iterate sequence only once without need to ToList/ToArray.
Another approach for your particular example, is to skip the comma for the first element and prepend it to the tail elements, like this:
List<string> listString = new() { "1", "2", "3" };
string joined = listString
.Select((value, index) => (value, index))
.Aggregate(new StringBuilder(), (sb, s) =>
s.index == 0
? sb.Append(s.value)
: sb.Append(", ").Append(s.value))
.ToString();
I know this does not address the question in the title, but for most "join things with some infix" problems, this works well. That is, when string.Join is not the solution.

How to concatenate two IEnumerable<T> into a new IEnumerable<T>?

I have two instances of IEnumerable<T> (with the same T). I want a new instance of IEnumerable<T> which is the concatenation of both.
Is there a built-in method in .NET to do that or do I have to write it myself?
Yes, LINQ to Objects supports this with Enumerable.Concat:
var together = first.Concat(second);
NB: Should first or second be null you would receive a ArgumentNullException. To avoid this & treat nulls as you would an empty set, use the null coalescing operator like so:
var together = (first ?? Enumerable.Empty<string>()).Concat(second ?? Enumerable.Empty<string>()); //amending `<string>` to the appropriate type
The Concat method will return an object which implements IEnumerable<T> by returning an object (call it Cat) whose enumerator will attempt to use the two passed-in enumerable items (call them A and B) in sequence. If the passed-in enumerables represent sequences which will not change during the lifetime of Cat, and which can be read from without side-effects, then Cat may be used directly. Otherwise, it may be a good idea to call ToList() on Cat and use the resulting List<T> (which will represent a snapshot of the contents of A and B).
Some enumerables take a snapshot when enumeration begins, and will return data from that snapshot if the collection is modified during enumeration. If B is such an enumerable, then any change to B which occurs before Cat has reached the end of A will show up in Cat's enumeration, but changes which occur after that will not. Such semantics may likely be confusing; taking a snapshot of Cat can avoid such issues.
You can use below code for your solution:-
public void Linq94()
{
int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 };
int[] numbersB = { 1, 3, 5, 7, 8 };
var allNumbers = numbersA.Concat(numbersB);
Console.WriteLine("All numbers from both arrays:");
foreach (var n in allNumbers)
{
Console.WriteLine(n);
}
}
I know this is a relatively old post, but if you wanted to concatenate multiple IEnumerable's, I use the following
var joinedSel = new[] { first, second, third }.Where(x => x != null).SelectMany(x => x);
This eliminates any null IEnumerable's and allows for multiple concatenations.
Based off of craig1231's answer, I've created some extension methods...
public static IEnumerable<T> JoinLists<T>(this IEnumerable<T> list1, IEnumerable<T> list2)
{
var joined = new[] { list1, list2 }.Where(x => x != null).SelectMany(x => x);
return joined ?? Enumerable.Empty<T>();
}
public static IEnumerable<T> JoinLists<T>(this IEnumerable<T> list1, IEnumerable<T> list2, IEnumerable<T> list3)
{
var joined = new[] { list1, list2, list3 }.Where(x => x != null).SelectMany(x => x);
return joined ?? Enumerable.Empty<T>();
}
public static IEnumerable<T> JoinMany<T>(params IEnumerable<T>[] array)
{
var final = array.Where(x => x != null).SelectMany(x => x);
return final ?? Enumerable.Empty<T>();
}
// The answer that I was looking for when searching
public void Answer()
{
IEnumerable<YourClass> first = this.GetFirstIEnumerableList();
// Assign to empty list so we can use later
IEnumerable<YourClass> second = new List<YourClass>();
if (IwantToUseSecondList)
{
second = this.GetSecondIEnumerableList();
}
IEnumerable<SchemapassgruppData> concatedList = first.Concat(second);
}

Get Non-Distinct elements from an IEnumerable

I have a class called Item. Item has an identifier property called ItemCode which is a string. I would like to get a list of all non-distinct Items in a list of Items.
Example:
List<Item> itemList = new List<Item>()
{
new Item("code1", "description1"),
new Item("code2", "description2"),
new Item("code2", "description3"),
};
I want a list containing the bottom two entries
If I use
var distinctItems = itemsList.Distinct();
I get the list of distinct items which is great, but I want almost the opposite of that. I could subtract the the distinct list from the original list but that wouldn't contain ALL repeats, just one instance of each.
I've had a play and can't figure out an elegant solution. Any pointers or help would be much appreciated. Thanks!
I have 3.5 so LINQ is available
My take:
var distinctItems =
from list in itemsList
group list by list.ItemCode into grouped
where grouped.Count() > 1
select grouped;
as an extension method:
public static IEnumerable<T> NonDistinct<T, TKey> (this IEnumerable<T> source, Func<T, TKey> keySelector)
{
return source.GroupBy(keySelector).Where(g => g.Count() > 1).SelectMany(r => r);
}
You might want to try it with group by operator. The idea would be to group them by the ItemCode and taking the groups with more than one member, something like :
var grouped = from i in itemList
group i by i.ItemCode into g
select new { Code = g.Key, Items = g };
var result = from g in grouped
where g.Items.Count() > 1;
I'd suggest writing a custom extension method, something like this:
static class RepeatedExtension
{
public static IEnumerable<T> Repeated<T>(this IEnumerable<T> source)
{
var distinct = new Dictionary<T, int>();
foreach (var item in source)
{
if (!distinct.ContainsKey(item))
distinct.Add(item, 1);
else
{
if (distinct[item]++ == 1) // only yield items on first repeated occurence
yield return item;
}
}
}
}
You also need to override Equals() method for your Item class, so that items are correctly compared by their code.

Categories