NOtherLookup is a set of LINQ-like extensions that are aimed to make working with the splendid ILookup<TKey, TValue>
even easier. It tries to resolve two main ILookup
's deficiencies - lack of easy way to create it from the scratch and lack of easy way to apply transformations.
- Creating
ILookup
- Manipulating single
ILookup
- Manipulating two
ILookup
sConcat
- concatenates values for each keyUnion
- gets the unique values for each keyExcept
- gets the difference of values set for each keyIntersect
- gets the intersection of values set for each keyJoin
- combines two lookups by values in each key using provided selectorZip
- combines two lookups by pairs of values using provided selector for each key
.NET's Lookup
class has no public constructor. NOtherLookup offers several ways to obtain an ILookup<TKey, TValue>
instance.
Useful to keep the code clean and obvious.
ILookup<int, string> emptyLookup = Lookup.Empty<int, string>();
ILookup<int, string> lookup = Lookup.Builder
.WithKey(1, new[] { "a", "b" })
.WithKey(2, new[] { "c", "d" })
.Build();
Allows specifying a custom key comparer:
ILookup<int, string> lookup = Lookup.Builder
.WithComparer(new CustomComparer())
.WithKey(1, new[] { "a", "b" })
.WithKey(2, new[] { "c", "d" })
.Build();
IDictionary<int, string[]> sourceDictionary = new Dictionary<int, string[]>()
{
{ 1, new[] { "a", "b" }},
{ 2, new[] { "c", "d" }}
};
Converting dictionaries to lookups works for multiple types of TValue
collections - TValue[]
, ICollection<TValue>
and IList<TValue>
- as well as for IEnumerable<IGrouping<TKey, TValue>>
(decomposed lookup).
ILookup<int, string> lookup = sourceDictionary.ToLookup();
And back to mutable IDictionary
- doable using standard LINQ operators, but quite verbose and convoluted.
Dictionary<int, List<string>> backToDict = lookup.ToDictionary();
Contrary to what we get using standard LINQ operators on ILookup
, all the operators below maintains ILookup
typing.
Lookup instance used in the examples:
ILookup<int, string> lookup = Lookup.Builder
.WithKey(1, new[] { "a", "b" })
.WithKey(2, new[] { "c", "d" })
.Build();
ILookup<int, string> projected = lookup.Select(x => x + "!");
Result:
1 => [a!, b!]
2 => [c!, d!]
ILookup<int, string> filtered = lookup.Select(x => x != "a");
Result:
1 => [b]
2 => [c, d]
It is a generalization for any transformation that is supposed to run on each key in lookup. Note that Select
and Where
can be also easily accomplished through OnEachKey
- they are standalone methods only for convenience.
ILookup<int, string> transformed = lookup.OnEachKey(g => g.Select(x => x + g.Key).Reverse());
Result:
1 => [b1, a1]
2 => [d2, c2]
Contrary to what we get using standard LINQ operators on ILookup
, all the operators below maintains ILookup
typing.
The operators allow specifying custom key comparers, where applicable.
Lookup instances used in the examples:
ILookup<int, string> first = Lookup.Builder
.WithKey(1, new[] { "a", "b" })
.WithKey(2, new[] { "c", "d" })
.Build();
ILookup<int, string> second = Lookup.Builder
.WithKey(1, new[] { "a", "c" })
.WithKey(3, new[] { "e", "f" })
.Build();
ILookup<int, string> concatenated = first.Concat(second);
Result:
1 => [a, b, a, c]
2 => [c, d]
3 => [e, f]
ILookup<int, string> unionized = first.Union(second);
Result:
1 => [a, b, c]
2 => [c, d]
3 => [e, f]
Also supports custom values comparers.
ILookup<int, string> difference = first.Except(second);
Result:
1 => [b]
2 => [c, d]
Also supports custom values comparers.
ILookup<int, string> intersection = first.Intersect(second);
Result:
1 => [a]
Also supports custom values comparers.
ILookup<int, string> joined = first.Join(second, (x, y) => x + y);
Result:
1 => [aa, ac, ba, bc]
ILookup<int, string> zipped = first.Zip(second, (x, y) => x + y);
Result:
1 => [aa, bc]