Here's an implementation of a simpler sort and collect method that Steve avoided.
public static class EnumerableExtensions { public static IEnumerable<IGrouping<double, T>> GroupByWithTolerance<T>(this IEnumerable<T> source, Func<T, double> keySelector, double tolerance) { var orderedSource = source .Select(e => new {Key = keySelector(e), Value = e}) .OrderBy(e => e.Key); if (!orderedSource.Any()) yield break; var prev = orderedSource.First(); var itemGroup = new Group<double, T>(prev.Key) {prev.Value}; foreach (var current in orderedSource.Skip(1)) { if (current.Key - prev.Key <= tolerance) { itemGroup.Add(current.Value); } else { yield return itemGroup; itemGroup = new Group<double, T>(current.Key) {current.Value}; } prev = current; } yield return itemGroup; } private class Group<TKey, TSource> : List<TSource>, IGrouping<TKey, TSource> { public Group(TKey key) { Key = key; } public TKey Key { get; } } }
EDIT
Sample Usage:
[Test] public void Test() { var items = new[] { new Item {Id = 2, Price = 80.0}, new Item {Id = 8, Price = 44.25}, new Item {Id = 14, Price = 43.5}, new Item {Id = 30, Price = 79.98}, new Item {Id = 54, Price = 44.24}, new Item {Id = 74, Price = 80.01} }; var groups = items.GroupByWithTolerance(i => i.Price, 0.02); foreach (var itemGroup in groups) { var groupString = string.Join(", ", itemGroup.Select(i => i.ToString())); System.Console.WriteLine($"{itemGroup.Key} -> {groupString}"); } } private class Item { public int Id { get; set; } public double Price { get; set; } public override string ToString() => $"[ID: {Id}, Price: {Price}]"; }
Output:
43.5 -> [ID: 14, Price: 43.5] 44.24 -> [ID: 54, Price: 44.24], [ID: 8, Price: 44.25] 79.98 -> [ID: 30, Price: 79.98], [ID: 2, Price: 80], [ID: 74, Price: 80.01]