Skip to content

Instantly share code, notes, and snippets.

@neremin
Last active April 10, 2018 14:16
Show Gist options
  • Save neremin/dd6f69974786babeba8c to your computer and use it in GitHub Desktop.
Save neremin/dd6f69974786babeba8c to your computer and use it in GitHub Desktop.
LINQ extensions
public static class LinqExtensions
{
/// <summary>
/// Final method, instructing to iterate through sequence elements immediately.
/// </summary>
/// <typeparam name="T">Type of sequence element.</typeparam>
/// <param name="source">Processed sequence.</param>
public static void Do<T>(this IEnumerable<T> source)
{
using (var e = source.GetEnumerator())
{
while (e.MoveNext()) ;
}
}
/// <summary>
/// Invokes <paramref name="action"/> for each item in sequence.
/// </summary>
/// <typeparam name="T">Type of sequence element.</typeparam>
/// <param name="source">Processed sequence.</param>
/// <param name="action">Action delegate.</param>
/// <returns>Sequence of modified elements.</returns>
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
{
action(item);
yield return item;
}
}
/// <summary>
/// Returns sub-sequence from <paramref name="start"/> to <paramref name="end"/> elements inclusively.
/// </summary>
/// <typeparam name="T">Type of sequence element.</typeparam>
/// <param name="source">Initial sequence.</param>
/// <param name="start">New sub-sequence first element condition.</param>
/// <param name="end">New sub-sequence last element condition.</param>
/// <returns>New sub-sequence.</returns>
public static IEnumerable<T> SubRange<T>(this IEnumerable<T> source, Predicate<T> start, Predicate<T> end)
{
using (var e = source.GetEnumerator())
{
bool subrange = false;
while (e.MoveNext())
{
var c = e.Current;
if (!subrange && start(c))
{
subrange = true;
}
if (subrange) yield return c;
if (subrange && end(c))
{
//subrange = false;
break;
}
}
}
}
/// <devdoc>
/// Y-Combinator for Lambda recursion.
/// Based on http://www.codeproject.com/Articles/68978/Recursively-Traversing-a-Hierarchical-Data-Structu?msg=3435479#xx3435479xx
/// </devdoc>
static Func<A, A> Y<A>(Func<Func<A, A>, Func<A, A>> F) { return a => F(Y(F))(a); }
static IEnumerable<T> SelectSubTreeIterator<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> selector, Predicate<T> canSelect)
{
foreach (T element in source)
{
if (canSelect(element))
{
yield return element;
foreach (T subElement in selector(element))
{
yield return subElement;
}
}
}
}
/// <summary>
/// Traverse items tree and returns items matching <paramref name="canSelect"/>.
/// </summary>
/// <remarks>
/// If node does not match <paramref name="canSelect"/> this node sub-tree is skipped.
/// </remarks>
/// <typeparam name="T">Type of tree node.</typeparam>
/// <param name="source">Root nodes collection.</param>
/// <param name="selector">Returns children of the given item.</param>
/// <paramref name="canSelect">Predicate for filtering matching nodes.</paramref>
/// <returns>Items including <paramref name="source"/> and their sub-trees matching <paramref name="canSelect"/>.</returns>
/// <remarks>
/// Search enabled controls by Name:
/// <code>
/// Control root;
/// root.Controls
/// .Cast&lt;Control&gt;()
/// .Traverse
/// (
/// c => c.Controls.Cast&lt;Control&gt;(),
/// c => c.Enabled
/// )
/// .Where(c => c.Name == "Searched name");
/// </code>
/// </remarks>
public static IEnumerable<T> Traverse<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector, Predicate<T> canSelect)
{
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(selector != null);
Contract.Requires<ArgumentNullException>(canSelect != null);
return Y<IEnumerable<T>>
(
f => items => SelectSubTreeIterator(items, item => f(selector(item)), canSelect)
)
(source);
}
/// <summary>
/// Traverse items tree and returns items matching <paramref name="canSelect"/>.
/// </summary>
/// <remarks>
/// If node does not match <paramref name="canSelect"/> this node sub-tree is skipped.
/// </remarks>
/// <typeparam name="T">Type of tree node.</typeparam>
/// <param name="root">Tree root node.</param>
/// <param name="selector">Returns children of the given item.</param>
/// <paramref name="canSelect">Predicate for filtering matching nodes.</paramref>
/// <returns>Sub-trees nodes matching <paramref name="canSelect"/>, except <paramref name="root"/>.</returns>
/// <remarks>
/// Search enabled controls by Name:
/// <code>
/// Control root;
/// root.Traverse
/// (
/// c => c.Controls.Cast&lt;Control&gt;(),
/// c => c.Enabled
/// )
/// .Where(c => c.Name == "Searched name");
/// </code>
/// </remarks>
public static IEnumerable<T> Traverse<T>(this T root, Func<T, IEnumerable<T>> selector, Predicate<T> canSelect)
{
Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(selector != null);
return selector(root).Traverse(selector, canSelect);
}
/// <summary>
/// Traverse items tree and returns all items.
/// </summary>
/// <typeparam name="T">Type of tree node.</typeparam>
/// <param name="source">Root nodes collection.</param>
/// <param name="selector">Returns children of the given item.</param>
/// <returns>All items in the tree, including <paramref name="source"/>.</returns>
/// <remarks>
/// Search all controls by Name:
/// <code>
/// Control root;
/// root.Controls
/// .Cast&lt;Control&gt;()
/// .Traverse
/// (
/// c => c.Controls.Cast&lt;Control&gt;(),
/// )
/// .Where(c => c.Name == "Searched name");
/// </code>
/// </remarks>
public static IEnumerable<T> TraverseAll<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> selector)
{
return source.Traverse(selector, e => true);
}
/// <summary>
/// Traverse items tree and returns all items.
/// </summary>
/// <typeparam name="T">Type of tree node.</typeparam>
/// <param name="root">Tree root node.</param>
/// <param name="selector">Returns children of the given item.</param>
/// <returns>All items except <paramref name="root"/>.</returns>
/// <remarks>
/// Search all controls by Name:
/// <code>
/// Control root;
/// root.Traverse
/// (
/// c => c.Controls.Cast&lt;Control&gt;(),
/// )
/// .Where(c => c.Name == "Searched name");
/// </code>
/// </remarks>
public static IEnumerable<T> TraverseAll<T>(this T root, Func<T, IEnumerable<T>> selector)
{
return root.Traverse(selector, e => true);
}
/// <summary>
/// Prepends element to the sequence.
/// </summary>
/// <typeparam name="T">Type of items.</typeparam>
/// <param name="tail">Appended sequence.</param>
/// <param name="head">Prepending element.</param>
/// <returns>Concatenated sequence with the <paramref name="head"/> as the first element.</returns>
/// <remarks>
/// <code>
/// Enumrable.Empty&lt;int&gt;().Prepend(10);
/// </code>
/// </remarks>
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> tail, T head)
{
yield return head;
foreach (var item in tail)
{
yield return item;
}
}
/// <summary>
/// Appends element to the sequence.
/// </summary>
/// <typeparam name="T">Type of sequence elements.</typeparam>
/// <param name="tail">Appended element.</param>
/// <param name="head">Prepending elements sequence.</param>
/// <returns>Concatenated sequence with the <paramref name="tail"/> as the last element.</returns>
/// <remarks>
/// <code>
/// Enumrable.Empty&lt;int&gt;().Append(10);
/// </code>
/// </remarks>
public static IEnumerable<T> Append<T>(this IEnumerable<T> head, T tail)
{
foreach (var item in head)
{
yield return item;
}
yield return tail;
}
/// <summary>
/// Converts sequence into HashSet.
/// </summary>
/// <typeparam name="T">Time of sequence element.</typeparam>
/// <param name="source">Source sequence.</param>
/// <returns><see cref="HashSet{T}"/> instance, initialized with the seqence elements.</returns>
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
return new HashSet<T>(source);
}
/// <summary>
/// Concatenates the members of a constructed <see cref="IEnumerable{T}"/> collection of type <see cref="String"/>.
/// </summary>
/// <param name="values">A collection that contains the strings to concatenate.</param>
/// <returns>The concatenated strings in values.</returns>
/// <exception cref="ArgumentNullException"><paramref name="values"/> is null.</exception>
public static string Concat(this IEnumerable<string> values)
{
var array = values as string[];
return array == null ? string.Concat(values) : string.Concat(array);
}
/// <summary>
/// Concatenates the members of a constructed <see cref="IEnumerable{T}"/> collection of type <see cref="String"/>,
/// using the specified separator between each member.
/// </summary>
/// <param name="values">A collection that contains the strings to concatenate.</param>
/// <param name="separator">The string to use as a separator.</param>
/// <returns>A string that consists of the members of values delimited by the separator string.
/// If values has no members, the method returns <see cref="String.Empty"/>.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="values"/> is null.</exception>
public static string Join(this IEnumerable<string> values, string separator)
{
var array = values as string[];
return array == null ? string.Join(separator, values) : string.Join(separator, array);
}
/// <summary>
/// Concatenates the members of a constructed <see cref="IEnumerable{T}"/> collection of type <see cref="String"/>,
/// using <see cref="Environment.NewLine"/> between each member.
/// </summary>
/// <param name="lines">A collection that contains the strings to concatenate.</param>
/// <returns>A string that consists of the members of values delimited by the <see cref="Environment.NewLine"/>.
/// If values has no members, the method returns <see cref="String.Empty"/>.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="lines"/> is null.</exception>
public static string JoinLines(this IEnumerable<string> lines)
{
return lines.Join(Environment.NewLine);
}
/// <summary>
/// Select projection of two sibling items in a sequence pairwise.
/// </summary>
/// <typeparam name="TSource">Source sequence item type</typeparam>
/// <typeparam name="TResult">Projected item type</typeparam>
/// <param name="source">Source items sequence.</param>
/// <param name="selector">Projects previous and current item to a new value.</param>
/// <returns>Sequence of projected sibling pairs.</returns>
/// <remarks>
/// Numbers sequence to deltas:
/// <code>
/// var deltas = Enumerable.Range(0, 10000).SelectPairwise((first, second) => second - first)
/// </code>
/// In .NET 4.0+
/// <code>
/// var sequence = Enumerable.Range(0, 10000).ToArray();
/// var deltas = sequence.Zip(sequence.Skip(1), (first, second) => second - first);
/// </code>
/// </remarks>
/// <devdoc>
/// Based on http://stackoverflow.com/a/3970131/550068 and http://stackoverflow.com/a/3683217/550068
/// </devdoc>
public static IEnumerable<TResult> SelectPairwise<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TSource, TResult> selector)
{
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(selector != null);
using (var e = source.GetEnumerator())
{
if (!e.MoveNext())
{
yield break; // throw new InvalidOperationException("Sequence cannot be empty.");
}
var previous = e.Current;
if (!e.MoveNext())
{
yield break; // throw new InvalidOperationException("Sequence must contain at least two elements.");
}
do
{
yield return selector(previous, e.Current);
previous = e.Current;
} while (e.MoveNext());
}
}
}
Imports System.Runtime.CompilerServices
Module LinqExtensions
' Y-combinator for Lambda recursion
' Based on http://www.codeproject.com/Articles/68978/Recursively-Traversing-a-Hierarchical-Data-Structu?msg=3435479#xx3435479xx
Private Function Y(Of A)(ByVal F As Func(Of Func(Of A, A), Func(Of A, A))) As Func(Of A, A)
Return Function(b) F(Y(Of A)(F))(b)
End Function
Private Iterator Function SelectSubTreeIterator(Of T)(ByVal source As IEnumerable(Of T), ByVal selector As Func(Of T, IEnumerable(Of T)), ByVal canSelect As Predicate(Of T)) As IEnumerable(Of T)
For Each element In source
If canSelect(element) Then
Yield element
For Each subElement In selector.Invoke(element)
Yield subElement
Next
End If
Next
End Function
<Extension>
Public Function Traverse(Of T)(ByVal source As IEnumerable(Of T), ByVal selector As Func(Of T, IEnumerable(Of T)), ByVal canSelect As Predicate(Of T)) As IEnumerable(Of T)
Return Y(Of IEnumerable(Of T))(Function(f) Function(items) SelectSubTreeIterator(Of T)(items, Function(item) f(selector(item)), canSelect)).Invoke(source)
End Function
<Extension>
Public Function Traverse(Of T)(ByVal root As T, ByVal selector As Func(Of T, IEnumerable(Of T)), ByVal canSelect As Predicate(Of T)) As IEnumerable(Of T)
Return selector(root).Traverse(selector, canSelect)
End Function
<Extension>
Public Function TraverseAll(Of T)(ByVal source As IEnumerable(Of T), ByVal selector As Func(Of T, IEnumerable(Of T))) As IEnumerable(Of T)
Return source.Traverse(selector, Function(e) True)
End Function
<Extension>
Public Function TraverseAll(Of T)(ByVal root As T, ByVal selector As Func(Of T, IEnumerable(Of T))) As IEnumerable(Of T)
Return root.Traverse(selector, Function(e) True)
End Function
<Extension>
Public Iterator Function Append(Of T)(ByVal head As IEnumerable(Of T), ByVal tail As T) As IEnumerable(Of T)
For Each item In head
Yield item
Next
Yield tail
End Function
<Extension>
Public Function Concat(ByVal values As IEnumerable(Of String)) As IEnumerable(Of String)
Dim array As String() = TryCast(values, String())
Return IIf(array Is Nothing, String.Concat(values), String.Concat(array))
End Function
<Extension>
Public Function Join(ByVal values As IEnumerable(Of String), ByVal separator As String) As String
Dim array As String() = TryCast(values, String())
Return IIf(array Is Nothing, String.Join(separator, values), String.Join(separator, array))
End Function
<Extension>
Public Function JoinLines(ByVal lines As IEnumerable(Of String)) As String
Return lines.Join(Environment.NewLine)
End Function
<Extension>
Public Iterator Function Prepend(Of T)(ByVal tail As IEnumerable(Of T), ByVal head As T) As IEnumerable(Of T)
Yield head
For Each item In tail
Yield item
Next
End Function
<Extension>
Public Function ToHashSet(Of T)(ByVal source As IEnumerable(Of T)) As HashSet(Of T)
Return New HashSet(Of T)(source)
End Function
<Extension>
Public Iterator Function SelectPairwise(Of TSource, TResult)(ByVal source As IEnumerable(Of TSource), ByVal projection As Func(Of TSource, TSource, TResult)) As IEnumerable(Of TResult)
Using e As IEnumerator(Of TSource) = source.GetEnumerator
If e.MoveNext Then
Dim previous As TSource = e.Current
If e.MoveNext Then
Do
Yield projection(previous, e.Current)
previous = e.Current
Loop While e.MoveNext
End If
End If
End Using
End Function
End Module
[TestFixture]
public class LinqExtensionsTests
{
[Test]
public void TraverseAll_maintain_items_order()
{
var nodes = new[]
{
new Node { ID = 1 },
new Node { ID = 2 },
};
nodes.TraverseAll(n => n.Nodes)
.Should()
.ContainInOrder(nodes);
}
[Test]
public void TraverseAll_returns_node_and_children_before_siblings()
{
var nodes = new[]
{
new Node { ID = 1, Nodes = new [] { new Node{ ID = 3 } } },
new Node { ID = 2 },
};
nodes.TraverseAll(n => n.Nodes)
.Should()
.ContainInOrder(new[] { nodes[0], nodes[0].Nodes[0], nodes[1] });
}
[Test]
public void Traverse_skips_filtered_subtree()
{
var nodes = new[]
{
new Node { ID = 1, Nodes = new [] { new Node{ ID = 3 } } },
new Node { ID = 2 },
};
nodes.Traverse(n => n.Nodes, n => n.ID != 1)
.Should()
.ContainInOrder(new[] { nodes[1] });
}
class Node
{
static readonly Node[] EmptyNodes = new Node[0];
public int ID = -1;
public Node[] Nodes = EmptyNodes;
public override bool Equals(object obj)
{
var that = obj as Node;
return ReferenceEquals(that, this);
}
public override string ToString()
{
return Nodes.Length == 0
? string.Concat("{ID = ", ID, ", Children = {", Nodes.Select(n => n.ToString()).Join(", "), "}}")
: string.Concat("{ID = ", ID, "}");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment