Skip to content

Maps data types in C# to and from monotonically increasing consecutive integers. These integers can be used for both hashing and equality. It is also possible to get the original value back from the mapped integer. Also includes a memoizer that makes use of the integer mapper.

License

Notifications You must be signed in to change notification settings

WCoetser/Trl.IntegerMapper

Repository files navigation

Overview

Integer Mapper maps data structures to monotonically increasing consecutive integers starting from 0, with 0 being reserved for null or any collection that is empty. This applies to strings as well, with the empty string being mapped to 0.

Integer Mapper is useful for implementing hashing and equality in the sense that everything is always mapped to a predictable value. The value can serve as a hash code, but that can also directly be used to compare two objects.

It is also possible to get the original data back because the mapped integers preserve equality. This makes it useful for things like caching, or algorithms that need to represent complex data as a simple integer that is mapped to an array address.

Integer Mapper is also well suited to memoization, therefore it includes support for memoization. Any type that has an integer mapper associated with it could also be used save inputs and outputs for functions.

Trl.IntegerMapper is build on .NET Standard 2.1 for cross-platform compatibility.

Using the Integer Mapper

The core interface for mapping values is IIntegerMapper. All mappers implement this interface.

For example, to map string and then get the original string back from the assigned uint:

IIntegerMapper<string> mapper = new StringMapper();

mapper.Map("One");
mapper.Map("Two");
mapper.Map("Three");

for (uint i = 0; i < mapper.MappedObjectsCount; i++)
{
    string originalValue = mapper.ReverseMap(i);
    Console.WriteLine($"{i} => {originalValue}");
}

// Output:
// 0 =>
// 1 => One
// 2 => Two
// 3 => Three

Note that all collections contain the empty case, which is mapped to 0. For example, StringMapper maps 0 to the empty string. This also means that all collections contain at least one element.

Integer Mapper also supports byte[] and IEnumberable<byte>, ex.:

IIntegerMapper<byte[]> mapper1 = new ByteArrayMapper();
IIntegerMapper<IEnumerable<byte>> mapper2 = new ByteEnumerableMapper();

Integer Mapper with the IEqualityComparer<T>

Integer mapper allows you to map any .NET type with an equality comparer.

For example, to map int values, the following code could be used:

var intMapper = new EqualityComparerMapper<int>(EqualityComparer<int>.Default);

Mappings collections with GenericSequenceMapper for IEnumerable<T>

Generic collections can be mapped using an equality comparer for the item type contained in the collection.

For example, to map IEnumerable<byte>, the following decleration can be used:

var genericSequenceMapper = new GenericSequenceMapper<byte>(EqualityComparer<byte>.Default);
var mappedValue = genericSequenceMapper.Map(new [] { 1,2,3 });

GenericSequenceMapper stores values in a tree data structure to avoid doing multiple enumerations when storing values.

Using the memoizer

In order to use the memoizer, declare integer mappers for the input and output types of the function being memoized, and pass it in.

For example, if you want to calculate the first 92 Fibonacci numbers, and you are using the recursive formula, you can speed it up by using memoization:

var intMapper = new EqualityComparerMapper<long>(EqualityComparer<long>.Default);
var nullableIntMapper = new EqualityComparerMapper<long?>(EqualityComparer<long?>.Default);

var memoizer = new Memoizer<long, long?>(intMapper, nullableIntMapper);

long fast_fib(long n)
{
    var existingSolution = memoizer.GetOutput(n);
    var returnValue = (n, existingSolution.HasValue) switch
    {
        (0, _) => 0,
        (1, _) => 1,
        (_, false) => fast_fib(n - 1) + fast_fib(n - 2),
        (_, true) => existingSolution.Value
    };
    memoizer.Memoize(n, returnValue);
    return returnValue;
}

for (int i = 0; i < 92; i++)
{
    Console.WriteLine($"Fibonacci number {i + 1} is {fast_fib(i)}");
}

// Output:
// Fibonacci number 1 is 0
// Fibonacci number 2 is 1
// Fibonacci number 3 is 1
// Fibonacci number 4 is 2
// ...
// Fibonacci number 90 is 1779979416004714189
// Fibonacci number 91 is 2880067194370816120
// Fibonacci number 92 is 4660046610375530309

ValueTuple can be used for functions with multiple input parameters, ex.:

var inputEqualityComparer = EqualityComparer<ValueTuple<string, int>>.Default;
var inputMapper = new EqualityComparerMapper<ValueTuple<string, int>>(inputEqualityComparer);
var outputMapper = new EqualityComparerMapper<double>(EqualityComparer<double>.Default);

var memoizer = new Memoizer<ValueTuple<string, int>, double>(inputMapper, outputMapper);
memoizer.Memoize(("one", 1), 1.0);

Installation via Nuget

See https://www.nuget.org/packages/Trl.IntegerMapper/ for nuget package.

Unit Test Code Coverage

Unit tests can be run using the .\test.ps1 script. This will generate a code coverage report in the .\UnitTestCoverageReport folder using Coverlet and ReportGenerator.

Code Coverage

Licence

Integer Mapper is released under the MIT open source licence. See LICENCE.txt in this repository for the full text.

About

Maps data types in C# to and from monotonically increasing consecutive integers. These integers can be used for both hashing and equality. It is also possible to get the original value back from the mapped integer. Also includes a memoizer that makes use of the integer mapper.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages