First version added.
This commit is contained in:
1 parent
e126a9d81b
commit
d054376f71
21 files changed
+711
-3
No files matched your search
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Huffman.Tree.Tests\Huffman.Tree.Tests.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
using BenchmarkDotNet.Running;
|
||||||
|
using Huffman.Tree.Tests;
|
||||||
|
|
||||||
|
BenchmarkRunner.Run<HuffmanTreeTests>();
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.3" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.8.0" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
|
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
|
||||||
|
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.2.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Huffman.Tree\Huffman.Tree.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using AutoFixture;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using FluentAssertions;
|
||||||
|
using MoreLinq;
|
||||||
|
|
||||||
|
namespace Huffman.Tree.Tests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class HuffmanTreeTests
|
||||||
|
{
|
||||||
|
public readonly record struct TestCase
|
||||||
|
{
|
||||||
|
public (string token, long frequency)[] Tokens { get; init; }
|
||||||
|
public (string token, int[] bits)[] Expected { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<TestCase> TestCases => EnumerateTestCases().SelectMany(x => x.OfType<TestCase>());
|
||||||
|
|
||||||
|
private IEnumerable<HuffmanToken<string>>? _codes;
|
||||||
|
private (string token, long frequency)[] _tokens;
|
||||||
|
|
||||||
|
public HuffmanTreeTests()
|
||||||
|
{
|
||||||
|
_tokens = TestCases.First().Tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DynamicData(nameof(EnumerateTestCases), DynamicDataSourceType.Method)]
|
||||||
|
public void BuildTreeShouldSuccess(TestCase testCase)
|
||||||
|
{
|
||||||
|
// This is to overcome the requirement for BuildTree() to be argument- & deferred-result-free. It's required by BenchmarkDotNet.
|
||||||
|
_tokens = testCase.Tokens;
|
||||||
|
BuildTree();
|
||||||
|
PrintCodesToOutput();
|
||||||
|
ValidateCodes(testCase.Expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateCodes((string token, int[] bits)[] expected)
|
||||||
|
{
|
||||||
|
var expectedBits = expected.Select(x => (x.token, x.bits.Select(bit => Convert.ToBoolean(bit))));
|
||||||
|
_ = _codes.Should().NotBeNull();
|
||||||
|
_ = _codes!.Select(x => (x.Token, x.Code))
|
||||||
|
.Should()
|
||||||
|
.NotBeNull().And
|
||||||
|
.BeEquivalentTo(expectedBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintCodesToOutput()
|
||||||
|
{
|
||||||
|
foreach (var node in _codes!)
|
||||||
|
{
|
||||||
|
string bits = node.Code!.AsString();
|
||||||
|
Console.WriteLine($"{node.Token} => {bits}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> EnumerateTestCases()
|
||||||
|
{
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new TestCase
|
||||||
|
{
|
||||||
|
// test case
|
||||||
|
Tokens = new[] { ("a", 10L), ("b", 5L), ("c", 2L), ("d", 14L), ("e", 15L), (";", long.MaxValue) },
|
||||||
|
// expected result
|
||||||
|
Expected = new[] { (";", new[] { 1 }), ("a", new[] { 0, 0, 1 }), ("b", new[] { 0, 0, 0, 1 }), ("c", new[] { 0, 0, 0, 0 }), ("d", new[] { 0, 1, 0 }), ("e", new[] { 0, 1, 1 }) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BuildTree()
|
||||||
|
{
|
||||||
|
_codes = _tokens?.AsHuffmanTree().Nodes.OfType<HuffmanToken<string>>();
|
||||||
|
// We keep this assertions here because we want to build all the codes. For this we need to access each of them.
|
||||||
|
_ = _codes.Should().NotBeNull().And.AllSatisfy(node => node.Code!.AsString().Should().NotBeNull().And.NotBe(string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
[TestMethod]
|
||||||
|
public void BuildTree100()
|
||||||
|
{
|
||||||
|
var fixture = new Fixture();
|
||||||
|
var collection = fixture.CreateMany<(string token, long frequency)>(100);
|
||||||
|
_tokens = collection.ToArray();
|
||||||
|
_codes = _tokens?.AsHuffmanTree().Nodes.OfType<HuffmanToken<string>>();
|
||||||
|
_codes.ForEach(x => Console.WriteLine($"{x.Token} => {x.Code.AsString()}"));
|
||||||
|
// We keep this assertions here because we want to build all the codes. For this we need to access each of them.
|
||||||
|
_ = _codes.Should().NotBeNull().And.AllSatisfy(node => node.Code!.AsString().Should().NotBeNull().And.NotBe(string.Empty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
global using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.4.33122.133
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Huffman.Tree.Tests", "Huffman.Tree.Tests\Huffman.Tree.Tests.csproj", "{52626365-AA6C-47E9-8D14-1EA6E15FA594}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Huffman.Tree", "Huffman.Tree\Huffman.Tree.csproj", "{2A56CE56-B796-446E-A2AA-DF01C2B11AE5}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7C571B9C-CF39-4544-B333-140DECE7B8CE}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huffman.Tree.Benchmarks", "Huffman.Tree.Benchmarks\Huffman.Tree.Benchmarks.csproj", "{60270511-A662-42BD-B534-F5DBC7CD9960}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{52626365-AA6C-47E9-8D14-1EA6E15FA594}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{52626365-AA6C-47E9-8D14-1EA6E15FA594}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{52626365-AA6C-47E9-8D14-1EA6E15FA594}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{52626365-AA6C-47E9-8D14-1EA6E15FA594}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2A56CE56-B796-446E-A2AA-DF01C2B11AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2A56CE56-B796-446E-A2AA-DF01C2B11AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2A56CE56-B796-446E-A2AA-DF01C2B11AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2A56CE56-B796-446E-A2AA-DF01C2B11AE5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{60270511-A662-42BD-B534-F5DBC7CD9960}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{60270511-A662-42BD-B534-F5DBC7CD9960}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{60270511-A662-42BD-B534-F5DBC7CD9960}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{60270511-A662-42BD-B534-F5DBC7CD9960}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {00C6AF26-9407-4E01-BE33-B899A9577A18}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public static class BitArrayExtensions
|
||||||
|
{
|
||||||
|
public static BitArray Empty { get; } = new(0);
|
||||||
|
|
||||||
|
public static BitArray Reverse(this BitArray original)
|
||||||
|
{
|
||||||
|
BitArray reversed = original;
|
||||||
|
int length = reversed.Length;
|
||||||
|
int mid = length / 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < mid; i++)
|
||||||
|
{
|
||||||
|
(reversed[length - i - 1], reversed[i]) = (reversed[i], reversed[length - i - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reversed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string AsString(this BitArray bits)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new(bits.Count);
|
||||||
|
foreach (var bit in Enumerable.Range(0, bits.Count))
|
||||||
|
{
|
||||||
|
sb = sb.Append(bits[bit] ? '1' : '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BitArray AsBitArray(this DateTime dateTime)
|
||||||
|
{
|
||||||
|
long asBinary = dateTime.ToBinary();
|
||||||
|
return new BitArray(BitConverter.GetBytes(asBinary));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BitArray AsBitArray(this nint nint) => new(BitConverter.GetBytes(@nint));
|
||||||
|
public static BitArray AsBitArray(this bool @bool) => new(BitConverter.GetBytes(@bool));
|
||||||
|
public static BitArray AsBitArray(this char @char) => new(BitConverter.GetBytes(@char));
|
||||||
|
public static BitArray AsBitArray(this byte @byte) => new(BitConverter.GetBytes((char)@byte));
|
||||||
|
public static BitArray AsBitArray(this double @double) => new(BitConverter.GetBytes(@double));
|
||||||
|
public static BitArray AsBitArray(this float @float) => new(BitConverter.GetBytes(@float));
|
||||||
|
public static BitArray AsBitArray(this short @short) => new(BitConverter.GetBytes(@short));
|
||||||
|
public static BitArray AsBitArray(this int @int, int? length = default)
|
||||||
|
{
|
||||||
|
BitArray bitArray = new(BitConverter.GetBytes(@int));
|
||||||
|
if (length != null)
|
||||||
|
{
|
||||||
|
bitArray.Length = length.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BitArray AsBitArray(this long @long, int? length = default)
|
||||||
|
{
|
||||||
|
BitArray bitArray = new(BitConverter.GetBytes(@long));
|
||||||
|
if (length != null)
|
||||||
|
{
|
||||||
|
bitArray.Length = length.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BitArray AsBitArray(this ulong @ulong) => new(BitConverter.GetBytes(@ulong));
|
||||||
|
public static BitArray AsBitArray(this ushort @ushort) => new(BitConverter.GetBytes(@ushort));
|
||||||
|
public static BitArray AsBitArray(this nuint nuint) => new(BitConverter.GetBytes(nuint));
|
||||||
|
|
||||||
|
public static BitArray AsBitArray(this string value, Encoding? encoding = default)
|
||||||
|
{
|
||||||
|
encoding ??= Encoding.UTF8;
|
||||||
|
var byteCount = encoding.GetByteCount(value);
|
||||||
|
var buffer = ArrayPool<byte>.Shared.Rent(byteCount);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bufferSpan = buffer.AsSpan();
|
||||||
|
byteCount = encoding.GetBytes(value, bufferSpan);
|
||||||
|
BitArray result = new(buffer[..byteCount]);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BitArray AsBitArray<T>(this T value, Encoding? encoding = default) => value switch
|
||||||
|
{
|
||||||
|
bool @bool => @bool.AsBitArray(),
|
||||||
|
char @char => @char.AsBitArray(),
|
||||||
|
byte @byte => @byte.AsBitArray(),
|
||||||
|
double @double => @double.AsBitArray(),
|
||||||
|
float @float => @float.AsBitArray(),
|
||||||
|
short @short => @short.AsBitArray(),
|
||||||
|
int @int => @int.AsBitArray(),
|
||||||
|
long @long => @long.AsBitArray(),
|
||||||
|
ulong @ulong => @ulong.AsBitArray(),
|
||||||
|
ushort @ushort => @ushort.AsBitArray(),
|
||||||
|
nuint @nuint => @nuint.AsBitArray(),
|
||||||
|
nint @nint => @nint.AsBitArray(),
|
||||||
|
DateTime @dateTime => @dateTime.AsBitArray(),
|
||||||
|
string @string => @string.AsBitArray(encoding),
|
||||||
|
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(value))
|
||||||
|
};
|
||||||
|
|
||||||
|
public static BitArray CloneWithLength(this BitArray bitArray, int newLength, bool value)
|
||||||
|
{
|
||||||
|
BitArray result = new(bitArray)
|
||||||
|
{
|
||||||
|
Length = newLength
|
||||||
|
};
|
||||||
|
result[^1] = value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool[] AddValue(this bool[] array, bool value)
|
||||||
|
{
|
||||||
|
bool[] result = new bool[array.Length + 1];
|
||||||
|
array.CopyTo(result, 0);
|
||||||
|
result[array.Length] = value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int OptimalBitCount(this long value) => OptimalBitCount((long?)value);
|
||||||
|
public static int OptimalBitCount(this long? value) => value switch
|
||||||
|
{
|
||||||
|
< 2 or null => 1,
|
||||||
|
< 4 => 2,
|
||||||
|
< 8 => 3,
|
||||||
|
< 16 => 4,
|
||||||
|
< 32 => 5,
|
||||||
|
< 64 => 6,
|
||||||
|
< 128 => 7,
|
||||||
|
<= byte.MaxValue => 8,
|
||||||
|
<= ushort.MaxValue => 16,
|
||||||
|
<= uint.MaxValue => 32,
|
||||||
|
_ => 64
|
||||||
|
};
|
||||||
|
|
||||||
|
public static BitArray RightShiftAndSet(this BitArray bitArray, bool value)
|
||||||
|
{
|
||||||
|
bitArray.Length++;
|
||||||
|
//bitArray = bitArray.LeftShift(1);
|
||||||
|
bitArray[^1] = value;
|
||||||
|
return bitArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BitArray WithLength(this BitArray bitArray, int length)
|
||||||
|
{
|
||||||
|
bitArray.Length = length;
|
||||||
|
return bitArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<PackageId>Huffman.Tree</PackageId>
|
||||||
|
<PackageVersion>0.0.1</PackageVersion>
|
||||||
|
<Authors>Marek Lemańczyk</Authors>
|
||||||
|
<Company>N/A</Company>
|
||||||
|
<Product>Huffman.Tree</Product>
|
||||||
|
<PackageTags>Recyclable Huffman Tree Builder Code Codes</PackageTags>
|
||||||
|
<Description>Huffman.Tree project is an open source package for building & operating Huffman tree & codes. It aims at providing minimal memory footprint.</Description>
|
||||||
|
<PackageProjectUrl>https://github.com/mlemanczyk/Huffman.Tree</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://github.com/mlemanczyk/Huffman.Tree</RepositoryUrl>
|
||||||
|
<Copyright>Marek Lemańczyk 2023</Copyright>
|
||||||
|
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PackageReleaseNotes>First Implementation.</PackageReleaseNotes>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="morelinq" Version="3.3.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\LICENSE.md" Pack="true" PackagePath="" Link="LICENSE.md">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Include="..\README.md" Pack="true" PackagePath="" Link="README.md">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public readonly record struct HuffmanCode<T>
|
||||||
|
{
|
||||||
|
public HuffmanCode(T? token, BitArray code)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
Code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly T? Token;
|
||||||
|
public readonly BitArray Code;
|
||||||
|
|
||||||
|
public override int GetHashCode() => Token?.GetHashCode() ?? 0;
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string token = Token?.ToString() ?? "<null>";
|
||||||
|
StringBuilder sb = new(token.Length + 12 + Code.Count);
|
||||||
|
sb = sb.Append("{{").Append(token).Append("}} => {{");
|
||||||
|
sb = AddBits(sb, Code);
|
||||||
|
sb = sb.Append("}}");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder AddBits(StringBuilder sb, BitArray bits)
|
||||||
|
{
|
||||||
|
for (var bitIdx = 0; bitIdx < bits.Count; bitIdx++)
|
||||||
|
{
|
||||||
|
_ = sb.Append(bits[bitIdx] ? '1' : '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public record HuffmanLeaf : HuffmanNode
|
||||||
|
{
|
||||||
|
public HuffmanLeaf(HuffmanNode? left, HuffmanNode? right)
|
||||||
|
{
|
||||||
|
long leftFrequency;
|
||||||
|
long rightFrequency;
|
||||||
|
|
||||||
|
if (left != default)
|
||||||
|
{
|
||||||
|
left.Top = this;
|
||||||
|
left.Bit = false;
|
||||||
|
leftFrequency = left.Frequency;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
leftFrequency = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right != null)
|
||||||
|
{
|
||||||
|
right.Top = this;
|
||||||
|
right.Bit = true;
|
||||||
|
rightFrequency = right.Frequency;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rightFrequency= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Frequency = leftFrequency + rightFrequency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public record HuffmanNode
|
||||||
|
{
|
||||||
|
protected virtual long RecalculateTotalSize() => Frequency;
|
||||||
|
|
||||||
|
private long _frequency;
|
||||||
|
public long Frequency
|
||||||
|
{
|
||||||
|
get => _frequency;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_frequency != value)
|
||||||
|
{
|
||||||
|
_frequency = value;
|
||||||
|
_totalSize = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long? _totalSize;
|
||||||
|
public long TotalSize => _totalSize ??= RecalculateTotalSize();
|
||||||
|
|
||||||
|
private BitArray? _code;
|
||||||
|
public BitArray Code => _code ??= BuildCode(this);
|
||||||
|
|
||||||
|
private static BitArray BuildCode(HuffmanNode root)
|
||||||
|
{
|
||||||
|
var parent = root.Top;
|
||||||
|
if (parent == null)
|
||||||
|
{
|
||||||
|
return BitArrayExtensions.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitArray bits = new(parent.Code);
|
||||||
|
var oldLength = bits.Length++;
|
||||||
|
bits[oldLength] = (bool)root.Bit!;
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HuffmanNode? Top { get; set; }
|
||||||
|
public bool? Bit { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public record HuffmanToken<T> : HuffmanNode
|
||||||
|
{
|
||||||
|
protected override long RecalculateTotalSize()
|
||||||
|
=> Token.SizeOf(Encoding) * Frequency;
|
||||||
|
|
||||||
|
public Encoding Encoding { get; init; }
|
||||||
|
|
||||||
|
private T? _token;
|
||||||
|
|
||||||
|
public HuffmanToken(Encoding? encoding = default)
|
||||||
|
{
|
||||||
|
Encoding = encoding ?? Encoding.UTF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HuffmanToken(HuffmanNode original, Encoding? encoding = default) : base(original)
|
||||||
|
{
|
||||||
|
Encoding = encoding ?? Encoding.UTF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? Token
|
||||||
|
{
|
||||||
|
get => _token;
|
||||||
|
|
||||||
|
init
|
||||||
|
{
|
||||||
|
_token = value;
|
||||||
|
_totalSize = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public static class HuffmanTokenBuilder
|
||||||
|
{
|
||||||
|
public static HuffmanToken<TToken> AsHuffmanToken<TToken>(this TToken value, long frequency = 0)
|
||||||
|
=> new() { Token = value, Frequency = frequency };
|
||||||
|
|
||||||
|
public static HuffmanToken<TToken> WithFrequency<TToken>(this HuffmanToken<TToken> token, long frequency)
|
||||||
|
=> token with { Frequency = frequency };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public static partial class HuffmanTreeBuilder
|
||||||
|
{
|
||||||
|
internal class HuffmanTotalSizeComparer : IComparer<HuffmanNode?>
|
||||||
|
{
|
||||||
|
public int Compare(HuffmanNode? x, HuffmanNode? y)
|
||||||
|
{
|
||||||
|
return (x?.TotalSize - y?.TotalSize) switch
|
||||||
|
{
|
||||||
|
> 0L => 1,
|
||||||
|
< 0L => -1,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public class HuffmanTree<T> : IEnumerable<HuffmanNode>
|
||||||
|
{
|
||||||
|
private IEnumerable<HuffmanToken<T>> EnumerateCodes() => EnumerateNodes().OfType<HuffmanToken<T>>();
|
||||||
|
private IEnumerable<HuffmanNode> EnumerateNodes() => Nodes;
|
||||||
|
|
||||||
|
public HuffmanTree(IEnumerable<HuffmanNode> nodes)
|
||||||
|
{
|
||||||
|
Nodes = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Nodes = Enumerable.Empty<HuffmanNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<HuffmanNode> GetEnumerator() => Nodes.GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
private IEnumerable<HuffmanToken<T>>? _codes;
|
||||||
|
public IEnumerable<HuffmanToken<T>> Codes => _codes ??= EnumerateCodes();
|
||||||
|
public IEnumerable<HuffmanNode> Nodes { get; protected set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public static partial class HuffmanTreeBuilder
|
||||||
|
{
|
||||||
|
public static HuffmanTree<string> AsHuffmanTree(this IEnumerable<(string token, long frequency)> inputTokens)
|
||||||
|
{
|
||||||
|
return inputTokens.Select(x => new HuffmanToken<string>()
|
||||||
|
{
|
||||||
|
Token = x.token,
|
||||||
|
Frequency = x.frequency
|
||||||
|
})
|
||||||
|
.AsHuffmanTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HuffmanTree<T> AsHuffmanTree<T>(this IEnumerable<HuffmanToken<T>> inputTokens)
|
||||||
|
{
|
||||||
|
var tokensQueue = inputTokens.OrderByTotalSize();
|
||||||
|
// TODO: Find a way to avoid .ToArray() & be able to dynamically calculate HuffmanNode.Code before knowing all parents.
|
||||||
|
var treeNodes = tokensQueue.BuildTreeNodes().ToArray();
|
||||||
|
return new HuffmanTree<T>(treeNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<HuffmanNode> BuildTreeNodes(this PriorityQueue<HuffmanNode, long> tokensQueue)
|
||||||
|
{
|
||||||
|
var leftNode = tokensQueue.Dequeue();
|
||||||
|
HuffmanNode headNode = leftNode;
|
||||||
|
while (tokensQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var rightNode = tokensQueue.Dequeue();
|
||||||
|
headNode = new HuffmanLeaf(leftNode, rightNode);
|
||||||
|
// Don't move yielding before topNode creation. Otherwise the yielded nodes
|
||||||
|
// will have Top = null. We shall only yield nodes that have assigned nodes.
|
||||||
|
yield return leftNode;
|
||||||
|
yield return rightNode;
|
||||||
|
leftNode = tokensQueue.EnqueueDequeue(headNode, headNode.TotalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return headNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PriorityQueue<HuffmanNode, long> OrderByTotalSize<T>(this IEnumerable<HuffmanToken<T>> tokens)
|
||||||
|
=> new(tokens.Select(x => ((HuffmanNode)x, x.TotalSize)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
internal static class HuffmanTreeQueueExtensions
|
||||||
|
{
|
||||||
|
public static bool EnqueueIfNotNull<T>(this Queue<HuffmanNode> queue, in HuffmanNode? item)
|
||||||
|
{
|
||||||
|
if (item != default)
|
||||||
|
{
|
||||||
|
queue.Enqueue(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public record HuffmanValueToken<TToken, TValue> : HuffmanToken<TToken>
|
||||||
|
{
|
||||||
|
public TValue? Value { get; init; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Huffman.Tree
|
||||||
|
{
|
||||||
|
public static class ObjectExtensions
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<Type, int> _dataTypeSizes = new()
|
||||||
|
{
|
||||||
|
{ typeof(bool), 1 },
|
||||||
|
{ typeof(sbyte), 8 },
|
||||||
|
{ typeof(byte), 8 },
|
||||||
|
{ typeof(short), 16 },
|
||||||
|
{ typeof(ushort), 16 },
|
||||||
|
{ typeof(int), 32 },
|
||||||
|
{ typeof(uint), 32 },
|
||||||
|
{ typeof(long), 32 },
|
||||||
|
{ typeof(ulong), 32 },
|
||||||
|
{ typeof(float), 32 },
|
||||||
|
{ typeof(double), 64 },
|
||||||
|
{ typeof(decimal), 128 },
|
||||||
|
};
|
||||||
|
|
||||||
|
internal static long GetSize<T>(T? value, Encoding? encoding = default)
|
||||||
|
{
|
||||||
|
encoding ??= Encoding.UTF8;
|
||||||
|
return value is string @string
|
||||||
|
? encoding.GetByteCount(@string)
|
||||||
|
: value != null
|
||||||
|
? _dataTypeSizes[typeof(T)]
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long SizeOf(this long value) => GetSize(value);
|
||||||
|
public static long SizeOf(this int value) => GetSize(value);
|
||||||
|
public static long SizeOf(this string value, Encoding? encoding = default) => GetSize(value, encoding);
|
||||||
|
public static long SizeOf<T>(this T value, Encoding? encoding = default) => GetSize(value, encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# License
|
||||||
|
The project & source code is free for educational & personal use. **Any kind** of commercial use requires a donation, as prescribed below.
|
||||||
|
|
||||||
|
# Donation requirements for a life-time, commercial use
|
||||||
|
* Commercial license is paid one time.
|
||||||
|
* It can be obtained by donaiting $10 for each developer seat, using the link provided below. If you hire 2 developers, you need to donate $20 for a life-time, commercial use of this package.
|
||||||
|
* Your license stays with you, if your developer moves somewhere else.
|
||||||
|
|
||||||
|
# Link for donations
|
||||||
|
[](https://www.buymeacoffee.com/mlemanczyk)
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
# Huffman.Tree
|
# Huffman.Tree
|
||||||
Huffman.Tree project is an open source framework for building Huffman trees in an easy & efficient way. The project & source code is free for educational, open-source & private use.
|
Huffman.Tree project is an open source framework for building Huffman trees in an easy & efficient way.
|
||||||
|
|
||||||
Commercial (re-)use requires separate license. Please contact me for details.
|
|
||||||
Reference in new issue
Block a user