Skip to content

Commit

Permalink
add multi type multiMap (#10)
Browse files Browse the repository at this point in the history
- add multi type **multiMap<T1, T2>** to reduce RAM and speed up lookup.
- add multi type **multiMapBS<T1, T2>** binary search version.
- add example for multi type
- update examples
- update readme.md
- minor edits
  • Loading branch information
RobTillaart authored Nov 13, 2023
1 parent 59fc478 commit 0305de9
Show file tree
Hide file tree
Showing 14 changed files with 396 additions and 85 deletions.
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).


## [0.2.0] - 2023-11-12
- add multi type **multiMap<T1, T2>** to reduce RAM and speed up lookup.
- add multi type **multiMapBS<T1, T2>** binary search version.
- add example for multi type
- update examples
- update readme.md
- minor edits

----

## [0.1.7] - 2023-06-24
- add **multiMapCache()**, experimental version that caches the last value.
to be used with input that do not change often.
Expand All @@ -14,7 +24,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- add examples
- major rewrite readme.md


## [0.1.6] - 2022-11-17
- add RP2040 in build-CI
- add changelog.md
Expand All @@ -31,7 +40,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- fix Arduino-lint

## [0.1.3] - 2021-01-02
- add Arduino-CI
- add Arduino-CI

## [0.1.2] - 2020-06-19
- fix library.json
Expand Down
101 changes: 82 additions & 19 deletions MultiMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,57 @@
//
// FILE: MultiMap.h
// AUTHOR: Rob Tillaart
// VERSION: 0.1.7
// VERSION: 0.2.0
// DATE: 2011-01-26
// PURPOSE: Arduino library for fast non-linear mapping or interpolation of values
// URL: https://github.com/RobTillaart/MultiMap
// URL: http://playground.arduino.cc/Main/MultiMap



#define MULTIMAP_LIB_VERSION (F("0.1.7"))
#define MULTIMAP_LIB_VERSION (F("0.2.0"))


#include "Arduino.h"


// note: the in array must have increasing values
////////////////////////////////////////////////////////////////////////
//
// SINGLE TYPE MULTIMAP - LINEAR SEARCH - the reference
//
// note: the in array must have increasing values
template<typename T>
T multiMap(T value, T* _in, T* _out, uint8_t size)
{
// output is constrained to out array
if (value <= _in[0]) return _out[0];
if (value >= _in[size-1]) return _out[size-1];

// search right interval
// search right interval
uint8_t pos = 1; // _in[0] already tested
while(value > _in[pos]) pos++;

// this will handle all exact "points" in the _in array
// this will handle all exact "points" in the _in array
if (value == _in[pos]) return _out[pos];

// interpolate in the right segment for the rest
// interpolate in the right segment for the rest
return (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
}


// performance optimized version if inputs do not change often
// e.g. 2 2 2 2 2 3 3 3 3 5 5 5 5 5 5 8 8 8 8 5 5 5 5 5
// implements a minimal cache of the lastValue.
////////////////////////////////////////////////////////////////////////
//
// SINGLE TYPE MULTIMAP CACHE - LINEAR SEARCH
//
// note: the in array must have increasing values
// performance optimized version if inputs do not change often
// e.g. 2 2 2 2 2 3 3 3 3 5 5 5 5 5 5 8 8 8 8 5 5 5 5 5
// implements a minimal cache of the lastValue.
template<typename T>
T multiMapCache(T value, T* _in, T* _out, uint8_t size)
{
static T lastValue = -1;
static T cache = -1;
static T cache = -1;

if (value == lastValue)
{
Expand All @@ -65,36 +72,92 @@ T multiMapCache(T value, T* _in, T* _out, uint8_t size)
else
{
// search right interval; index 0 _in[0] already tested
uint8_t pos = 1;
uint8_t pos = 1;
while(value > _in[pos]) pos++;
// this will handle all exact "points" in the _in array
if (value == _in[pos])

// this will handle all exact "points" in the _in array
if (value == _in[pos])
{
cache = _out[pos];
}
else
{
// interpolate in the right segment for the rest
// interpolate in the right segment for the rest
cache = (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
}
}
return cache;
}


// binary search version, should be faster for size > 10
////////////////////////////////////////////////////////////////////////
//
// SINGLE TYPE MULTIMAP - BINARY SEARCH
//
// should be faster for size >= 10
// (rule of thumb)
//
// note: the in array must have increasing values
// note: the in array must have increasing values
template<typename T>
T multiMapBS(T value, T* _in, T* _out, uint16_t size)
T multiMapBS(T value, T* _in, T* _out, uint8_t size)
{
// output is constrained to out array
if (value <= _in[0]) return _out[0];
if (value >= _in[size-1]) return _out[size-1];

// Binary Search, uint16_t needed to prevent overflow.
uint16_t lower = 0;
uint16_t upper = size - 1;
while (lower < upper - 1)
{
uint8_t mid = (lower + upper) / 2;
if (value >= _in[mid]) lower = mid;
else upper = mid;
}

return (value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower]) + _out[lower];
}


////////////////////////////////////////////////////////////////////////
//
// MULTITYPE MULTIMAP - LINEAR SEARCH
//
// note: the in array must have increasing values
template<typename T1, typename T2>
T2 multiMap(T1 value, T1* _in, T2* _out, uint8_t size)
{
// output is constrained to out array
if (value <= _in[0]) return _out[0];
if (value >= _in[size-1]) return _out[size-1];

// search right interval
uint16_t pos = 1; // _in[0] already tested
while(value > _in[pos]) pos++;

// this will handle all exact "points" in the _in array
if (value == _in[pos]) return _out[pos];

// interpolate in the right segment for the rest
return (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
}


////////////////////////////////////////////////////////////////////////
//
// MULTITYPE MULTIMAP - BINARY SEARCH
// should be faster for size >= 10
// (rule of thumb)
//
// note: the in array must have increasing values
template<typename T1, typename T2>
T2 multiMapBS(T1 value, T1* _in, T2* _out, uint8_t size)
{
// output is constrained to out array
if (value <= _in[0]) return _out[0];
if (value >= _in[size-1]) return _out[size-1];

// Binary Search
// Binary Search, uint16_t needed to prevent overflow.
uint16_t lower = 0;
uint16_t upper = size - 1;
while (lower < upper - 1)
Expand Down
98 changes: 77 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
[![Arduino CI](https://github.com/RobTillaart/MultiMap/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci)
[![Arduino-lint](https://github.com/RobTillaart/MultiMap/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/MultiMap/actions/workflows/arduino-lint.yml)
[![JSON check](https://github.com/RobTillaart/MultiMap/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/MultiMap/actions/workflows/jsoncheck.yml)
[![GitHub issues](https://img.shields.io/github/issues/RobTillaart/MultiMap.svg)](https://github.com/RobTillaart/MultiMap/issues)

[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/MultiMap/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/release/RobTillaart/MultiMap.svg?maxAge=3600)](https://github.com/RobTillaart/MultiMap/releases)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/MultiMap.svg)](https://registry.platformio.org/libraries/robtillaart/MultiMap)


# MultiMap
Expand Down Expand Up @@ -39,7 +42,25 @@ Of course this approximation introduces an error.
By increasing the number of points and choose their position strategically the average error will be reduced.

Note: some functions are hard to approximate with multiMap as they go to infinity or have a singularity.
Think of **tan(x)** around x = PI/2 (90°) or **sin(1/x)** around zero.
Think of **tan(x)** around x = PI/2 (90°) or **sin(1/x)** around zero.


#### Related

Other mapping libraries

- https://github.com/RobTillaart/FastMap
- https://github.com/RobTillaart/Gamma
- https://github.com/RobTillaart/map2colour
- https://github.com/RobTillaart/moduloMap
- https://github.com/RobTillaart/MultiMap


## Interface

```cpp
#include "MultiMap.h"
```


#### Usage
Expand All @@ -66,7 +87,7 @@ This is a explicit difference with the **map()** function.
Therefore it is important to extend the range of the arrays to cover all possible values.


#### Performance
## Performance

**multiMap()** does a linear search for the inputValue in the inputArray.
This implies that usage of larger and more precise arrays will take more time.
Expand Down Expand Up @@ -96,24 +117,59 @@ Experimental 0.1.7 => use with care.

**multiMapCache()** MMC for short, is a very similar function as **multiMap()**.
The main difference is that MMC caches the last input and output value.
The goal is to improve the performance by preventing
The goal is to improve the performance by preventing searching the same
value again and again.

If the input sequence has a lot of repeating values e.g. 2 2 2 2 2 2 5 5 5 5 5 4 4 4 4 2 2 2 2 2 2
MMC will be able to return the value from cache often.
Otherwise keeping cache is overhead.

Be sure to do your own tests to see if MMC improves your performance.

A possible variation is to cache the last interval - lower and upper index.
It would allow a to test that value and improve the linear search.
(to be investigated).

#### Related

Other mapping libraries
#### MultiMap two types

- https://github.com/RobTillaart/FastMap
- https://github.com/RobTillaart/Gamma
- https://github.com/RobTillaart/map2colour
- https://github.com/RobTillaart/moduloMap
- https://github.com/RobTillaart/MultiMap
Experimental 0.2.0 => use with care.

**multiMap<T1, T2>()** MMTT for short, is a very similar function as **multiMap()**.
The main difference is that MMTT uses two different types, typical the input
is an integer type and the output is a float or double type.
It is expected that there will be a gain if two different sized integer types are used.
This is not tested.

See the example **multimap_distance_two_types.ino**

```cpp
// for a sharp distance range finder
float sharp2cm2(int val)
{
// out[] holds the distances in cm
float out[] = {150, 140, 130, 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20};

// in[] holds the measured analogRead() values for that distance
int in[] = { 90, 97, 105, 113, 124, 134, 147, 164, 185, 218, 255, 317, 408, 506};

float dist = multiMap<int, float>(val, in, out, 14);
return dist;
}
```
A first test indicate that using the int type for the input in the example
is substantial (~37%) faster per call. Test on UNO, time in micros per call.
| types | time us | call |
|:-------:|:---------:|:-------|
| 1 | 194.93 | ```float dist = multiMap<float>(val, in, out, 14);``` |
| 2 | 121.97 | ```float dist = multiMap<int, float>(val, in, out, 14);``` |
Furthermore it is obvious that there is less need for RAM if the integer type is smaller
in size than the float type.
Be sure to do your own tests to see if MMTT improves your performance.
## Operation
Expand All @@ -129,22 +185,17 @@ Please note the fail example as this shows that in the intern math overflow can
- improve documentation

#### Should
- investigate multiMapCache behaviour
- determine overhead.
- investigate binary search multiMapBS behaviour
- expect a constant time
- where is the tipping point between linear and binary search.
(expect around size = 8)
- extend unit tests

- multi type versions
#### Could
- Investigate class implementation
- basic call out = mm.map(value);
- basic call ```out = mm.map(value);```
- runtime adjusting input and output array **begin(in[], out[])**
- performance / footprint
- less parameter passing
Expand All @@ -154,14 +205,19 @@ Please note the fail example as this shows that in the intern math overflow can
now it is constrained without user being informed.
- Investigate a 2D multiMap e.g. for complex numbers?
- is it possible / feasible?
- data type input array does not need to be equal to the output array.
- template<typename T1, typename T2>
```T2 multiMapBS(T1 value, T1* _in, T2* _out, uint16_t size)```

#### Wont
- should the lookup tables be merged into one array of pairs?
- you cannot reuse e.g. the input array or the output array then.
this would not improve the memory footprint.
## Support
If you appreciate my libraries, you can support the development and maintenance.
Improve the quality of the libraries by providing issues and Pull Requests, or
donate through PayPal or GitHub sponsors.
Thank you,
5 changes: 5 additions & 0 deletions examples/multimap_BS_compare/multimap_BS_compare.ino
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@
// PURPOSE: demo
// DATE: 2023-06-23


#include "MultiMap.h"

long in[100];
long out[100];

volatile int x;


void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();
delay(100);

for (int i = 0; i < 100; i++)
{
Expand Down
Loading

0 comments on commit 0305de9

Please sign in to comment.