Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Span: Add BinarySearch(...) extension methods for ReadOnlySpan<T> (and Span<T>) #25777

Merged
merged 32 commits into from
Dec 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d4f644a
S.M: Add initial BinarySearch span extensions method definitions.
nietras Nov 29, 2017
0d76e79
Add dummy xml comments to allow compiling. Add empty test files.
nietras Dec 4, 2017
9fb189f
Add to ref.
nietras Dec 4, 2017
9b5ad5b
Uncomment in test since extension method not found.
nietras Dec 4, 2017
8b4a8ba
Add initial implementation.
nietras Dec 7, 2017
cd7e6a7
Add more comments and string test.
nietras Dec 7, 2017
039349c
Add more tests.
nietras Dec 7, 2017
3d0ac20
Fix string test, add double tests.
nietras Dec 7, 2017
b84fad2
cleanup
nietras Dec 7, 2017
dc79fad
Remove empty check, and add empty tests.
nietras Dec 7, 2017
88c8e0b
Remove xml doc on exception, since that case is not applicable here.
nietras Dec 7, 2017
d86b248
Remove try/catch, and SR text etc.
nietras Dec 7, 2017
0a8a9c2
minor cleanup
nietras Dec 7, 2017
471f1f7
refactor tests.
nietras Dec 7, 2017
d953a2c
remove redundant.
nietras Dec 7, 2017
a31def9
merge master
nietras Dec 7, 2017
4327a5f
refactor string tests.
nietras Dec 7, 2017
9a19658
refactor tests, add comparer tests.
nietras Dec 7, 2017
c68312e
add icomparable overload tests.
nietras Dec 7, 2017
41ebc1c
Add test for int.MaxValue size span and possible overflow.
nietras Dec 7, 2017
42c5dec
compute median without subtract
nietras Dec 7, 2017
1e3b969
make generic params 'in'
nietras Dec 7, 2017
74f0496
Remove 'in' and update xml comments.
nietras Dec 8, 2017
5ce195e
add a few more test
nietras Dec 8, 2017
6d75e2a
add null throw tests
nietras Dec 8, 2017
90f3525
simplify max length no overflow test
nietras Dec 8, 2017
955bc4b
add basic perf tests.
nietras Dec 9, 2017
ee9830f
fix remaining review issues.
nietras Dec 9, 2017
cedb211
use InnerCount
nietras Dec 11, 2017
0b3ccec
use InnerCount
nietras Dec 11, 2017
dc60908
fix string perf test
nietras Dec 13, 2017
67a5965
fix MiddleIndex perf tests
nietras Dec 13, 2017
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
9 changes: 9 additions & 0 deletions src/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// Changes to this file must follow the http://aka.ms/api-review process.
// ------------------------------------------------------------------------------

using System.Collections.Generic;

namespace System
{
public readonly ref struct ReadOnlySpan<T>
Expand Down Expand Up @@ -148,6 +150,13 @@ public static class MemoryExtensions
public static bool Overlaps<T>(this Span<T> first, ReadOnlySpan<T> second, out int elementOffset) { throw null; }
public static bool Overlaps<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second) { throw null; }
public static bool Overlaps<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second, out int elementOffset) { throw null; }

public static int BinarySearch<T>(this ReadOnlySpan<T> span, IComparable<T> comparable) { throw null; }
public static int BinarySearch<T, TComparable>(this ReadOnlySpan<T> span, TComparable comparable) where TComparable : IComparable<T> { throw null; }
public static int BinarySearch<T, TComparer>(this ReadOnlySpan<T> span, T value, TComparer comparer) where TComparer : IComparer<T> { throw null; }
public static int BinarySearch<T>(this Span<T> span, IComparable<T> comparable) { throw null; }
public static int BinarySearch<T, TComparable>(this Span<T> span, TComparable comparable) where TComparable : IComparable<T> { throw null; }
public static int BinarySearch<T, TComparer>(this Span<T> span, T value, TComparer comparer) where TComparer : IComparer<T> { throw null; }
}

public readonly struct ReadOnlyMemory<T>
Expand Down
7 changes: 2 additions & 5 deletions src/System.Memory/src/System.Memory.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'uap-Windows_NT-Release|AnyCPU'" />
<ItemGroup>
<Compile Include="System\MemoryExtensions.cs" />
<Compile Include="System\SpanHelpers.BinarySearch.cs" />
<Compile Include="System\SpanHelpers.T.cs" />
<Compile Include="System\SpanHelpers.byte.cs" />
<Compile Include="System\ThrowHelper.cs" />
Expand All @@ -35,7 +36,6 @@
<Compile Include="System\Buffers\Text\Base64Decoder.cs" />
<Compile Include="System\Buffers\Text\Base64Encoder.cs" />
</ItemGroup>

<!-- Utf8 Formatter/Parser -->
<ItemGroup>
<Compile Include="System\Buffers\StandardFormat.cs" />
Expand Down Expand Up @@ -89,19 +89,16 @@
<Compile Include="System\Buffers\Text\Utf8Parser\Utf8Parser.TimeSpan.LittleG.cs" />
<Compile Include="System\Buffers\Text\Utf8Parser\Utf8Parser.TimeSpanSplitter.cs" />
</ItemGroup>

<!-- Double/Decimal format/parser logic copied (almost verbatim) over from CoreRT -->
<ItemGroup>
<Compile Include="System\Number\Decimal.DecCalc.cs" />
<Compile Include="System\Number\Number.cs" />
<Compile Include="System\Number\Number.FormatAndParse.cs" />
<Compile Include="System\Number\Number.NumberBuffer.cs" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Common\src\System\MutableDecimal.cs" />
</ItemGroup>

<ItemGroup Condition="'$(IsPartialFacadeAssembly)' != 'true'">
<Compile Include="System\ReadOnlySpan.cs" />
<Compile Include="System\Span.cs" />
Expand Down Expand Up @@ -146,4 +143,4 @@
<ProjectReference Include="..\..\System.Numerics.Vectors\src\System.Numerics.Vectors.csproj" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>
165 changes: 165 additions & 0 deletions src/System.Memory/src/System/MemoryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace System
Expand Down Expand Up @@ -637,5 +638,169 @@ ref first.DangerousGetPinnableReference(),
}
}
}

/// <summary>
/// Searches an entire sorted <see cref="Span{T}"/> for a value
/// using the specified <see cref="IComparable{T}"/> generic interface.
/// </summary>
/// <typeparam name="T">The element type of the span.</typeparam>
/// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
/// <param name="comparable">The <see cref="IComparable{T}"/> to use when comparing.</param>
/// <returns>
/// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
/// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
/// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
/// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name = "comparable" /> is <see langword="null"/> .
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int BinarySearch<T>(
this Span<T> span, IComparable<T> comparable)
{
return BinarySearch<T, IComparable<T>>(span, comparable);
}

/// <summary>
/// Searches an entire sorted <see cref="Span{T}"/> for a value
/// using the specified <typeparamref name="TComparable"/> generic type.
/// </summary>
/// <typeparam name="T">The element type of the span.</typeparam>
/// <typeparam name="TComparable">The specific type of <see cref="IComparable{T}"/>.</typeparam>
/// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
/// <param name="comparable">The <typeparamref name="TComparable"/> to use when comparing.</param>
/// <returns>
/// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
/// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
/// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
/// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name = "comparable" /> is <see langword="null"/> .
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int BinarySearch<T, TComparable>(
this Span<T> span, TComparable comparable)
where TComparable : IComparable<T>
{
return BinarySearch((ReadOnlySpan<T>)span, comparable);
}

/// <summary>
/// Searches an entire sorted <see cref="Span{T}"/> for the specified <paramref name="value"/>
/// using the specified <typeparamref name="TComparer"/> generic type.
/// </summary>
/// <typeparam name="T">The element type of the span.</typeparam>
/// <typeparam name="TComparer">The specific type of <see cref="IComparer{T}"/>.</typeparam>
/// <param name="span">The sorted <see cref="Span{T}"/> to search.</param>
/// <param name="value">The object to locate. The value can be null for reference types.</param>
/// <param name="comparer">The <typeparamref name="TComparer"/> to use when comparing.</param>
/// /// <returns>
/// The zero-based index of <paramref name="value"/> in the sorted <paramref name="span"/>,
/// if <paramref name="value"/> is found; otherwise, a negative number that is the bitwise complement
/// of the index of the next element that is larger than <paramref name="value"/> or, if there is
/// no larger element, the bitwise complement of <see cref="Span{T}.Length"/>.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name = "value" /> is <see langword="null"/> .
/// </exception>
// TODO: Do we accept a null comparer and then revert to T as IComparable if possible??
// T:System.ArgumentException:
// comparer is null, and value is of a type that is not compatible with the elements
// of array.
// T:System.InvalidOperationException:
// comparer is null, and T does not implement the System.IComparable`1 generic interface
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int BinarySearch<T, TComparer>(
this Span<T> span, T value, TComparer comparer)
where TComparer : IComparer<T>
{
return BinarySearch((ReadOnlySpan<T>)span, value, comparer);
}

/// <summary>
/// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for a value
/// using the specified <see cref="IComparable{T}"/> generic interface.
/// </summary>
/// <typeparam name="T">The element type of the span.</typeparam>
/// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
/// <param name="comparable">The <see cref="IComparable{T}"/> to use when comparing.</param>
/// <returns>
/// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
/// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
/// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
/// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name = "comparable" /> is <see langword="null"/> .
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int BinarySearch<T>(
this ReadOnlySpan<T> span, IComparable<T> comparable)
{
return BinarySearch<T, IComparable<T>>(span, comparable);
}

/// <summary>
/// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for a value
/// using the specified <typeparamref name="TComparable"/> generic type.
/// </summary>
/// <typeparam name="T">The element type of the span.</typeparam>
/// <typeparam name="TComparable">The specific type of <see cref="IComparable{T}"/>.</typeparam>
/// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
/// <param name="comparable">The <typeparamref name="TComparable"/> to use when comparing.</param>
/// <returns>
/// The zero-based index of <paramref name="comparable"/> in the sorted <paramref name="span"/>,
/// if <paramref name="comparable"/> is found; otherwise, a negative number that is the bitwise complement
/// of the index of the next element that is larger than <paramref name="comparable"/> or, if there is
/// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name = "comparable" /> is <see langword="null"/> .
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int BinarySearch<T, TComparable>(
this ReadOnlySpan<T> span, TComparable comparable)
where TComparable : IComparable<T>
{
return SpanHelpers.BinarySearch(span, comparable);
}

/// <summary>
/// Searches an entire sorted <see cref="ReadOnlySpan{T}"/> for the specified <paramref name="value"/>
/// using the specified <typeparamref name="TComparer"/> generic type.
/// </summary>
/// <typeparam name="T">The element type of the span.</typeparam>
/// <typeparam name="TComparer">The specific type of <see cref="IComparer{T}"/>.</typeparam>
/// <param name="span">The sorted <see cref="ReadOnlySpan{T}"/> to search.</param>
/// <param name="value">The object to locate. The value can be null for reference types.</param>
/// <param name="comparer">The <typeparamref name="TComparer"/> to use when comparing.</param>
/// /// <returns>
/// The zero-based index of <paramref name="value"/> in the sorted <paramref name="span"/>,
/// if <paramref name="value"/> is found; otherwise, a negative number that is the bitwise complement
/// of the index of the next element that is larger than <paramref name="value"/> or, if there is
/// no larger element, the bitwise complement of <see cref="ReadOnlySpan{T}.Length"/>.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name = "value" /> is <see langword="null"/> .
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int BinarySearch<T, TComparer>(
this ReadOnlySpan<T> span, T value, TComparer comparer)
where TComparer : IComparer<T>
{
if (comparer == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparer);
// TODO: Do we accept a null comparer and then revert to T as IComparable if possible??
// T:System.ArgumentException:
// comparer is null, and value is of a type that is not compatible with the elements
// of array.
// T:System.InvalidOperationException:
// comparer is null, and T does not implement the System.IComparable`1 generic interface
var comparable = new SpanHelpers.ComparerComparable<T, TComparer>(
value, comparer);
return BinarySearch(span, comparable);
}
}
}
80 changes: 80 additions & 0 deletions src/System.Memory/src/System/SpanHelpers.BinarySearch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace System
{
internal static partial class SpanHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int BinarySearch<T, TComparable>(
this ReadOnlySpan<T> span, TComparable comparable)
where TComparable : IComparable<T>
{
if (comparable == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.comparable);
// TODO: Make `ref readonly`/`in` when Unsafe.Add(ReadOnly) supports it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ahsonkhan, @nietras , do we have workitems filed for these Unsafe APIs and to fix the TODOs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrzysztofCwalina My proposal to change Unsafe APIs to ref readonly was rejected a while ago. A few things have changed since then (such as the rename to in), so maybe it's time the proposal could be revisited?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably just remove the TODOs since the Unsafe fix was to "cast" away the readonly and then there is no reason as such to change the impl, if DangerousGetPinnableReference() will still return a ref for ReadOnlySpan<T> otherwise the readonly simply has to be cast away. This will show up if any such change comes to ReadOnlySpan<T> so deleting the TODOs should be fine.

return BinarySearch(ref span.DangerousGetPinnableReference(), span.Length, comparable);
}

// TODO: Make spanStart `ref readonly`/`in` when Unsafe.Add(ReadOnly) supports it
internal static int BinarySearch<T, TComparable>(
ref T spanStart, int length, TComparable comparable)
where TComparable : IComparable<T>
{
int lo = 0;
int hi = length - 1;
// If length == 0, hi == -1, and loop will not be entered
while (lo <= hi)
{
// PERF: `lo` or `hi` will never be negative inside the loop,
// so computing median using uints is safe since we know
// `length <= int.MaxValue`, and indices are >= 0
// and thus cannot overflow an uint.
// Saves one subtraction per loop compared to
// `int i = lo + ((hi - lo) >> 1);`
int i = (int)(((uint)hi + (uint)lo) >> 1);

// TODO: We probably need to add `ref readonly`/`in` methods e.g. `AddReadOnly` to unsafe
int c = comparable.CompareTo(Unsafe.Add(ref spanStart, i));
if (c == 0)
{
return i;
}
else if (c > 0)
{
lo = i + 1;
}
else
{
hi = i - 1;
}
}
// If none found, then a negative number that is the bitwise complement
// of the index of the next element that is larger than or, if there is
// no larger element, the bitwise complement of `length`, which
// is `lo` at this point.
return ~lo;
}

// Helper to allow sharing all code via IComparable<T> inlineable
internal struct ComparerComparable<T, TComparer> : IComparable<T>
where TComparer : IComparer<T>
{
readonly T _value;
readonly TComparer _comparer;

public ComparerComparable(T value, TComparer comparer)
{
_value = value;
_comparer = comparer;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int CompareTo(T other) => _comparer.Compare(_value, other);
}
}
}
4 changes: 3 additions & 1 deletion src/System.Memory/src/System/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ internal enum ExceptionArgument
text,
obj,
ownedMemory,
pointer
pointer,
comparable,
comparer
}
}
Loading