Skip to content

Commit 69eae7c

Browse files
committed
Enumerable.IsFirst/IsLast
1 parent 79f8a0a commit 69eae7c

File tree

3 files changed

+177
-2
lines changed

3 files changed

+177
-2
lines changed

Main/src/Collections/CollectionExtensions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32

43
using JetBrains.Annotations;
54

Main/src/Collections/Enumerable/EnumerableExtensions.cs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,5 +438,131 @@ private static IEnumerable<string> ToStringsCore<T>(IEnumerable<T> source)
438438
foreach (var obj in source)
439439
yield return obj?.ToString() ?? "";
440440
}
441+
442+
/// <summary>
443+
/// Checks, if <paramref name="item"/> is first element of <paramref name="source"/>.
444+
/// </summary>
445+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
446+
/// <param name="source">An <see cref="IEnumerable{T}"/> to check.</param>
447+
/// <param name="item">Source item to compare with first element.</param>
448+
/// <returns>
449+
/// <c>true</c>, if <paramref name="source"/> has at least one element and first element is equals to
450+
/// <paramref name="item"/>, otherwise <c>false</c>.
451+
/// </returns>
452+
public static bool IsFirst<TSource>([NotNull] this IEnumerable<TSource> source, TSource item)
453+
{
454+
Code.NotNull(source, nameof(source));
455+
456+
// Fast path
457+
// ReSharper disable once CollectionNeverUpdated.Local
458+
if (source is IList<TSource> list)
459+
return Equals(item, list[0]);
460+
461+
foreach (var current in source)
462+
return Equals(item, current);
463+
return false;
464+
}
465+
466+
/// <summary>
467+
/// Checks, if <paramref name="item"/> is first element of <paramref name="source"/>.
468+
/// </summary>
469+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
470+
/// <param name="source">An <see cref="IEnumerable{T}"/> to check.</param>
471+
/// <param name="item">Source item to compare with first element.</param>
472+
/// <param name="comparer">The comparer.</param>
473+
/// <returns>
474+
/// <c>true</c>, if <paramref name="source"/> has at least one element and first element is equals to
475+
/// <paramref name="item"/>, otherwise <c>false</c>.
476+
/// </returns>
477+
public static bool IsFirst<TSource>(
478+
[NotNull] this IEnumerable<TSource> source,
479+
TSource item,
480+
[CanBeNull] IEqualityComparer<TSource> comparer)
481+
{
482+
Code.NotNull(source, nameof(source));
483+
484+
comparer = comparer ?? EqualityComparer<TSource>.Default;
485+
486+
// Fast path
487+
// ReSharper disable once CollectionNeverUpdated.Local
488+
if (source is IList<TSource> list)
489+
return comparer.Equals(item, list[0]);
490+
491+
foreach (var current in source)
492+
return comparer.Equals(item, current);
493+
return false;
494+
}
495+
496+
/// <summary>
497+
/// Checks, if <paramref name="item"/> is last element of <paramref name="source"/>.
498+
/// </summary>
499+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
500+
/// <param name="source">An <see cref="IEnumerable{T}"/> to check.</param>
501+
/// <param name="item">Source item to compare with last element.</param>
502+
/// <returns>
503+
/// <c>true</c>, if <paramref name="source"/> has at least one element and last element is equals to
504+
/// <paramref name="item"/>, otherwise <c>false</c>.
505+
/// </returns>
506+
public static bool IsLast<TSource>([NotNull] this IEnumerable<TSource> source, TSource item)
507+
{
508+
Code.NotNull(source, nameof(source));
509+
510+
// Fast path
511+
// ReSharper disable once CollectionNeverUpdated.Local
512+
if (source is IList<TSource> list)
513+
return Equals(item, list[list.Count - 1]);
514+
515+
using (var en = source.GetEnumerator())
516+
if (en.MoveNext())
517+
{
518+
TSource current;
519+
do
520+
{
521+
current = en.Current;
522+
} while (en.MoveNext());
523+
return Equals(item, current);
524+
}
525+
else
526+
return false;
527+
}
528+
529+
/// <summary>
530+
/// Checks, if <paramref name="item"/> is last element of <paramref name="source"/>.
531+
/// </summary>
532+
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
533+
/// <param name="source">An <see cref="IEnumerable{T}"/> to check.</param>
534+
/// <param name="item">Source item to compare with last element.</param>
535+
/// <param name="comparer">The comparer.</param>
536+
/// <returns>
537+
/// <c>true</c>, if <paramref name="source"/> has at least one element and last element is equals to
538+
/// <paramref name="item"/>, otherwise <c>false</c>.
539+
/// </returns>
540+
public static bool IsLast<TSource>(
541+
[NotNull] this IEnumerable<TSource> source,
542+
TSource item,
543+
[CanBeNull] IEqualityComparer<TSource> comparer)
544+
{
545+
Code.NotNull(source, nameof(source));
546+
547+
comparer = comparer ?? EqualityComparer<TSource>.Default;
548+
549+
// Fast path
550+
// ReSharper disable once CollectionNeverUpdated.Local
551+
if (source is IList<TSource> list)
552+
return comparer.Equals(item, list[list.Count - 1]);
553+
554+
using (var en = source.GetEnumerator())
555+
if (en.MoveNext())
556+
{
557+
TSource current;
558+
do
559+
{
560+
current = en.Current;
561+
} while (en.MoveNext());
562+
return comparer.Equals(item, current);
563+
}
564+
else
565+
return false;
566+
}
441567
}
442568
}

Main/tests/Collections/Enumerable/EnumerableExtensionTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,55 @@ public string Prepend1(string[] input, string prepend)
3030
[TestCase(new[] {"1", "2"}, new[] {"-1", "0"}, TestName = "Prepend2 2", ExpectedResult = "-1, 0, 1, 2")]
3131
public string Prepend(string[] input, string[] prepend)
3232
=> input.Prepend(prepend).Join(", ");
33+
34+
[Test]
35+
public void IsFirst()
36+
{
37+
var src = new[] { "a", "b", "c" };
38+
39+
// Fast path
40+
Assert.IsTrue(src.IsFirst("a"), "#A01");
41+
Assert.IsFalse(src.IsFirst("b"), "#A02");
42+
43+
Assert.IsTrue(src.IsFirst("a", null), "#A03");
44+
Assert.IsFalse(src.IsFirst("A", null), "#A04");
45+
Assert.IsTrue(src.IsFirst("A", StringComparer.OrdinalIgnoreCase), "#A05");
46+
47+
// Slow path
48+
var enSrc = src.Select(i => i);
49+
// ReSharper disable PossibleMultipleEnumeration
50+
Assert.IsTrue(enSrc.IsFirst("a"), "#A06");
51+
Assert.IsFalse(enSrc.IsFirst("b"), "#A07");
52+
53+
Assert.IsTrue(enSrc.IsFirst("a", null), "#A08");
54+
Assert.IsFalse(enSrc.IsFirst("A", null), "#A09");
55+
Assert.IsTrue(enSrc.IsFirst("A", StringComparer.OrdinalIgnoreCase), "#A10");
56+
// ReSharper restore PossibleMultipleEnumeration
57+
}
58+
59+
[Test]
60+
public void IsLast()
61+
{
62+
var src = new[] { "a", "b", "c" };
63+
64+
// Fast path
65+
Assert.IsTrue(src.IsLast("c"), "#A01");
66+
Assert.IsFalse(src.IsLast("b"), "#A02");
67+
68+
Assert.IsTrue(src.IsLast("c", null), "#A03");
69+
Assert.IsFalse(src.IsLast("C", null), "#A04");
70+
Assert.IsTrue(src.IsLast("C", StringComparer.OrdinalIgnoreCase), "#A05");
71+
72+
// Slow path
73+
var enSrc = src.Select(i => i);
74+
// ReSharper disable PossibleMultipleEnumeration
75+
Assert.IsTrue(enSrc.IsLast("c"), "#A06");
76+
Assert.IsFalse(enSrc.IsLast("b"), "#A07");
77+
78+
Assert.IsTrue(enSrc.IsLast("c", null), "#A08");
79+
Assert.IsFalse(enSrc.IsLast("C", null), "#A09");
80+
Assert.IsTrue(enSrc.IsLast("C", StringComparer.OrdinalIgnoreCase), "#A10");
81+
// ReSharper restore PossibleMultipleEnumeration
82+
}
3383
}
3484
}

0 commit comments

Comments
 (0)