Skip to content

Commit 97886cb

Browse files
committed
ToLookup lookup implements ICollection #86
1 parent 4faec3a commit 97886cb

File tree

2 files changed

+129
-1
lines changed

2 files changed

+129
-1
lines changed

src/ZLinq/Linq/ToLookup.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,8 @@ void ResizeAndRehash()
315315
}
316316
}
317317

318-
internal sealed class Lookup<TKey, TElement> : ILookup<TKey, TElement>
318+
// .NET ILookup implements ICollection
319+
internal sealed class Lookup<TKey, TElement> : ILookup<TKey, TElement>, ICollection<IGrouping<TKey, TElement>>, IReadOnlyCollection<IGrouping<TKey, TElement>>
319320
{
320321
internal static readonly Lookup<TKey, TElement> Empty = new Lookup<TKey, TElement>();
321322

@@ -429,6 +430,46 @@ uint InternalGetHashCode(TKey key)
429430
// allows null.
430431
return (uint)((key is null) ? 0 : comparer.GetHashCode(key) & 0x7FFFFFFF);
431432
}
433+
434+
// ICollection
435+
436+
bool ICollection<IGrouping<TKey, TElement>>.IsReadOnly => true;
437+
438+
void ICollection<IGrouping<TKey, TElement>>.Add(IGrouping<TKey, TElement> item) => throw new NotSupportedException();
439+
bool ICollection<IGrouping<TKey, TElement>>.Remove(IGrouping<TKey, TElement> item) => throw new NotSupportedException();
440+
void ICollection<IGrouping<TKey, TElement>>.Clear() => throw new NotSupportedException();
441+
442+
bool ICollection<IGrouping<TKey, TElement>>.Contains(IGrouping<TKey, TElement> item)
443+
{
444+
var group = GetGroup(item.Key);
445+
if (group != null && group == item)
446+
{
447+
return true;
448+
}
449+
return false;
450+
}
451+
452+
void ICollection<IGrouping<TKey, TElement>>.CopyTo(IGrouping<TKey, TElement>[] array, int arrayIndex)
453+
{
454+
ArgumentNullException.ThrowIfNull(array);
455+
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Non-negative number required.");
456+
if (arrayIndex > array.Length) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Index was out of range. Must be non-negative and less than the size of the collection.");
457+
if (array.Length - arrayIndex < Count) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Destination array is not long enough to copy all the items in the collection. Check array index and length.");
458+
459+
if (last is null) return;
460+
461+
var group = last.NextGroupInAddOrder; // as first.
462+
if (group is null) return;
463+
464+
var first = group;
465+
do
466+
{
467+
array[arrayIndex] = group;
468+
++arrayIndex;
469+
470+
group = group.NextGroupInAddOrder;
471+
} while (group != null && group != first);
472+
}
432473
}
433474

434475
[DebuggerDisplay("Key = {Key}, ({Count})")]

tests/ZLinq.Tests/Linq/ToLookupTest.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,91 @@ public void ElementSelectorTransformation()
174174
lookup[3].ShouldBe(new[] { "CCC" });
175175
lookup[4].ShouldBe(new[] { "DDDD" });
176176
}
177+
178+
[Fact]
179+
public void ICollectionContains()
180+
{
181+
var xs = new[] { 1, 2, 3, 4, 5 };
182+
var lookup = xs.AsValueEnumerable().ToLookup(x => x);
183+
184+
// Cast to ICollection to test the explicit interface implementation
185+
var collection = (ICollection<IGrouping<int, int>>)lookup;
186+
187+
// Contains should return true only when the exact same grouping instance is passed
188+
foreach (var group in collection)
189+
{
190+
collection.Contains(group).ShouldBeTrue();
191+
}
192+
193+
// A different grouping with the same key should return false
194+
var otherLookup = xs.AsValueEnumerable().ToLookup(x => x);
195+
foreach (var group in otherLookup)
196+
{
197+
collection.Contains(group).ShouldBeFalse();
198+
}
199+
}
200+
201+
[Fact]
202+
public void ICollectionCopyTo()
203+
{
204+
var xs = new[] { 1, 2, 3, 4, 5 };
205+
var lookup = xs.AsValueEnumerable().ToLookup(x => x);
206+
207+
// Cast to ICollection to test the explicit interface implementation
208+
var collection = (ICollection<IGrouping<int, int>>)lookup;
209+
210+
// Create array to copy to
211+
var array = new IGrouping<int, int>[collection.Count];
212+
collection.CopyTo(array, 0);
213+
214+
// Verify that all elements were copied
215+
var i = 0;
216+
foreach (var group in lookup)
217+
{
218+
array[i++].ShouldBe(group);
219+
}
220+
221+
// Test with offset
222+
var largerArray = new IGrouping<int, int>[collection.Count + 2];
223+
collection.CopyTo(largerArray, 1);
224+
225+
largerArray[0].ShouldBeNull();
226+
i = 0;
227+
foreach (var group in lookup)
228+
{
229+
largerArray[i + 1].ShouldBe(group);
230+
i++;
231+
}
232+
largerArray[collection.Count + 1].ShouldBeNull();
233+
}
234+
235+
[Fact]
236+
public void ICollectionCopyToExceptions()
237+
{
238+
var xs = new[] { 1, 2, 3, 4, 5 };
239+
var lookup = xs.AsValueEnumerable().ToLookup(x => x);
240+
241+
// Cast to ICollection to test the explicit interface implementation
242+
var collection = (ICollection<IGrouping<int, int>>)lookup;
243+
244+
// Should throw when array is null
245+
Should.Throw<ArgumentNullException>(() => collection.CopyTo(null!, 0));
246+
247+
// Should throw when index is negative
248+
Should.Throw<ArgumentOutOfRangeException>(() =>
249+
collection.CopyTo(new IGrouping<int, int>[collection.Count], -1));
250+
251+
// Should throw when index is greater than array length
252+
Should.Throw<ArgumentOutOfRangeException>(() =>
253+
collection.CopyTo(new IGrouping<int, int>[collection.Count], collection.Count + 1));
254+
255+
// Should throw when array is too small
256+
Should.Throw<ArgumentOutOfRangeException>(() =>
257+
collection.CopyTo(new IGrouping<int, int>[collection.Count - 1], 0));
258+
259+
// Should throw when array with offset is too small
260+
Should.Throw<ArgumentOutOfRangeException>(() =>
261+
collection.CopyTo(new IGrouping<int, int>[collection.Count], 1));
262+
}
263+
177264
}

0 commit comments

Comments
 (0)