Merge elements in list by property - c#

Context
I have a list of time intervals. Time interval type is HistoMesures.
Each HistoMesure is defined by a Debut (begin) property, a Fin (end) property, and a Commentaires (a little note) property.
My list is made in such a way that :
All HistoMesure are exclusive, I mean that they can't be overlapping each other.
The list is sorted by Debut, so by the beggining of the interval.
Edit : All HistoMesure are contiguous in this configuration.
Question
I want to merge (transform two little intervals in one big interval) all adjacent HistoMesure which have the same Commentaires. Currently I achieve this that way :
//sortedHistos type is List<HistoMesure>
int i = 0;
while (i < sortedHistos.Count - 1)
{
if (sortedHistos[i].Commentaires == sortedHistos[i + 1].Commentaires)
{
sortedHistos[i].Fin = sortedHistos[i + 1].Fin;
sortedHistos.RemoveAt(i + 1);
}
else
{
++i;
}
}
But I feel that it exists a more elegant way to do this, maybe with LINQ. Do you have any suggestion ?

Your solution works fine, I would keep it.
Don't try too hard to use LINQ if it doesn't match your requirements. LINQ is great to write queries (this is the Q of LINQ), not so great to modify existing lists.

This code will produce overlapping merged intervals. I.e. if you have intervals A, B, C where A and C have same commentaries, result will be AC, B:
var result = from h in sortedHistos
group h by h.Commentaires into g
select new HistoMesure {
Debut = g.First().Debut, // thus you have sorted entries
Fin = g.Last().Fin,
Commentaires = g.Key
};
You can use Min and Max if intervals are not sorted.
UPDATE: There is no default LINQ operator which allows you to create adjacent groups. But you always can create one. Here is IEnumerable<T> extension (I skipped arguments check):
public static IEnumerable<IGrouping<TKey, TElement>> GroupAdjacent<TKey, TElement>(
this IEnumerable<TElement> source, Func<TElement, TKey> keySelector)
{
using (var iterator = source.GetEnumerator())
{
if(!iterator.MoveNext())
{
yield break;
}
else
{
var comparer = Comparer<TKey>.Default;
var group = new Grouping<TKey, TElement>(keySelector(iterator.Current));
group.Add(iterator.Current);
while(iterator.MoveNext())
{
TKey key = keySelector(iterator.Current);
if (comparer.Compare(key, group.Key) != 0)
{
yield return group;
group = new Grouping<TKey, TElement>(key);
}
group.Add(iterator.Current);
}
if (group.Any())
yield return group;
}
}
}
This extension creates groups of adjacent elements which have same key value. Unfortunately all implementations of IGrouping in .NET are internal, so you need yours:
public class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
{
private List<TElement> elements = new List<TElement>();
public Grouping(TKey key)
{
Key = key;
}
public TKey Key { get; private set; }
public IEnumerator<TElement> GetEnumerator()
{
return elements.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(TElement element)
{
elements.Add(element);
}
}
And now your code will look like:
var result = sortedHistos.GroupAdjacent(h => h.Commentaries)
.Select(g => new HistoMesure {
Debut = g.Min(h => h.Debut),
Fin = g.Max(h => h.Fin),
Commentaries = g.Key
});

Using Linq and borrowing from this article to group by adjacent values, this should work:
Your query:
var filteredHistos = sortedHistos
.GroupAdjacent(h => h.Commentaires)
.Select(g => new HistoMesure
{
Debut = g.First().Debut,
Fin = g.Last().Fin,
Commentaires = g.Key
});
And copying from the article, the rest of the code to group by:
public class GroupOfAdjacent<TSource, TKey> : IEnumerable<TSource>, IGrouping<TKey, TSource>
{
public TKey Key { get; set; }
private List<TSource> GroupList { get; set; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((System.Collections.Generic.IEnumerable<TSource>)this).GetEnumerator();
}
System.Collections.Generic.IEnumerator<TSource> System.Collections.Generic.IEnumerable<TSource>.GetEnumerator()
{
foreach (var s in GroupList)
yield return s;
}
public GroupOfAdjacent(List<TSource> source, TKey key)
{
GroupList = source;
Key = key;
}
}
public static class LocalExtensions
{
public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
TKey last = default(TKey);
bool haveLast = false;
List<TSource> list = new List<TSource>();
foreach (TSource s in source)
{
TKey k = keySelector(s);
if (haveLast)
{
if (!k.Equals(last))
{
yield return new GroupOfAdjacent<TSource, TKey>(list, last);
list = new List<TSource>();
list.Add(s);
last = k;
}
else
{
list.Add(s);
last = k;
}
}
else
{
list.Add(s);
last = k;
haveLast = true;
}
}
if (haveLast)
yield return new GroupOfAdjacent<TSource, TKey>(list, last);
}
}

If I understood you correctly, you need something like this:
var mergedMesures = mesures
.GroupBy(_ => _.Commentaires)
.Select(_ => new HistoMesures
{
Debut = _.Min(item => item.Debut),
Fin = _.Max(item => item.Fin),
Commentaires = _.Key
});

Related

Linq group by Chunks [duplicate]

Let's take a class called Cls:
public class Cls
{
public int SequenceNumber { get; set; }
public int Value { get; set; }
}
Now, let's populate some collection with following elements:
Sequence
Number Value
======== =====
1 9
2 9
3 15
4 15
5 15
6 30
7 9
What I need to do, is to enumerate over Sequence Numbers and check if the next element has the same value. If yes, values are aggregated and so, desired output is as following:
Sequence Sequence
Number Number
From To Value
======== ======== =====
1 2 9
3 5 15
6 6 30
7 7 9
How can I perform this operation using LINQ query?
You can use Linq's GroupBy in a modified version which groups only if the two items are adjacent, then it's easy as:
var result = classes
.GroupAdjacent(c => c.Value)
.Select(g => new {
SequenceNumFrom = g.Min(c => c.SequenceNumber),
SequenceNumTo = g.Max(c => c.SequenceNumber),
Value = g.Key
});
foreach (var x in result)
Console.WriteLine("SequenceNumFrom:{0} SequenceNumTo:{1} Value:{2}", x.SequenceNumFrom, x.SequenceNumTo, x.Value);
DEMO
Result:
SequenceNumFrom:1 SequenceNumTo:2 Value:9
SequenceNumFrom:3 SequenceNumTo:5 Value:15
SequenceNumFrom:6 SequenceNumTo:6 Value:30
SequenceNumFrom:7 SequenceNumTo:7 Value:9
This is the extension method to to group adjacent items:
public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
TKey last = default(TKey);
bool haveLast = false;
List<TSource> list = new List<TSource>();
foreach (TSource s in source)
{
TKey k = keySelector(s);
if (haveLast)
{
if (!k.Equals(last))
{
yield return new GroupOfAdjacent<TSource, TKey>(list, last);
list = new List<TSource>();
list.Add(s);
last = k;
}
else
{
list.Add(s);
last = k;
}
}
else
{
list.Add(s);
last = k;
haveLast = true;
}
}
if (haveLast)
yield return new GroupOfAdjacent<TSource, TKey>(list, last);
}
}
and the class used:
public class GroupOfAdjacent<TSource, TKey> : IEnumerable<TSource>, IGrouping<TKey, TSource>
{
public TKey Key { get; set; }
private List<TSource> GroupList { get; set; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((System.Collections.Generic.IEnumerable<TSource>)this).GetEnumerator();
}
System.Collections.Generic.IEnumerator<TSource> System.Collections.Generic.IEnumerable<TSource>.GetEnumerator()
{
foreach (var s in GroupList)
yield return s;
}
public GroupOfAdjacent(List<TSource> source, TKey key)
{
GroupList = source;
Key = key;
}
}
You can use this linq query
Demo
var values = (new[] { 9, 9, 15, 15, 15, 30, 9 }).Select((x, i) => new { x, i });
var query = from v in values
let firstNonValue = values.Where(v2 => v2.i >= v.i && v2.x != v.x).FirstOrDefault()
let grouping = firstNonValue == null ? int.MaxValue : firstNonValue.i
group v by grouping into v
select new
{
From = v.Min(y => y.i) + 1,
To = v.Max(y => y.i) + 1,
Value = v.Min(y => y.x)
};
MoreLinq provides this functionality out of the box
It's called GroupAdjacent and is implemented as extension method on IEnumerable:
Groups the adjacent elements of a sequence according to a specified key selector function.
enumerable.GroupAdjacent(e => e.Key)
There is even a Nuget "source" package that contains only that method, if you don't want to pull in an additional binary Nuget package.
The method returns an IEnumerable<IGrouping<TKey, TValue>>, so its output can be processed in the same way output from GroupBy would be.
You can do it like this:
var all = new [] {
new Cls(1, 9)
, new Cls(2, 9)
, new Cls(3, 15)
, new Cls(4, 15)
, new Cls(5, 15)
, new Cls(6, 30)
, new Cls(7, 9)
};
var f = all.First();
var res = all.Skip(1).Aggregate(
new List<Run> {new Run {From = f.SequenceNumber, To = f.SequenceNumber, Value = f.Value} }
, (p, v) => {
if (v.Value == p.Last().Value) {
p.Last().To = v.SequenceNumber;
} else {
p.Add(new Run {From = v.SequenceNumber, To = v.SequenceNumber, Value = v.Value});
}
return p;
});
foreach (var r in res) {
Console.WriteLine("{0} - {1} : {2}", r.From, r.To, r.Value);
}
The idea is to use Aggregate creatively: starting with a list consisting of a single Run, examine the content of the list we've got so far at each stage of aggregation (the if statement in the lambda). Depending on the last value, either continue the old run, or start a new one.
Here is a demo on ideone.
I was able to accomplish it by creating a custom extension method.
static class Extensions {
internal static IEnumerable<Tuple<int, int, int>> GroupAdj(this IEnumerable<Cls> enumerable) {
Cls start = null;
Cls end = null;
int value = Int32.MinValue;
foreach (Cls cls in enumerable) {
if (start == null) {
start = cls;
end = cls;
continue;
}
if (start.Value == cls.Value) {
end = cls;
continue;
}
yield return Tuple.Create(start.SequenceNumber, end.SequenceNumber, start.Value);
start = cls;
end = cls;
}
yield return Tuple.Create(start.SequenceNumber, end.SequenceNumber, start.Value);
}
}
Here's the implementation:
static void Main() {
List<Cls> items = new List<Cls> {
new Cls { SequenceNumber = 1, Value = 9 },
new Cls { SequenceNumber = 2, Value = 9 },
new Cls { SequenceNumber = 3, Value = 15 },
new Cls { SequenceNumber = 4, Value = 15 },
new Cls { SequenceNumber = 5, Value = 15 },
new Cls { SequenceNumber = 6, Value = 30 },
new Cls { SequenceNumber = 7, Value = 9 }
};
Console.WriteLine("From To Value");
Console.WriteLine("===== ===== =====");
foreach (var item in items.OrderBy(i => i.SequenceNumber).GroupAdj()) {
Console.WriteLine("{0,-5} {1,-5} {2,-5}", item.Item1, item.Item2, item.Item3);
}
}
And the expected output:
From To Value
===== ===== =====
1 2 9
3 5 15
6 6 30
7 7 9
Here is an implementation without any helper methods:
var grp = 0;
var results =
from i
in
input.Zip(
input.Skip(1).Concat(new [] {input.Last ()}),
(n1, n2) => Tuple.Create(
n1, (n2.Value == n1.Value) ? grp : grp++
)
)
group i by i.Item2 into gp
select new {SequenceNumFrom = gp.Min(x => x.Item1.SequenceNumber),SequenceNumTo = gp.Max(x => x.Item1.SequenceNumber), Value = gp.Min(x => x.Item1.Value)};
The idea is:
Keep track of your own grouping indicator, grp.
Join each item of the collection to the next item in the collection (via Skip(1) and Zip).
If the Values match, they are in the same group; otherwise, increment grp to signal the start of the next group.
Untested dark magic follows. The imperative version seems like it would be easier in this case.
IEnumerable<Cls> data = ...;
var query = data
.GroupBy(x => x.Value)
.Select(g => new
{
Value = g.Key,
Sequences = g
.OrderBy(x => x.SequenceNumber)
.Select((x,i) => new
{
x.SequenceNumber,
OffsetSequenceNumber = x.SequenceNumber - i
})
.GroupBy(x => x.OffsetSequenceNumber)
.Select(g => g
.Select(x => x.SequenceNumber)
.OrderBy(x => x)
.ToList())
.ToList()
})
.SelectMany(x => x.Sequences
.Select(s => new { First = s.First(), Last = s.Last(), x.Value }))
.OrderBy(x => x.First)
.ToList();
Let me propose another option, which yields lazily both sequence of groups and
elements inside groups.
Demonstration in .NET Fiddle
Implementation:
public static class EnumerableExtensions
{
public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey>? comparer = null)
{
var comparerOrDefault = comparer ?? EqualityComparer<TKey>.Default;
using var iterator = new Iterator<TSource>(source.GetEnumerator());
iterator.MoveNext();
while (iterator.HasCurrent)
{
var key = keySelector(iterator.Current);
var elements = YieldAdjacentElements(iterator, key, keySelector, comparerOrDefault);
yield return new Grouping<TKey, TSource>(key, elements);
while (iterator.HasCurrentWithKey(key, keySelector, comparerOrDefault))
{
iterator.MoveNext();
}
}
}
static IEnumerable<TSource> YieldAdjacentElements<TKey, TSource>(
Iterator<TSource> iterator,
TKey key,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
while (iterator.HasCurrentWithKey(key, keySelector, comparer))
{
yield return iterator.Current;
iterator.MoveNext();
}
}
private static bool HasCurrentWithKey<TKey, TSource>(
this Iterator<TSource> iterator,
TKey key,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer) =>
iterator.HasCurrent && comparer.Equals(keySelector(iterator.Current), key);
private sealed class Grouping<TKey, TElement> : IGrouping<TKey, TElement>
{
public Grouping(TKey key, IEnumerable<TElement> elements)
{
Key = key;
Elements = elements;
}
public TKey Key { get; }
public IEnumerable<TElement> Elements { get; }
public IEnumerator<TElement> GetEnumerator() => Elements.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Elements.GetEnumerator();
}
private sealed class Iterator<T> : IDisposable
{
private readonly IEnumerator<T> _enumerator;
public Iterator(IEnumerator<T> enumerator)
{
_enumerator = enumerator;
}
public bool HasCurrent { get; private set; }
public T Current => _enumerator.Current;
public void MoveNext()
{
HasCurrent = _enumerator.MoveNext();
}
public void Dispose()
{
_enumerator.Dispose();
}
}
}
Notice, that it is impossible to achieve such level of laziness with regular GroupBy operation, since it needs to look through the whole collection before yielding the first group.
Particularly, in my case migration from GroupBy to GroupAdjacent in connection with lazy handling of whole pipeline helped to resolve memory consumption issues for large sequences.
In general, GroupAdjacent can be used as lazy and more efficient alternative of GroupBy, provided that input collection satisfies condition, that keys are sorted (or at least not fragmented), and provided that all operations in pipeline are lazy.

LINQ: Group by index and value [duplicate]

This question already has answers here:
linq group by contiguous blocks
(5 answers)
Closed 4 years ago.
Lets say I have an list of strings with the following values:
["a","a","b","a","a","a","c","c"]
I want to execute a linq query that will group into 4 groups:
Group 1: ["a","a"] Group 2: ["b"] Group 3: ["a","a","a"] Group 4:
["c","c"]
Basically I want to create 2 different groups for the value "a" because they are not coming from the same "index sequence".
Anyone has a LINQ solution for this?
You just need key other than items of array
var x = new string[] { "a", "a", "a", "b", "a", "a", "c" };
int groupId = -1;
var result = x.Select((s, i) => new
{
value = s,
groupId = (i > 0 && x[i - 1] == s) ? groupId : ++groupId
}).GroupBy(u => new { groupId });
foreach (var item in result)
{
Console.WriteLine(item.Key);
foreach (var inner in item)
{
Console.WriteLine(" => " + inner.value);
}
}
Here is the result: Link
Calculate the "index sequence" first, then do your group.
private class IndexedData
{
public int Sequence;
public string Text;
}
string[] data = [ "a", "a", "b" ... ]
// Calculate "index sequence" for each data element.
List<IndexedData> indexes = new List<IndexedData>();
foreach (string s in data)
{
IndexedData last = indexes.LastOrDefault() ?? new IndexedData();
indexes.Add(new IndexedData
{
Text = s,
Sequence = (last.Text == s
? last.Sequence
: last.Sequence + 1)
});
}
// Group by "index sequence"
var grouped = indexes.GroupBy(i => i.Sequence)
.Select(g => g.Select(i => i.Text));
This is a naive foreach implementation where whole dataset ends up in memory (probably not an issue for you since you do GroupBy):
public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
var result = new List<List<string>>();
foreach (var value in values)
{
var currentGroup = result.LastOrDefault();
if (currentGroup?.FirstOrDefault()?.Equals(value) == true)
{
currentGroup.Add(value);
}
else
{
result.Add(new List<string> { value });
}
}
return result;
}
Here comes a slightly complicated implementation with foreach and yield return enumerator state machine which keeps only current group in memory - this is probably how this would be implemented on framework level:
EDIT: This is apparently also the way MoreLINQ does it.
public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
var currentValue = default(string);
var group = (List<string>)null;
foreach (var value in values)
{
if (group == null)
{
currentValue = value;
group = new List<string> { value };
}
else if (currentValue.Equals(value))
{
group.Add(value);
}
else
{
yield return group;
currentValue = value;
group = new List<string> { value };
}
}
if (group != null)
{
yield return group;
}
}
And this is a joke version using LINQ only, it is basically the same as the first one but is slightly harder to understand (especially since Aggregate is not the most frequently used LINQ method):
public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
return values.Aggregate(
new List<List<string>>(),
(lists, str) =>
{
var currentGroup = lists.LastOrDefault();
if (currentGroup?.FirstOrDefault()?.Equals(str) == true)
{
currentGroup.Add(str);
}
else
{
lists.Add(new List<string> { str });
}
return lists;
},
lists => lists);
}
Using an extension method based on the APL scan operator, that is like Aggregate but returns intermediate results paired with source values:
public static IEnumerable<KeyValuePair<TKey, T>> ScanPair<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<KeyValuePair<TKey, T>, T, TKey> combine) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var prevkv = new KeyValuePair<TKey, T>(seedKey, srce.Current);
while (srce.MoveNext()) {
yield return prevkv;
prevkv = new KeyValuePair<TKey, T>(combine(prevkv, srce.Current), srce.Current);
}
yield return prevkv;
}
}
}
You can create extension methods for grouping by consistent runs:
public static IEnumerable<IGrouping<int, TResult>> GroupByRuns<TElement, TKey, TResult>(this IEnumerable<TElement> src, Func<TElement, TKey> key, Func<TElement, TResult> result, IEqualityComparer<TKey> cmp = null) {
cmp = cmp ?? EqualityComparer<TKey>.Default;
return src.ScanPair(0,
(kvp, cur) => cmp.Equals(key(kvp.Value), key(cur)) ? kvp.Key : kvp.Key + 1)
.GroupBy(kvp => kvp.Key, kvp => result(kvp.Value));
}
public static IEnumerable<IGrouping<int, TElement>> GroupByRuns<TElement, TKey>(this IEnumerable<TElement> src, Func<TElement, TKey> key) => src.GroupByRuns(key, e => e);
public static IEnumerable<IGrouping<int, TElement>> GroupByRuns<TElement>(this IEnumerable<TElement> src) => src.GroupByRuns(e => e, e => e);
public static IEnumerable<IEnumerable<TResult>> Runs<TElement, TKey, TResult>(this IEnumerable<TElement> src, Func<TElement, TKey> key, Func<TElement, TResult> result, IEqualityComparer<TKey> cmp = null) =>
src.GroupByRuns(key, result).Select(g => g.Select(s => s));
public static IEnumerable<IEnumerable<TElement>> Runs<TElement, TKey>(this IEnumerable<TElement> src, Func<TElement, TKey> key) => src.Runs(key, e => e);
public static IEnumerable<IEnumerable<TElement>> Runs<TElement>(this IEnumerable<TElement> src) => src.Runs(e => e, e => e);
And using the simplest version, you can get either an IEnumerable<IGrouping>>:
var ans1 = src.GroupByRuns();
Or a version that dumps the IGrouping (and its Key) for an IEnumerable:
var ans2 = src.Runs();

Order parent collection by minimum values in child collection in Linq

Parent{ List<Child> Children {get;set;} }
Child { int Age {get;set;} }
I would like to order the parents by the lowest age of their children, proceeding to the second or third child in the case of a tie.
The closest I've come is this, which only orders by the youngest child:
parents.OrderBy(p => p.Children.Min(c => c.Age))
This doesn't account for second (or third, etc) youngest in the case of a tie.
Given these 3 parents with corresponding child ages, I'd like them to come out in this order.
P1 1,2,7
P2 1,3,6
P3 1,4,5
So what you're trying to do, at a conceptual level, is compare two sequences. Rather than trying to special case it for this specific sequence, we can simply write a comparer capable of comparing any two sequences.
It will go through the items in the sequence compare the items at the same position, and then if it finds a pair that aren't equal, it knows the result.
public class SequenceComparer<TSource> : IComparer<IEnumerable<TSource>>
{
private IComparer<TSource> comparer;
public SequenceComparer(IComparer<TSource> comparer = null)
{
this.comparer = comparer ?? Comparer<TSource>.Default;
}
public int Compare(IEnumerable<TSource> x, IEnumerable<TSource> y)
{
return x.Zip(y, (a, b) => comparer.Compare(a, b))
.Where(n => n != 0)
.DefaultIfEmpty(x.Count().CompareTo(y.Count()))
.First();
}
}
Now we can simply use this comparer when calling OrderBy:
var query = parents.OrderBy(parent => parent.Children
.OrderBy(child => child.Age)
.Select(child => child.Age)
, new SequenceComparer<int>());
You'll need to write something like this extension method:
var orderedParents = parents.OrderBy(p => p.Children, c => c.Age);
Generic implementation:
/// <summary>
/// Given a way to determine a collection of elements (for example
/// children of a parent) and a comparable property of those items
/// (for example age of a child) this orders a collection of elements
/// according to the sorting order of the property of the first element
/// of their respective collections. In case of a tie, fall back to
/// subsequent elements as appropriate.
/// </summary>
public static IOrderedEnumerable<T> OrderBy<T, TKey, TValue>(this IEnumerable<T> #this, Func<T, IEnumerable<TKey>> getKeys, Func<TKey, TValue> getValue)
where TValue : IComparable<TValue>
{
return #this.OrderBy(x => x, new KeyComparer<T, TKey, TValue>(getKeys, getValue));
}
private class KeyComparer<T, TKey, TValue> : IComparer<T>
where TValue : IComparable<TValue>
{
private Func<T, IEnumerable<TKey>> GetKeys;
private Func<TKey, TValue> GetValue;
public KeyComparer(Func<T, IEnumerable<TKey>> getKeys, Func<TKey, TValue> getValue)
{
this.GetKeys = getKeys;
this.GetValue = getValue;
}
public int Compare(T x, T y)
{
var xKeys = GetKeys(x).OrderBy(GetValue).Select(GetValue);
var yKeys = GetKeys(y).OrderBy(GetValue).Select(GetValue);
foreach (var pair in xKeys.Zip(yKeys, Tuple.Create))
{
if (pair.Item1.CompareTo(pair.Item2) != 0)
return pair.Item1.CompareTo(pair.Item2);
}
return xKeys.Count().CompareTo(yKeys.Count());
}
}
You could use ThenBy and take the 2nd and 3rd children. But, that is not scalable, so it depends on the needs of the impl
If you want something more robust, you could do the following. It will work for this specific case. I am going to see if I can optimize it to be more generic though :)
public static class myExt
{
public static List<Parent> OrderByWithTieBreaker(this List<Parent> parents, int depth = 0)
{
if (depth > parents[0].Children.Count())
return parents;
var returnedList = new List<Parent>();
Func<Parent, int> keySelector = x =>
{
IEnumerable<Child> enumerable = x.Children.OrderBy(y => y.Age).Skip(depth);
if (!enumerable.Any())
return 0; //If no children left, then return lowest possible age
return enumerable.Min(z => z.Age);
};
var orderedParents = parents.OrderBy(keySelector);
var groupings = orderedParents.GroupBy(keySelector);
foreach (var grouping in groupings)
{
if (grouping.Count() > 1)
{
var innerOrder = grouping.ToList().OrderByWithTieBreaker(depth + 1);
returnedList = returnedList.Union(innerOrder).ToList();
}
else
returnedList.Add(grouping.First());
}
return returnedList;
}
}
[TestFixture]
public class TestClass
{
public class Parent { public string Name { get; set; } public List<Child> Children { get; set; } }
public class Child { public int Age {get;set;} }
[Test]
public void TestName()
{
var parents = new List<Parent>
{
new Parent{Name="P3", Children = new List<Child>{new Child{Age=1}, new Child{Age=3}, new Child{Age=6}, new Child{Age=7}}},
new Parent{Name="P4", Children = new List<Child>{new Child{Age=1}, new Child{Age=3}, new Child{Age=6}, new Child{Age=7}}},
new Parent{Name="P2", Children = new List<Child>{new Child{Age=1}, new Child{Age=3}, new Child{Age=6}}},
new Parent{Name="P1", Children = new List<Child>{new Child{Age=1}, new Child{Age=2}, new Child{Age=7}}},
new Parent{Name="P5", Children = new List<Child>{new Child{Age=1}, new Child{Age=4}, new Child{Age=5}}}
};
var f = parents.OrderByWithTieBreaker();
int count = 1;
foreach (var d in f)
{
Assert.That(d.Name, Is.EqualTo("P"+count));
count++;
}
}

Order a List without hardcoding the fields or direction

I have an ObservableCollection which I want to sort, not in place but I want to create a new sorted copy.
There's lots of examples on how to sort lists using nifty lambda expressions or using LINQ, but I can't hardcode the fields I want to sort by into code.
I have an array of NSSortDescription which work kinda like SortDescription. There's a string with the name of the property but the direction is specified by a bool (true = ascending). The first value in the array should be the primary sorting field, when the values in that field match, the second sort descriptor should be used, etc.
Example:
Artist: Bob Marley, Title: No Woman No Cry
Artist: Bob Marley, Title: Could You Be Loved
Artist: Infected Mushroom, Title: Converting Vegetarians
Artist: Bob Marley, Title: One Love
Artist: Chemical Brothers, Title: Do It Again
Sort descriptor: Artist descending, Title ascending.
Result:
Artist: Infected Mushroom, Title: Converting Vegetarians
Artist: Chemical Brothers, Title: Do It Again
Artist: Bob Marley, Title: Could You Be Loved
Artist: Bob Marley, Title: No Woman No Cry
Artist: Bob Marley, Title: One Love
Any suggestions on how to accomplish this?
UPDATE: Change Sort to OrderBy as Sort is unstable sort algorithm
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
namespace PNS
{
public class SortableList<T> : List<T>
{
private string _propertyName;
private bool _ascending;
public void Sort(string propertyName, bool ascending)
{
//Flip the properties if the parameters are the same
if (_propertyName == propertyName && _ascending == ascending)
{
_ascending = !ascending;
}
//Else, new properties are set with the new values
else
{
_propertyName = propertyName;
_ascending = ascending;
}
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
PropertyDescriptor propertyDesc = properties.Find(propertyName, true);
// Apply and set the sort, if items to sort
PropertyComparer<T> pc = new PropertyComparer<T>(propertyDesc, (_ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending);
//this.Sort(pc); UNSTABLE SORT ALGORITHM
this.OrderBy(t=>t, pc);
}
}
public class PropertyComparer<T> : System.Collections.Generic.IComparer<T>
{
// The following code contains code implemented by Rockford Lhotka:
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnadvnet/html/vbnet01272004.asp
private PropertyDescriptor _property;
private ListSortDirection _direction;
public PropertyComparer(PropertyDescriptor property, ListSortDirection direction)
{
_property = property;
_direction = direction;
}
public int Compare(T xWord, T yWord)
{
// Get property values
object xValue = GetPropertyValue(xWord, _property.Name);
object yValue = GetPropertyValue(yWord, _property.Name);
// Determine sort order
if (_direction == ListSortDirection.Ascending)
{
return CompareAscending(xValue, yValue);
}
else
{
return CompareDescending(xValue, yValue);
}
}
public bool Equals(T xWord, T yWord)
{
return xWord.Equals(yWord);
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
// Compare two property values of any type
private int CompareAscending(object xValue, object yValue)
{
int result;
if (xValue == null && yValue != null) return -1;
if (yValue == null && xValue != null) return 1;
if (xValue == null && yValue == null) return 0;
// If values implement IComparer
if (xValue is IComparable)
{
result = ((IComparable)xValue).CompareTo(yValue);
}
// If values don't implement IComparer but are equivalent
else if (xValue.Equals(yValue))
{
result = 0;
}
// Values don't implement IComparer and are not equivalent, so compare as string values
else result = xValue.ToString().CompareTo(yValue.ToString());
// Return result
return result;
}
private int CompareDescending(object xValue, object yValue)
{
// Return result adjusted for ascending or descending sort order ie
// multiplied by 1 for ascending or -1 for descending
return CompareAscending(xValue, yValue) * -1;
}
private object GetPropertyValue(T value, string property)
{
// Get property
PropertyInfo propertyInfo = value.GetType().GetProperty(property);
// Return value
return propertyInfo.GetValue(value, null);
}
}
}
You could dynamically create the OrderBy predicate based on string properties.
Func<MyType, object> firstSortFunc = null;
Func<MyType, object> secondSortFunc = null;
//these strings would be obtained from your NSSortDescription array
string firstProp = "firstPropertyToSortBy";
string secondProp = "secondPropertyToSortBy";
bool isAscending = true;
//create the predicate once you have the details
//GetProperty gets an object's property based on the string
firstSortFunc = x => x.GetType().GetProperty(firstProp).GetValue(x);
secondSortFunc = x => x.GetType().GetProperty(secondProp).GetValue(x);
List<MyType> ordered = new List<MyType>();
if(isAscending)
ordered = unordered.OrderBy(firstSortFunc).ThenBy(secondSortFunc).ToList();
else
ordered = unordered.OrderByDescending(firstSortFunc).ThenBy(secondSortFunc).ToList();
You could ceate a class named e.g. DynamicProperty which does retrieve the requested value. I do assume that the returned values do implement IComparable which should not be a too harsh limitation since you do want to compare the values anyway.
using System;
using System.Linq;
using System.Reflection;
namespace DynamicSort
{
class DynamicProperty<T>
{
PropertyInfo SortableProperty;
public DynamicProperty(string propName)
{
SortableProperty = typeof(T).GetProperty(propName);
}
public IComparable GetPropertyValue(T obj)
{
return (IComparable)SortableProperty.GetValue(obj);
}
}
class Program
{
class SomeData
{
public int X { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
SomeData[] data = new SomeData[]
{
new SomeData { Name = "ZZZZ", X = -1 },
new SomeData { Name = "AAAA", X = 5 },
new SomeData { Name = "BBBB", X = 5 },
new SomeData { Name = "CCCC", X = 5 }
};
var prop1 = new DynamicProperty<SomeData>("X");
var prop2 = new DynamicProperty<SomeData>("Name");
var sorted = data.OrderBy(x=> prop1.GetPropertyValue(x))
.ThenByDescending( x => prop2.GetPropertyValue(x));
foreach(var res in sorted)
{
Console.WriteLine("{0} X: {1}", res.Name, res.X);
}
}
}
}
I once wrote the following extension methods, which basically have the effect of either OrderBy or ThenBy, depending on whether the source is already ordered:
public static class Extensions {
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending) {
var orderedSource = source as IOrderedEnumerable<TSource>;
if (orderedSource != null) {
return orderedSource.CreateOrderedEnumerable(keySelector, comparer, descending);
}
if (descending) {
return source.OrderByDescending(keySelector, comparer);
}
return source.OrderBy(keySelector, comparer);
}
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) {
return source.OrderByPreserve(keySelector, null, false);
}
public static IOrderedEnumerable<TSource> OrderByPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) {
return source.OrderByPreserve(keySelector, comparer, false);
}
public static IOrderedEnumerable<TSource> OrderByDescendingPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) {
return source.OrderByPreserve(keySelector, null, true);
}
public static IOrderedEnumerable<TSource> OrderByDescendingPreserve<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer) {
return source.OrderByPreserve(keySelector, comparer, true);
}
}
The interface is the same as OrderBy / OrderByDescending (alternatively you can pass descending as a boolean). You can write:
list.OrderByPreserve(x => x.A).OrderByPreserve(x => x.B)
which has the same effect as:
list.OrderBy(x => x.A).ThenBy(x => x.B)
Thus you could easily use keyboardP's solution with an arbitrary list of property names:
public static IEnumerable<TSource> OrderByProperties<TSource>(IEnumerable<TSource> source, IEnumerable<string> propertyNames) {
IEnumerable<TSource> result = source;
foreach (var propertyName in propertyNames) {
var localPropertyName = propertyName;
result = result.OrderByPreserve(x => x.GetType().GetProperty(localPropertyName).GetValue(x, null));
}
return result;
}
(the localPropertyName variable is used here because the iteration variable will have changed by the time the query is executed -- see this question for details)
A possible issue with this is that the reflection operations will be executed for each item. It may be better to build a LINQ expression for each property beforehand so they can be called efficiently (this code requires the System.Linq.Expressions namespace):
public static IEnumerable<TSource> OrderByProperties<TSource>(IEnumerable<TSource> source, IEnumerable<string> propertyNames) {
IEnumerable<TSource> result = source;
var sourceType = typeof(TSource);
foreach (var propertyName in propertyNames) {
var parameterExpression = Expression.Parameter(sourceType, "x");
var propertyExpression = Expression.Property(parameterExpression, propertyName);
var castExpression = Expression.Convert(propertyExpression, typeof(object));
var lambdaExpression = Expression.Lambda<Func<TSource, object>>(castExpression, new[] { parameterExpression });
var keySelector = lambdaExpression.Compile();
result = result.OrderByPreserve(keySelector);
}
return result;
}
Essentially what those Expression lines are doing is building the expression x => (object)x.A (where "A" is the current property name), which is then used as the ordering key selector.
Example usage would be:
var propertyNames = new List<string>() { "Title", "Artist" };
var sortedList = OrderByProperties(list, propertyNames).ToList();
You just need to add the ascending / descending logic.

How to get the index of an element in an IEnumerable?

I wrote this:
public static class EnumerableExtensions
{
public static int IndexOf<T>(this IEnumerable<T> obj, T value)
{
return obj
.Select((a, i) => (a.Equals(value)) ? i : -1)
.Max();
}
public static int IndexOf<T>(this IEnumerable<T> obj, T value
, IEqualityComparer<T> comparer)
{
return obj
.Select((a, i) => (comparer.Equals(a, value)) ? i : -1)
.Max();
}
}
But I don't know if it already exists, does it?
I'd question the wisdom, but perhaps:
source.TakeWhile(x => x != value).Count();
(using EqualityComparer<T>.Default to emulate != if needed) - but you need to watch to return -1 if not found... so perhaps just do it the long way
public static int IndexOf<T>(this IEnumerable<T> source, T value)
{
int index = 0;
var comparer = EqualityComparer<T>.Default; // or pass in as a parameter
foreach (T item in source)
{
if (comparer.Equals(item, value)) return index;
index++;
}
return -1;
}
The whole point of getting things out as IEnumerable is so you can lazily iterate over the contents. As such, there isn't really a concept of an index. What you are doing really doesn't make a lot of sense for an IEnumerable. If you need something that supports access by index, put it in an actual list or collection.
I would implement it like this:
public static class EnumerableExtensions
{
public static int IndexOf<T>(this IEnumerable<T> obj, T value)
{
return obj.IndexOf(value, null);
}
public static int IndexOf<T>(this IEnumerable<T> obj, T value, IEqualityComparer<T> comparer)
{
comparer = comparer ?? EqualityComparer<T>.Default;
var found = obj
.Select((a, i) => new { a, i })
.FirstOrDefault(x => comparer.Equals(x.a, value));
return found == null ? -1 : found.i;
}
}
The way I'm currently doing this is a bit shorter than those already suggested and as far as I can tell gives the desired result:
var index = haystack.ToList().IndexOf(needle);
It's a bit clunky, but it does the job and is fairly concise.
I think the best option is to implement like this:
public static int IndexOf<T>(this IEnumerable<T> enumerable, T element, IEqualityComparer<T> comparer = null)
{
int i = 0;
comparer = comparer ?? EqualityComparer<T>.Default;
foreach (var currentElement in enumerable)
{
if (comparer.Equals(currentElement, element))
{
return i;
}
i++;
}
return -1;
}
It will also not create the anonymous object
The best way to catch the position is by FindIndex This function is available only for List<>
Example
int id = listMyObject.FindIndex(x => x.Id == 15);
If you have enumerator or array use this way
int id = myEnumerator.ToList().FindIndex(x => x.Id == 15);
or
int id = myArray.ToList().FindIndex(x => x.Id == 15);
A bit late in the game, i know... but this is what i recently did. It is slightly different than yours, but allows the programmer to dictate what the equality operation needs to be (predicate). Which i find very useful when dealing with different types, since i then have a generic way of doing it regardless of object type and <T> built in equality operator.
It also has a very very small memory footprint, and is very, very fast/efficient... if you care about that.
At worse, you'll just add this to your list of extensions.
Anyway... here it is.
public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
int retval = -1;
var enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
retval += 1;
if (predicate(enumerator.Current))
{
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
return retval;
}
}
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
return -1;
}
Hopefully this helps someone.
A few years later, but this uses Linq, returns -1 if not found, doesn't create extra objects, and should short-circuit when found [as opposed to iterating over the entire IEnumerable]:
public static int IndexOf<T>(this IEnumerable<T> list, T item)
{
return list.Select((x, index) => EqualityComparer<T>.Default.Equals(item, x)
? index
: -1)
.FirstOr(x => x != -1, -1);
}
Where 'FirstOr' is:
public static T FirstOr<T>(this IEnumerable<T> source, T alternate)
{
return source.DefaultIfEmpty(alternate)
.First();
}
public static T FirstOr<T>(this IEnumerable<T> source, Func<T, bool> predicate, T alternate)
{
return source.Where(predicate)
.FirstOr(alternate);
}
Stumbled across this today in a search for answers and I thought I'd add my version to the list (No pun intended). It utlises the null conditional operator of c#6.0
IEnumerable<Item> collection = GetTheCollection();
var index = collection
.Select((item,idx) => new { Item = item, Index = idx })
//or .FirstOrDefault(_ => _.Item.Prop == something)
.FirstOrDefault(_ => _.Item == itemToFind)?.Index ?? -1;
I've done some 'racing of the old horses' (testing) and for large collections (~100,000), worst case scenario that item you want is at the end, this is 2x faster than doing ToList().FindIndex(). If the Item you want is in the middle its ~4x faster.
For smaller collections (~10,000) it seems to be only marginally faster
Heres how I tested it https://gist.github.com/insulind/16310945247fcf13ba186a45734f254e
An alternative to finding the index after the fact is to wrap the Enumerable, somewhat similar to using the Linq GroupBy() method.
public static class IndexedEnumerable
{
public static IndexedEnumerable<T> ToIndexed<T>(this IEnumerable<T> items)
{
return IndexedEnumerable<T>.Create(items);
}
}
public class IndexedEnumerable<T> : IEnumerable<IndexedEnumerable<T>.IndexedItem>
{
private readonly IEnumerable<IndexedItem> _items;
public IndexedEnumerable(IEnumerable<IndexedItem> items)
{
_items = items;
}
public class IndexedItem
{
public IndexedItem(int index, T value)
{
Index = index;
Value = value;
}
public T Value { get; private set; }
public int Index { get; private set; }
}
public static IndexedEnumerable<T> Create(IEnumerable<T> items)
{
return new IndexedEnumerable<T>(items.Select((item, index) => new IndexedItem(index, item)));
}
public IEnumerator<IndexedItem> GetEnumerator()
{
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Which gives a use case of:
var items = new[] {1, 2, 3};
var indexedItems = items.ToIndexed();
foreach (var item in indexedItems)
{
Console.WriteLine("items[{0}] = {1}", item.Index, item.Value);
}
This can get really cool with an extension (functioning as a proxy), for example:
collection.SelectWithIndex();
// vs.
collection.Select((item, index) => item);
Which will automagically assign indexes to the collection accessible via this Index property.
Interface:
public interface IIndexable
{
int Index { get; set; }
}
Custom extension (probably most useful for working with EF and DbContext):
public static class EnumerableXtensions
{
public static IEnumerable<TModel> SelectWithIndex<TModel>(
this IEnumerable<TModel> collection) where TModel : class, IIndexable
{
return collection.Select((item, index) =>
{
item.Index = index;
return item;
});
}
}
public class SomeModelDTO : IIndexable
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int Index { get; set; }
}
// In a method
var items = from a in db.SomeTable
where a.Id == someValue
select new SomeModelDTO
{
Id = a.Id,
Name = a.Name,
Price = a.Price
};
return items.SelectWithIndex()
.OrderBy(m => m.Name)
.Skip(pageStart)
.Take(pageSize)
.ToList();
Try this:
static int FindIndex<T>(this IEnumerable<T> a, Predicate<T> f) =>
a.TakeWhile(x => !f(x)).Count();
static int IndexOf<T>(this IEnumerable<T> a, T value) =>
a.FindIndex(x => EqualityComparer<T>.Default.Equals(x, value));
var i = new[] { 1, 2, 3 }.IndexOf(2); // 1

Categories