diff --git a/Huffman.Tree.Benchmarks/Huffman.Tree.Benchmarks.csproj b/Huffman.Tree.Benchmarks/Huffman.Tree.Benchmarks.csproj new file mode 100644 index 0000000..31cd0eb --- /dev/null +++ b/Huffman.Tree.Benchmarks/Huffman.Tree.Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + + + + diff --git a/Huffman.Tree.Benchmarks/Program.cs b/Huffman.Tree.Benchmarks/Program.cs new file mode 100644 index 0000000..ce31231 --- /dev/null +++ b/Huffman.Tree.Benchmarks/Program.cs @@ -0,0 +1,5 @@ +// See https://aka.ms/new-console-template for more information +using BenchmarkDotNet.Running; +using Huffman.Tree.Tests; + +BenchmarkRunner.Run(); \ No newline at end of file diff --git a/Huffman.Tree.Tests/Huffman.Tree.Tests.csproj b/Huffman.Tree.Tests/Huffman.Tree.Tests.csproj new file mode 100644 index 0000000..bb5f413 --- /dev/null +++ b/Huffman.Tree.Tests/Huffman.Tree.Tests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + net6.0;net7.0 + enable + enable + + false + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Huffman.Tree.Tests/HuffmanTreeTests.cs b/Huffman.Tree.Tests/HuffmanTreeTests.cs new file mode 100644 index 0000000..c9f121c --- /dev/null +++ b/Huffman.Tree.Tests/HuffmanTreeTests.cs @@ -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 TestCases => EnumerateTestCases().SelectMany(x => x.OfType()); + + private IEnumerable>? _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 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>(); + // 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>(); + _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)); + } + } +} diff --git a/Huffman.Tree.Tests/Usings.cs b/Huffman.Tree.Tests/Usings.cs new file mode 100644 index 0000000..ab67c7e --- /dev/null +++ b/Huffman.Tree.Tests/Usings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/Huffman.Tree.sln b/Huffman.Tree.sln new file mode 100644 index 0000000..894682c --- /dev/null +++ b/Huffman.Tree.sln @@ -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 diff --git a/Huffman.Tree/BitArrayExtensions.cs b/Huffman.Tree/BitArrayExtensions.cs new file mode 100644 index 0000000..bdf0c68 --- /dev/null +++ b/Huffman.Tree/BitArrayExtensions.cs @@ -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.Shared.Rent(byteCount); + try + { + var bufferSpan = buffer.AsSpan(); + byteCount = encoding.GetBytes(value, bufferSpan); + BitArray result = new(buffer[..byteCount]); + return result; + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + + public static BitArray AsBitArray(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; + } + } +} diff --git a/Huffman.Tree/Huffman.Tree.csproj b/Huffman.Tree/Huffman.Tree.csproj new file mode 100644 index 0000000..a5431e1 --- /dev/null +++ b/Huffman.Tree/Huffman.Tree.csproj @@ -0,0 +1,37 @@ + + + + net6.0 + net6.0;net7.0 + enable + enable + true + Huffman.Tree + 0.0.1 + Marek Lemańczyk + N/A + Huffman.Tree + Recyclable Huffman Tree Builder Code Codes + Huffman.Tree project is an open source package for building & operating Huffman tree & codes. It aims at providing minimal memory footprint. + https://github.com/mlemanczyk/Huffman.Tree + https://github.com/mlemanczyk/Huffman.Tree + Marek Lemańczyk 2023 + LICENSE.md + README.md + git + First Implementation. + + + + + + + + + Always + + + PreserveNewest + + + diff --git a/Huffman.Tree/HuffmanCode.cs b/Huffman.Tree/HuffmanCode.cs new file mode 100644 index 0000000..daa4a97 --- /dev/null +++ b/Huffman.Tree/HuffmanCode.cs @@ -0,0 +1,39 @@ +using System.Collections; +using System.Text; + +namespace Huffman.Tree +{ + public readonly record struct HuffmanCode + { + 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() ?? ""; + 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; + } + } +} diff --git a/Huffman.Tree/HuffmanLeaf.cs b/Huffman.Tree/HuffmanLeaf.cs new file mode 100644 index 0000000..fba29ea --- /dev/null +++ b/Huffman.Tree/HuffmanLeaf.cs @@ -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; + } + } +} diff --git a/Huffman.Tree/HuffmanNode.cs b/Huffman.Tree/HuffmanNode.cs new file mode 100644 index 0000000..c32d911 --- /dev/null +++ b/Huffman.Tree/HuffmanNode.cs @@ -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; } + } +} \ No newline at end of file diff --git a/Huffman.Tree/HuffmanToken.cs b/Huffman.Tree/HuffmanToken.cs new file mode 100644 index 0000000..feea7eb --- /dev/null +++ b/Huffman.Tree/HuffmanToken.cs @@ -0,0 +1,35 @@ +using System.Text; + +namespace Huffman.Tree +{ + public record HuffmanToken : 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; + } + } + } +} diff --git a/Huffman.Tree/HuffmanTokenBuilder.cs b/Huffman.Tree/HuffmanTokenBuilder.cs new file mode 100644 index 0000000..bcd16a6 --- /dev/null +++ b/Huffman.Tree/HuffmanTokenBuilder.cs @@ -0,0 +1,11 @@ +namespace Huffman.Tree +{ + public static class HuffmanTokenBuilder + { + public static HuffmanToken AsHuffmanToken(this TToken value, long frequency = 0) + => new() { Token = value, Frequency = frequency }; + + public static HuffmanToken WithFrequency(this HuffmanToken token, long frequency) + => token with { Frequency = frequency }; + } +} diff --git a/Huffman.Tree/HuffmanTotalSizeComparer.cs b/Huffman.Tree/HuffmanTotalSizeComparer.cs new file mode 100644 index 0000000..98a7c87 --- /dev/null +++ b/Huffman.Tree/HuffmanTotalSizeComparer.cs @@ -0,0 +1,18 @@ +namespace Huffman.Tree +{ + public static partial class HuffmanTreeBuilder + { + internal class HuffmanTotalSizeComparer : IComparer + { + public int Compare(HuffmanNode? x, HuffmanNode? y) + { + return (x?.TotalSize - y?.TotalSize) switch + { + > 0L => 1, + < 0L => -1, + _ => 0 + }; + } + } + } +} diff --git a/Huffman.Tree/HuffmanTree.cs b/Huffman.Tree/HuffmanTree.cs new file mode 100644 index 0000000..77073da --- /dev/null +++ b/Huffman.Tree/HuffmanTree.cs @@ -0,0 +1,27 @@ +using System.Collections; + +namespace Huffman.Tree +{ + public class HuffmanTree : IEnumerable + { + private IEnumerable> EnumerateCodes() => EnumerateNodes().OfType>(); + private IEnumerable EnumerateNodes() => Nodes; + + public HuffmanTree(IEnumerable nodes) + { + Nodes = nodes; + } + + public void Dispose() + { + Nodes = Enumerable.Empty(); + } + + public IEnumerator GetEnumerator() => Nodes.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private IEnumerable>? _codes; + public IEnumerable> Codes => _codes ??= EnumerateCodes(); + public IEnumerable Nodes { get; protected set; } + } +} diff --git a/Huffman.Tree/HuffmanTreeBuilder.cs b/Huffman.Tree/HuffmanTreeBuilder.cs new file mode 100644 index 0000000..111e36a --- /dev/null +++ b/Huffman.Tree/HuffmanTreeBuilder.cs @@ -0,0 +1,44 @@ +namespace Huffman.Tree +{ + public static partial class HuffmanTreeBuilder + { + public static HuffmanTree AsHuffmanTree(this IEnumerable<(string token, long frequency)> inputTokens) + { + return inputTokens.Select(x => new HuffmanToken() + { + Token = x.token, + Frequency = x.frequency + }) + .AsHuffmanTree(); + } + + public static HuffmanTree AsHuffmanTree(this IEnumerable> 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(treeNodes); + } + + private static IEnumerable BuildTreeNodes(this PriorityQueue 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 OrderByTotalSize(this IEnumerable> tokens) + => new(tokens.Select(x => ((HuffmanNode)x, x.TotalSize))); + } +} diff --git a/Huffman.Tree/HuffmanTreeQueueExtensions.cs b/Huffman.Tree/HuffmanTreeQueueExtensions.cs new file mode 100644 index 0000000..dca8cb5 --- /dev/null +++ b/Huffman.Tree/HuffmanTreeQueueExtensions.cs @@ -0,0 +1,16 @@ +namespace Huffman.Tree +{ + internal static class HuffmanTreeQueueExtensions + { + public static bool EnqueueIfNotNull(this Queue queue, in HuffmanNode? item) + { + if (item != default) + { + queue.Enqueue(item); + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Huffman.Tree/HuffmanValueToken.cs b/Huffman.Tree/HuffmanValueToken.cs new file mode 100644 index 0000000..32e64b0 --- /dev/null +++ b/Huffman.Tree/HuffmanValueToken.cs @@ -0,0 +1,7 @@ +namespace Huffman.Tree +{ + public record HuffmanValueToken : HuffmanToken + { + public TValue? Value { get; init; } + } +} diff --git a/Huffman.Tree/ObjectExtensions.cs b/Huffman.Tree/ObjectExtensions.cs new file mode 100644 index 0000000..2a7dd4d --- /dev/null +++ b/Huffman.Tree/ObjectExtensions.cs @@ -0,0 +1,38 @@ +using System.Text; + +namespace Huffman.Tree +{ + public static class ObjectExtensions + { + private static readonly Dictionary _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? 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(this T value, Encoding? encoding = default) => GetSize(value, encoding); + } +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b345ef8 --- /dev/null +++ b/LICENSE.md @@ -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 +[![Donate](buy-me-a-coffee.svg)](https://www.buymeacoffee.com/mlemanczyk) \ No newline at end of file diff --git a/README.md b/README.md index a570fca..ef71a10 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,2 @@ # 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. - -Commercial (re-)use requires separate license. Please contact me for details. +Huffman.Tree project is an open source framework for building Huffman trees in an easy & efficient way. \ No newline at end of file