using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace System.Linq.Internal { internal class Lookup : ILookup { private readonly IEqualityComparer _comparer; private Grouping[] _groupings; private Grouping _lastGrouping; private int _count; internal static Lookup Create(IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) { Debug.Assert(source != null); Debug.Assert(keySelector != null); Debug.Assert(elementSelector != null); Lookup lookup = new Lookup(comparer); foreach (TSource item in source) { lookup.GetGrouping(keySelector(item), create: true).Add(elementSelector(item)); } return lookup; } internal static async Task> CreateAsync(IAsyncEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer, CancellationToken cancellationToken) { Debug.Assert(source != null); Debug.Assert(keySelector != null); Debug.Assert(elementSelector != null); Lookup lookup = new Lookup(comparer); using (var enu = source.GetEnumerator()) { while (await enu.MoveNext(cancellationToken) .ConfigureAwait(false)) { lookup.GetGrouping(keySelector(enu.Current), create: true).Add(elementSelector(enu.Current)); } } return lookup; } internal static Lookup Create(IEnumerable source, Func keySelector, IEqualityComparer comparer) { Debug.Assert(source != null); Debug.Assert(keySelector != null); Lookup lookup = new Lookup(comparer); foreach (TElement item in source) { lookup.GetGrouping(keySelector(item), create: true).Add(item); } return lookup; } internal static Lookup CreateForJoin(IEnumerable source, Func keySelector, IEqualityComparer comparer) { Lookup lookup = new Lookup(comparer); foreach (TElement item in source) { TKey key = keySelector(item); if (key != null) { lookup.GetGrouping(key, create: true).Add(item); } } return lookup; } internal static async Task> CreateForJoinAsync(IAsyncEnumerable source, Func keySelector, IEqualityComparer comparer, CancellationToken cancellationToken) { Lookup lookup = new Lookup(comparer); using (var enu = source.GetEnumerator()) { while (await enu.MoveNext(cancellationToken) .ConfigureAwait(false)) { TKey key = keySelector(enu.Current); if (key != null) { lookup.GetGrouping(key, create: true).Add(enu.Current); } } } return lookup; } private Lookup(IEqualityComparer comparer) { _comparer = comparer ?? EqualityComparer.Default; _groupings = new Grouping[7]; } public int Count { get { return _count; } } public IEnumerable this[TKey key] { get { Grouping grouping = GetGrouping(key, create: false); if (grouping != null) { return grouping; } #if NO_ARRAY_EMPTY return EmptyArray.Value; #else return Array.Empty(); #endif } } public bool Contains(TKey key) { return GetGrouping(key, create: false) != null; } public IEnumerator> GetEnumerator() { Grouping g = _lastGrouping; if (g != null) { do { g = g._next; yield return g; } while (g != _lastGrouping); } } internal TResult[] ToArray(Func, TResult> resultSelector) { TResult[] array = new TResult[_count]; int index = 0; Grouping g = _lastGrouping; if (g != null) { do { g = g._next; g.Trim(); array[index] = resultSelector(g._key, g._elements); ++index; } while (g != _lastGrouping); } return array; } internal List ToList(Func, TResult> resultSelector) { List list = new List(_count); Grouping g = _lastGrouping; if (g != null) { do { g = g._next; g.Trim(); list.Add(resultSelector(g._key, g._elements)); } while (g != _lastGrouping); } return list; } public IEnumerable ApplyResultSelector(Func, TResult> resultSelector) { Grouping g = _lastGrouping; if (g != null) { do { g = g._next; g.Trim(); yield return resultSelector(g._key, g._elements); } while (g != _lastGrouping); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } internal int InternalGetHashCode(TKey key) { // Handle comparer implementations that throw when passed null return (key == null) ? 0 : _comparer.GetHashCode(key) & 0x7FFFFFFF; } internal Grouping GetGrouping(TKey key, bool create) { int hashCode = InternalGetHashCode(key); for (Grouping g = _groupings[hashCode % _groupings.Length]; g != null; g = g._hashNext) { if (g._hashCode == hashCode && _comparer.Equals(g._key, key)) { return g; } } if (create) { if (_count == _groupings.Length) { Resize(); } int index = hashCode % _groupings.Length; Grouping g = new Grouping(); g._key = key; g._hashCode = hashCode; g._elements = new TElement[1]; g._hashNext = _groupings[index]; _groupings[index] = g; if (_lastGrouping == null) { g._next = g; } else { g._next = _lastGrouping._next; _lastGrouping._next = g; } _lastGrouping = g; _count++; return g; } return null; } private void Resize() { int newSize = checked((_count * 2) + 1); Grouping[] newGroupings = new Grouping[newSize]; Grouping g = _lastGrouping; do { g = g._next; int index = g._hashCode % newSize; g._hashNext = newGroupings[index]; newGroupings[index] = g; } while (g != _lastGrouping); _groupings = newGroupings; } } }