Skip to content

Commit

Permalink
Distinct
Browse files Browse the repository at this point in the history
  • Loading branch information
ufcpp committed Jun 14, 2024
1 parent 76bd45a commit 1e2feae
Show file tree
Hide file tree
Showing 17 changed files with 399 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Demo/2024/Distinct/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[*]
charset = utf-8
end_of_line = lf

csharp_style_namespace_declarations=file_scoped:suggestion
8 changes: 8 additions & 0 deletions Demo/2024/Distinct/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>

<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
13 changes: 13 additions & 0 deletions Demo/2024/Distinct/Distinct.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Solution>
<Properties Name="Visual Studio">
<Property Name="OpenWith" Value="17" />
</Properties>
<Project Path="DistinctBenchmark\DistinctBenchmark.csproj" Type="C#" />
<Project Path="DistinctTest\DistinctTest.csproj" Type="C#" />
<Project Path="Distinct\Distinct.csproj" />
<Folder Name="/solution items/">
<File Path=".editorconfig" />
<File Path="Directory.Build.props" />
<File Path="readme.md" />
</Folder>
</Solution>
57 changes: 57 additions & 0 deletions Demo/2024/Distinct/Distinct/Binary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace Distinct;

public class Binary
{
public static ReadOnlySpan<T> Distinct<T>(IEnumerable<T> source, Span<T> buffer)
where T : IComparable<T>
{
var count = 0;

foreach (var item in source)
{
var i = buffer[..count].BinarySearch(item);
if (i >= 0) continue;
InsertAt(buffer, ~i, item);
++count;
}

return buffer[..count];
}

private static void InsertAt<T>(Span<T> span, int index, T value)
{
span[index..^1].CopyTo(span[(index + 1)..]);
span[index] = value;
}
}

/*
public static int BinarySearch<TItem, TSearch>(this IList<TItem> list, TSearch value, Func<TSearch, TItem, int> comparer)
{
ThrowHelper.ThrowIfNull(list);
ThrowHelper.ThrowIfNull(comparer);
int lower = 0;
int upper = list.Count - 1;
while (lower <= upper)
{
int middle = lower + (upper - lower) / 2;
int comparisonResult = comparer(value, list[middle]);
if (comparisonResult < 0)
{
upper = middle - 1;
}
else if (comparisonResult > 0)
{
lower = middle + 1;
}
else
{
return middle;
}
}
return ~lower;
}
* */
7 changes: 7 additions & 0 deletions Demo/2024/Distinct/Distinct/Distinct.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

</Project>
23 changes: 23 additions & 0 deletions Demo/2024/Distinct/Distinct/Hash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Distinct;

public class Hash
{
/// <summary>
/// かなり癖の強い実装。
/// </summary>
/// <remarks>
/// * buffer がデフォルト値で初期化されてないと動作しない
/// * source にデフォルト値が入ってくると動作しない
/// * buffer サイズが source の倍程度の長さがないと衝突多くて遅い
/// </remarks>
public static Span<T> Distinct<T, TKey>(IEnumerable<T> source, Span<T> buffer, Func<T, TKey> getKey, Func<T, bool> isDefault)
where TKey : notnull
{
SpanHashSet<T, TKey> set = new(buffer, getKey, isDefault);

foreach (var x in source) set.Add(x);

var len = set.Compact();
return buffer[..len];
}
}
18 changes: 18 additions & 0 deletions Demo/2024/Distinct/Distinct/Linear.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Distinct;

public class Linear
{
public static ReadOnlySpan<T> Distinct<T>(IEnumerable<T> source, Span<T> buffer)
where T : IEquatable<T>
{
var count = 0;

foreach (var item in source)
{
if (buffer[..count].Contains(item)) continue;
buffer[count++] = item;
}

return buffer[..count];
}
}
68 changes: 68 additions & 0 deletions Demo/2024/Distinct/Distinct/SpanHashSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Runtime.CompilerServices;

namespace Distinct;

public readonly ref struct SpanHashSet<T, TKey>(Span<T> buffer, Func<T, TKey> getKey, Func<T, bool> isDefault)
where TKey : notnull
{
private readonly Span<T> _buffer = buffer;

public bool Add(T item)
{
ref var r = ref GetOrAddValueRef(getKey(item));
if (!isDefault(r)) return false;

r = item;
return true;
}

public ref T GetOrAddValueRef(TKey key)
{
var buffer = _buffer;
var len = buffer.Length;

int collisionCount = 0;
int bucketIndex = key.GetHashCode() % len;
for (int i = bucketIndex; i < len; i = (i + 1) % len)
{
ref var item = ref buffer[i];
if (key.Equals(getKey(item))
|| isDefault(item))
return ref item!;

if (collisionCount == buffer.Length) Throw();

collisionCount++;
}

Throw();
return ref Unsafe.NullRef<T>();
}

private static void Throw() => throw new InvalidOperationException();

public int Compact()
{
var buffer = _buffer;
var len = buffer.Length;

var count = 0;
for (int i = 0; i < len; i++)
{
ref var item = ref buffer[i];
if (!isDefault(item))
{
if (count != i)
{
buffer[count] = item;
item = default!;
}

count++;
}
}

return count;
}
}

16 changes: 16 additions & 0 deletions Demo/2024/Distinct/DistinctBenchmark/DistinctBenchmark.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Distinct\Distinct.csproj" />
</ItemGroup>

</Project>
50 changes: 50 additions & 0 deletions Demo/2024/Distinct/DistinctBenchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<DistinctBenchmark>();

public class DistinctBenchmark
{
[Params(10, 20, 50, 100, 200, 500, 1000)]
public int N { get; set; }

private int[] _data = null!;

[GlobalSetup]
public void Setup()
{
var r = new Random();
_data = new int[1000];
foreach (ref var x in _data.AsSpan()) x = r.Next(1, 1000);
}

[Benchmark(Baseline = true)]
public void Linq()
{
foreach (var _ in _data.Take(N).Distinct()) ;
}

[Benchmark]
public void Linear()
{
foreach (var _ in Distinct.Linear.Distinct(_data.Take(N), stackalloc int[N])) ;
}

[Benchmark]
public void Binary()
{
foreach (var _ in Distinct.Binary.Distinct(_data.Take(N), stackalloc int[N])) ;
}

[Benchmark]
public void Hash()
{
foreach (var _ in Distinct.Hash.Distinct(_data.Take(N), stackalloc int[N], x => x, x => x == 0)) ;
}

[Benchmark]
public void Hash2N()
{
foreach (var _ in Distinct.Hash.Distinct(_data.Take(N), stackalloc int[2 * N], x => x, x => x == 0)) ;
}
}
15 changes: 15 additions & 0 deletions Demo/2024/Distinct/DistinctTest/BinaryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

namespace DistinctTest;

public class BinaryTest
{
[Fact]
public void EqualToLinqDistictOrder()
{
var data = TestData.Data;
var expected = data.Distinct().Order().ToArray();
var actual = Distinct.Binary.Distinct(data, stackalloc int[data.Length]);

Assert.Equal(expected, actual);
}
}
26 changes: 26 additions & 0 deletions Demo/2024/Distinct/DistinctTest/DistinctTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Distinct\Distinct.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions Demo/2024/Distinct/DistinctTest/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
15 changes: 15 additions & 0 deletions Demo/2024/Distinct/DistinctTest/HashTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace DistinctTest;

public class HashTest
{
[Fact]
public void EqualToLinqDistictOrder()
{
var data = TestData.Data;
var expected = data.Distinct().Order().ToArray();
var actual = Distinct.Hash.Distinct(data, stackalloc int[data.Length], x => x, x => x == 0);
MemoryExtensions.Sort(actual);

Assert.Equal(expected, actual);
}
}
15 changes: 15 additions & 0 deletions Demo/2024/Distinct/DistinctTest/LinearTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

namespace DistinctTest;

public class LinearTest
{
[Fact]
public void EqualToLinqDistict()
{
var data = TestData.Data;
var expected = data.Distinct().ToArray();
var actual = Distinct.Linear.Distinct(data, stackalloc int[data.Length]);

Assert.Equal(expected, actual);
}
}
16 changes: 16 additions & 0 deletions Demo/2024/Distinct/DistinctTest/TestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

namespace DistinctTest;

public class TestData
{
public static int[] Data => _data ??= CreateData();
private static int[]? _data;

private static int[] CreateData()
{
var r = new Random();
var data = new int[1000];
foreach (ref var x in data.AsSpan()) x = r.Next(1, 1000);
return data;
}
}
Loading

0 comments on commit 1e2feae

Please sign in to comment.