Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ public static partial class MemoryExtensions
public static int CommonPrefixLength<T>(this System.Span<T> span, System.ReadOnlySpan<T> other, System.Collections.Generic.IEqualityComparer<T>? comparer) { throw null; }
public static int CommonPrefixLength<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other) { throw null; }
public static int CommonPrefixLength<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other, System.Collections.Generic.IEqualityComparer<T>? comparer) { throw null; }
public static int CommonSuffixLength<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other) { throw null; }
public static int CommonSuffixLength<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other, System.Collections.Generic.IEqualityComparer<T>? comparer) { throw null; }
public static int CompareTo(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> other, System.StringComparison comparisonType) { throw null; }
public static bool Contains(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> value, System.StringComparison comparisonType) { throw null; }
public static bool Contains<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T>? { throw null; }
Expand Down
71 changes: 71 additions & 0 deletions src/libraries/System.Memory/tests/Span/CommonSuffixLength.T.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using Xunit;

namespace System.SpanTests
{
public static class CommonSuffixLengthTests
{
[Theory]
[InlineData("", "", 0)]
[InlineData("abc", "", 0)]
[InlineData("", "abc", 0)]
[InlineData("abc", "xyz", 0)]
[InlineData("abc", "xbc", 2)]
[InlineData("abc", "abc", 3)]
[InlineData("xabc", "abc", 3)]
[InlineData("abc", "xabc", 3)]
[InlineData("abcdef", "xyzdef", 3)]
public static void Characters(string first, string second, int expected)
{
ReadOnlySpan<char> span = first.AsSpan();
ReadOnlySpan<char> other = second.AsSpan();

Assert.Equal(expected, MemoryExtensions.CommonSuffixLength(span, other));
Assert.Equal(expected, MemoryExtensions.CommonSuffixLength(span, other, null));
Assert.Equal(expected, MemoryExtensions.CommonSuffixLength(span, other, EqualityComparer<char>.Default));
}

[Fact]
public static void ValueTypesAndCustomComparer()
{
int[] first = { 1, 2, 3, 4, 5 };
int[] second = { 9, 3, 4, 5 };

Assert.Equal(3, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<int>)first, second));
Assert.Equal(3, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<int>)first, second, null));
Assert.Equal(3, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<int>)first, second, EqualityComparer<int>.Default));

second = new[] { 9, -3, -4, -5 };
EqualityComparer<int> absoluteValueComparer = EqualityComparer<int>.Create((x, y) => Math.Abs(x) == Math.Abs(y));
Assert.Equal(3, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<int>)first, second, absoluteValueComparer));
Assert.Equal(0, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<int>)first, second));
}

[Fact]
public static void ReferenceTypesAndCustomComparer()
{
string[] first = { "start", "A", null, "end" };
string[] second = { "A", null, "end" };

Assert.Equal(3, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<string>)first, second));
Assert.Equal(3, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<string>)first, second, null));

second = new[] { "a", null, "END" };
Assert.Equal(3, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<string>)first, second, StringComparer.OrdinalIgnoreCase));
Assert.Equal(0, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<string>)first, second));
}

[Fact]
public static void FloatingPointUsesEqualityComparerSemantics()
{
double[] first = { 1.0, double.NaN };
double[] second = { 2.0, double.NaN };

Assert.Equal(1, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<double>)first, second));
Assert.Equal(1, MemoryExtensions.CommonSuffixLength((ReadOnlySpan<double>)first, second, EqualityComparer<double>.Default));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<Compile Include="Span\Fill.cs" />
<Compile Include="Span\Clear.cs" />
<Compile Include="Span\CommonPrefixLength.T.cs" />
<Compile Include="Span\CommonSuffixLength.T.cs" />
<Compile Include="Span\Contains.T.cs" />
<Compile Include="Span\CopyTo.cs" />
<Compile Include="Span\CtorArray.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5006,6 +5006,57 @@ public static int CommonPrefixLength<T>(this ReadOnlySpan<T> span, ReadOnlySpan<
return span.Length;
}

/// <summary>Finds the length of any common suffix shared between <paramref name="span"/> and <paramref name="other"/>.</summary>
/// <typeparam name="T">The type of the elements in the spans.</typeparam>
/// <param name="span">The first sequence to compare.</param>
/// <param name="other">The second sequence to compare.</param>
/// <returns>The length of the common suffix shared by the two spans. If there's no shared suffix, 0 is returned.</returns>
public static int CommonSuffixLength<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other)
{
int length = Math.Min(span.Length, other.Length);
span = span.Slice(span.Length - length);
other = other.Slice(other.Length - length);

for (int i = length - 1; i >= 0; i--)
{
if (!EqualityComparer<T>.Default.Equals(span[i], other[i]))
{
return length - i - 1;
}
}

return length;
}

/// <summary>Finds the length of any common suffix shared between <paramref name="span"/> and <paramref name="other"/>.</summary>
/// <typeparam name="T">The type of the elements in the spans.</typeparam>
/// <param name="span">The first sequence to compare.</param>
/// <param name="other">The second sequence to compare.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> implementation to use when comparing elements, or <see langword="null"/> to use the default <see cref="IEqualityComparer{T}"/> for the type of an element.</param>
/// <returns>The length of the common suffix shared by the two spans. If there's no shared suffix, 0 is returned.</returns>
public static int CommonSuffixLength<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other, IEqualityComparer<T>? comparer)
{
if (typeof(T).IsValueType && (comparer is null || comparer == EqualityComparer<T>.Default))
{
return CommonSuffixLength(span, other);
}

int length = Math.Min(span.Length, other.Length);
span = span.Slice(span.Length - length);
other = other.Slice(other.Length - length);

comparer ??= EqualityComparer<T>.Default;
for (int i = length - 1; i >= 0; i--)
{
if (!comparer.Equals(span[i], other[i]))
{
return length - i - 1;
}
}

return length;
}

/// <summary>Determines if one span is longer than the other, and slices the longer one to match the length of the shorter.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SliceLongerSpanToMatchShorterLength<T>(ref ReadOnlySpan<T> span, ref ReadOnlySpan<T> other)
Expand Down
Loading