// Based on
// https://github.com/CommunityToolkit/dotnet/blob/main/src/CommunityToolkit.HighPerformance/Extensions/SpanExtensions.cs
// with additional methods and support for more types
// 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 on https://github.com/CommunityToolkit/dotnet for more information.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace FisheryLib;
[PublicAPI]
public static class SpanExtensions
{
///
/// Returns a reference to an element at a specified index within a given , with no bounds checks.
///
/// The type of elements in the input instance.
/// The input instance.
/// The index of the element to retrieve within .
/// A reference to the element within at the index specified by .
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt(this Span span, int i)
=> ref Unsafe.Add(ref span.DangerousGetPinnableReference(), (nint)(uint)i);
///
/// Returns a reference to an element at a specified index within a given , with no bounds checks.
///
/// The type of elements in the input instance.
/// The input instance.
/// The index of the element to retrieve within .
/// A reference to the element within at the index specified by .
/// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the parameter is valid.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T DangerousGetReferenceAt(this Span span, nint i)
=> ref Unsafe.Add(ref span.DangerousGetPinnableReference(), i);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Sort(this Span span, IComparer? comparer = null)
=> ArraySortHelper.Default.Sort(span, comparer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Sort(this Span values, Span keys, IComparer? comparer = null)
=> ArraySortHelper.Default.Sort(keys, values, comparer);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T First(this Span span) => ref span[0];
[return: MaybeNull]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T FirstOrDefault(this Span span)
=> !span.IsEmpty
? span[0]
: default;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref TSource First(this Span span, delegate*[ predicate)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset)))
goto returnForOffset0;
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource))))
return ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource));
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)))))
return ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)));
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)))))
return ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)));
length -= 4;
offset += 4 * (nuint)sizeof(TSource);
}
while (length > 0)
{
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset)))
goto returnForOffset0;
length--;
offset += (nuint)sizeof(TSource);
}
return ref ThrowNotFoundException();
returnForOffset0:
return ref Unsafe.AddByteOffset(ref r0, offset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref TSource First(this Span span, Predicate predicate)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (predicate(Unsafe.AddByteOffset(ref r0, offset)))
goto returnForOffset0;
if (predicate(Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource))))
return ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource));
if (predicate(Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)))))
return ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)));
if (predicate(Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)))))
return ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)));
length -= 4;
offset += 4 * (nuint)sizeof(TSource);
}
while (length > 0)
{
if (predicate(Unsafe.AddByteOffset(ref r0, offset)))
goto returnForOffset0;
length--;
offset += (nuint)sizeof(TSource);
}
return ref ThrowNotFoundException();
returnForOffset0:
return ref Unsafe.AddByteOffset(ref r0, offset);
}
[DoesNotReturn]
private static ref T ThrowNotFoundException() => throw new InvalidOperationException();
[return: MaybeNull]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe TSource FirstOrDefault(this Span span,
delegate*][ predicate)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset)))
goto returnForOffset0;
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource))))
return Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource));
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)))))
return Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)));
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)))))
return Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)));
length -= 4;
offset += 4 * (nuint)sizeof(TSource);
}
while (length > 0)
{
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset)))
goto returnForOffset0;
length--;
offset += (nuint)sizeof(TSource);
}
return default;
returnForOffset0:
return Unsafe.AddByteOffset(ref r0, offset);
}
[return: MaybeNull]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe TSource FirstOrDefault(this Span span, Predicate predicate)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (predicate(Unsafe.AddByteOffset(ref r0, offset)))
goto returnForOffset0;
if (predicate(Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource))))
return Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource));
if (predicate(Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)))))
return Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource)));
if (predicate(Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)))))
return Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)));
length -= 4;
offset += 4 * (nuint)sizeof(TSource);
}
while (length > 0)
{
if (predicate(Unsafe.AddByteOffset(ref r0, offset)))
goto returnForOffset0;
length--;
offset += (nuint)sizeof(TSource);
}
return default;
returnForOffset0:
return Unsafe.AddByteOffset(ref r0, offset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Any(this Span span) => !span.IsEmpty;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool Any(this Span span, delegate*][ predicate)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset))
|| predicate(ref Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)))
|| predicate(ref Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))))
|| predicate(ref Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)))))
return true;
length -= 4;
offset += 4 * (nuint)sizeof(TSource);
}
while (length > 0)
{
if (predicate(ref Unsafe.AddByteOffset(ref r0, offset)))
return true;
length--;
offset += (nuint)sizeof(TSource);
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool Any(this Span span, Predicate predicate)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (predicate(Unsafe.AddByteOffset(ref r0, offset))
|| predicate(Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(TSource)))
|| predicate(Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(TSource))))
|| predicate(Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(TSource)))))
return true;
length -= 4;
offset += 4 * (nuint)sizeof(TSource);
}
while (length > 0)
{
if (predicate(Unsafe.AddByteOffset(ref r0, offset)))
return true;
length--;
offset += (nuint)sizeof(TSource);
}
return false;
}
///
/// Counts the number of occurrences of a given value into a target instance.
///
/// The type of items in the input instance.
/// The input instance to read.
/// The value to look for.
/// The number of occurrences of in .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int Count(this Span span, T value)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint result = 0;
nuint offset = 0;
// Main loop with 4 unrolled iterations
while (length >= 4)
{
result += Unsafe.AddByteOffset(ref r0, offset).Equals(value).ToByte();
result += Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(value).ToByte();
result += Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(value).ToByte();
result += Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(value).ToByte();
length -= 4;
offset += 4 * (nuint)sizeof(T);
}
// Iterate over the remaining values and count those that match
while (length > 0)
{
result += Unsafe.AddByteOffset(ref r0, offset).Equals(value).ToByte();
length--;
offset += (nuint)sizeof(T);
}
return (int)result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool Contains(this Span span, T value)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (Unsafe.AddByteOffset(ref r0, offset).Equals(value)
|| Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(value)
|| Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(value)
|| Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(value))
return true;
length -= 4;
offset += 4 * (nuint)sizeof(T);
}
while (length > 0)
{
if (Unsafe.AddByteOffset(ref r0, offset).Equals(value))
return true;
length--;
offset += (nuint)sizeof(T);
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int IndexOf(this Span span, T value)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (Unsafe.AddByteOffset(ref r0, offset).Equals(value))
return (int)offset;
if (Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(value))
return (int)offset + 1;
if (Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(value))
return (int)offset + 2;
if (Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(value))
return (int)offset + 3;
length -= 4;
offset += 4 * (nuint)sizeof(T);
}
while (length > 0)
{
if (Unsafe.AddByteOffset(ref r0, offset).Equals(value))
return (int)offset;
length--;
offset += (nuint)sizeof(T);
}
return -1;
}
///
/// Enumerates the items in the input instance, as pairs of reference/index values.
/// This extension should be used directly within a loop:
/// ]
/// Span<int> numbers = new[] { 1, 2, 3, 4, 5, 6, 7 };
///
/// foreach (var item in numbers.Enumerate())
/// {
/// // Access the index and value of each item here...
/// int index = item.Index;
/// ref int value = ref item.Value;
/// }
///
/// The compiler will take care of properly setting up the loop with the type returned from this method.
///
/// The type of items to enumerate.
/// The source to enumerate.
/// A wrapper type that will handle the reference/index enumeration for .
/// The returned value shouldn't be used directly: use this extension in a loop.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SpanEnumerable Enumerate(this Span span) => new(span);
}
public static class SpanReferenceExtensions
{
///
/// Gets the index of an element of a given from its reference.
///
/// The type of items in the input .
/// The input to calculate the index for.
/// The reference to the target item to get the index for.
/// The index of within , or -1.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int IndexOf(this Span span, ref T reference)
{
ref var r0 = ref span.DangerousGetPinnableReference();
var byteOffset = Unsafe.ByteOffset(ref r0, ref reference);
var elementOffset = byteOffset / (nint)(uint)sizeof(T);
return (nuint)elementOffset >= (uint)span.Length
? -1
: (int)elementOffset;
}
///
/// Determines whether a reference points to an element within the .
///
/// The type of items in the input .
/// The input to locate the reference in.
/// The reference to the target item to locate.
/// true if the reference points towards an element of ; otherwise, false.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Contains(this Span span, ref T reference) => span.IndexOf(ref reference) >= 0;
}
public static class SpanStructExtensions
{
///
/// Counts the number of occurrences of a given value into a target instance.
///
/// The type of items in the input instance.
/// The input instance to read.
/// The value to look for.
/// The number of occurrences of in .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int Count(this Span span, in T value)
where T : struct
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint result = 0;
nuint offset = 0;
// Main loop with 4 unrolled iterations
while (length >= 4)
{
result += Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value)).ToByte();
result += Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(ref Unsafe.AsRef(in value)).ToByte();
result += Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value)).ToByte();
result += Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value)).ToByte();
length -= 4;
offset += 4 * (nuint)sizeof(T);
}
// Iterate over the remaining values and count those that match
while (length > 0)
{
result += Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value)).ToByte();
length--;
offset += (nuint)sizeof(T);
}
return (int)result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool Contains(this Span span, in T value)
where T : struct
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value))
|| Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(ref Unsafe.AsRef(in value))
|| Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value))
|| Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value)))
return true;
length -= 4;
offset += 4 * (nuint)sizeof(T);
}
while (length > 0)
{
if (Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value)))
return true;
length--;
offset += (nuint)sizeof(T);
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int IndexOf(this Span span, in T value)
where T : struct
{
ref var r0 = ref span.DangerousGetPinnableReference();
var length = (nuint)span.Length;
nuint offset = 0;
while (length >= 4)
{
if (Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value)))
return (int)offset;
if (Unsafe.AddByteOffset(ref r0, offset + (nuint)sizeof(T)).Equals(ref Unsafe.AsRef(in value)))
return (int)offset + 1;
if (Unsafe.AddByteOffset(ref r0, offset + (2 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value)))
return (int)offset + 2;
if (Unsafe.AddByteOffset(ref r0, offset + (3 * (nuint)sizeof(T))).Equals(ref Unsafe.AsRef(in value)))
return (int)offset + 3;
length -= 4;
offset += 4 * (nuint)sizeof(T);
}
while (length > 0)
{
if (Unsafe.AddByteOffset(ref r0, offset).Equals(ref Unsafe.AsRef(in value)))
return (int)offset;
length--;
offset += (nuint)sizeof(T);
}
return -1;
}
}