Skip to content

Commit 59fc478

Browse files
authored
add multiMapCache and multiMapBS template functions (#8)
- add **multiMapCache()**, experimental version that caches the last value. to be used with input that do not change often. - add **multiMapBS()**, experimental version that uses binary search. to be used with arrays > 10 (rule of thumb) - add examples - major rewrite readme.md
1 parent dcf7d5e commit 59fc478

File tree

8 files changed

+347
-55
lines changed

8 files changed

+347
-55
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

88

9+
## [0.1.7] - 2023-06-24
10+
- add **multiMapCache()**, experimental version that caches the last value.
11+
to be used with input that do not change often.
12+
- add **multiMapBS()**, experimental version that uses binary search.
13+
to be used with arrays > 10 (rule of thumb)
14+
- add examples
15+
- major rewrite readme.md
16+
17+
918
## [0.1.6] - 2022-11-17
1019
- add RP2040 in build-CI
1120
- add changelog.md
1221
- update readme.md
1322
- clean up unit test
1423

15-
1624
## [0.1.5] - 2021-12-22
1725
- update library.json
1826
- update readme

MultiMap.h

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,58 @@
22
//
33
// FILE: MultiMap.h
44
// AUTHOR: Rob Tillaart
5-
// VERSION: 0.1.6
5+
// VERSION: 0.1.7
66
// DATE: 2011-01-26
77
// PURPOSE: Arduino library for fast non-linear mapping or interpolation of values
88
// URL: https://github.com/RobTillaart/MultiMap
99
// URL: http://playground.arduino.cc/Main/MultiMap
1010

1111

1212

13-
#define MULTIMAP_LIB_VERSION (F("0.1.6"))
13+
#define MULTIMAP_LIB_VERSION (F("0.1.7"))
1414

1515

1616
#include "Arduino.h"
1717

1818

19-
// note: the in array should have increasing values
19+
// note: the in array must have increasing values
2020
template<typename T>
2121
T multiMap(T value, T* _in, T* _out, uint8_t size)
2222
{
23-
// take care the value is within range
24-
// value = constrain(value, _in[0], _in[size-1]);
25-
if (value <= _in[0]) return _out[0];
26-
if (value >= _in[size-1]) return _out[size-1];
23+
// output is constrained to out array
24+
if (value <= _in[0]) return _out[0];
25+
if (value >= _in[size-1]) return _out[size-1];
2726

28-
// search right interval
29-
uint8_t pos = 1; // _in[0] already tested
30-
while(value > _in[pos]) pos++;
27+
// search right interval
28+
uint8_t pos = 1; // _in[0] already tested
29+
while(value > _in[pos]) pos++;
3130

32-
// this will handle all exact "points" in the _in array
33-
if (value == _in[pos]) return _out[pos];
31+
// this will handle all exact "points" in the _in array
32+
if (value == _in[pos]) return _out[pos];
3433

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

3938

40-
/*
41-
// speed 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
42-
// implements a minimal cache
39+
// performance optimized version if inputs do not change often
40+
// 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
41+
// implements a minimal cache of the lastValue.
4342
//
44-
// note: the in array should have increasing values
45-
43+
// note: the in array must have increasing values
4644
template<typename T>
47-
T multiMap(T value, T* _in, T* _out, uint8_t size)
45+
T multiMapCache(T value, T* _in, T* _out, uint8_t size)
4846
{
49-
static T lastvalue = -1;
47+
static T lastValue = -1;
5048
static T cache = -1;
5149

52-
if (value == lastvalue)
50+
if (value == lastValue)
5351
{
5452
return cache;
5553
}
56-
lastvalue = value;
54+
lastValue = value;
5755

58-
// take care the value is within range
59-
// value = constrain(value, _in[0], _in[size-1]);
56+
// output is constrained to out array
6057
if (value <= _in[0])
6158
{
6259
cache = _out[0];
@@ -67,7 +64,7 @@ T multiMap(T value, T* _in, T* _out, uint8_t size)
6764
}
6865
else
6966
{
70-
// search right interval; index 0 _in[0] already tested
67+
// search right interval; index 0 _in[0] already tested
7168
uint8_t pos = 1;
7269
while(value > _in[pos]) pos++;
7370

@@ -84,8 +81,32 @@ T multiMap(T value, T* _in, T* _out, uint8_t size)
8481
}
8582
return cache;
8683
}
87-
*/
8884

8985

90-
// -- END OF FILE --
86+
// binary search version, should be faster for size > 10
87+
// (rule of thumb)
88+
//
89+
// note: the in array must have increasing values
90+
template<typename T>
91+
T multiMapBS(T value, T* _in, T* _out, uint16_t size)
92+
{
93+
// output is constrained to out array
94+
if (value <= _in[0]) return _out[0];
95+
if (value >= _in[size-1]) return _out[size-1];
96+
97+
// Binary Search
98+
uint16_t lower = 0;
99+
uint16_t upper = size - 1;
100+
while (lower < upper - 1)
101+
{
102+
uint16_t mid = (lower + upper) / 2;
103+
if (value >= _in[mid]) lower = mid;
104+
else upper = mid;
105+
}
106+
107+
return (value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower]) + _out[lower];
108+
}
109+
110+
111+
// -- END OF FILE --
91112

README.md

Lines changed: 121 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,112 @@
88

99
# MultiMap
1010

11-
Arduino library for fast non-linear mapping or interpolation of values
11+
Arduino library for fast non-linear mapping or interpolation of values.
1212

1313

1414
## Description
1515

16-
In Arduino applications often the value of a sensor is mapped upon a more
17-
usable value. E.g. the value of analogRead() is mapped onto 0 .. 5.0 Volt.
18-
This is done by the map function which does a linear interpolation.
16+
In Arduino applications often the 'raw' value of a sensor is mapped upon a more
17+
usable value. E.g. the value of analogRead() 0 .. 1023 is mapped onto 0 .. 5.0 Volt.
18+
This is often done by the map function which does a linear interpolation.
19+
1920
This means in code:
2021

2122
```cpp
2223
output = C1 + input * C2
2324
```
2425

25-
As C1 and C2 are to be calculated Arduino has the **map()** that calculates the
26-
two variables runtime from two given mappings.
26+
As C1 and C2 are to be determined, Arduino has the **map()** function that calculates the
27+
two variables runtime from two given mapping points (I1, O1) and (I2, O2).
2728

2829
```cpp
2930
output = map(input, I1, I2, O1, O2):
3031
```
3132

32-
In many cases when there is no linear mapping possible, as the 'points' are not on a single straight line.
33-
One needs non-linear math to calculate the output, **Multimap()** just simulates that.
33+
In many cases when there is no linear mapping possible as the 'points' are not on a single straight line.
34+
To solve this one needs non-linear math to calculate the output.
35+
36+
The **multiMap()** function simulates this math by approximating the non-linear function with multiple
37+
linear line segments.
38+
Of course this approximation introduces an error.
39+
By increasing the number of points and choose their position strategically the average error will be reduced.
40+
41+
Note: some functions are hard to approximate with multiMap as they go to infinity or have a singularity.
42+
Think of **tan(x)** around x = PI/2 (90°) or **sin(1/x)** around zero.
43+
44+
45+
#### Usage
46+
47+
The basic call for **multiMap()** is:
48+
49+
```cpp
50+
output = Multimap<datatype>(input, inputArray, outputArray, size);
51+
```
52+
53+
**multiMap()** needs two equally sized arrays representing the reference 'points' named
54+
**inputArray\[\]** and **outputArray\[\]** both of the **datatype**.
3455

35-
**out = Multimap(value, input, output, size)** needs two equal sized arrays of reference 'points',
36-
**input\[\]** and **output\[\]**, it looks up the
37-
input value in the input\[\] array and if needed it linear interpolates between two
38-
points of the output\[\] array.
56+
**multiMap()** will do a lookup of the input value in the inputArray\[\].
57+
If it cannot find the index of an exact point it will determine a weighted position between two points.
58+
This optional weighted point is used to interpolate a value from data in the output\[\] array.
3959

40-
- The **input\[\]** array must have increasing values,
60+
- The **inputArray\[\]** must have increasing values,
4161
there is no such restriction for the **output\[\]** array.
42-
- **Multimap()** automatically constrains the output to the first and last value in the **output\[\]** array.
62+
- The values of the **inputArray\[\]** do not need to have the same distance (non-equidistant).
63+
E.g an array like { 1, 10, 100, 1000 } is valid.
64+
- **multiMap()** automatically constrains the output to the first and last value in the **output\[\]** array.
65+
This is a explicit difference with the **map()** function.
66+
Therefore it is important to extend the range of the arrays to cover all possible values.
67+
68+
69+
#### Performance
70+
71+
**multiMap()** does a linear search for the inputValue in the inputArray.
72+
This implies that usage of larger and more precise arrays will take more time.
73+
Furthermore "low" input values will be found faster than "high" values.
74+
75+
As every usage of multiMap() is unique one should always do a performance check to see
76+
if there is a substantial gain in the case at hand. In my experience there often is.
77+
78+
79+
#### MultiMapBS
80+
81+
Experimental 0.1.7 => use with care.
82+
83+
**multiMapBS()** MMBS for short, is a very similar function as **multiMap()**.
84+
The main difference is that MMBS uses binary search instead of linear search.
85+
86+
First performance tests indicate that for array sizes about 10 MMBS is on par
87+
with **multiMap()**. This is expected as both need on average about 5 steps
88+
to find the right interval.
89+
90+
Be sure to do your own tests to see if MMBS improves your performance.
91+
92+
93+
#### MultiMapCache
94+
95+
Experimental 0.1.7 => use with care.
96+
97+
**multiMapCache()** MMC for short, is a very similar function as **multiMap()**.
98+
The main difference is that MMC caches the last input and output value.
99+
The goal is to improve the performance by preventing
100+
101+
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
102+
MMC will be able to return the value from cache often.
103+
Otherwise keeping cache is overhead.
104+
105+
Be sure to do your own tests to see if MMC improves your performance.
106+
107+
108+
#### Related
109+
110+
Other mapping libraries
111+
112+
- https://github.com/RobTillaart/FastMap
113+
- https://github.com/RobTillaart/Gamma
114+
- https://github.com/RobTillaart/map2colour
115+
- https://github.com/RobTillaart/moduloMap
116+
- https://github.com/RobTillaart/MultiMap
43117

44118

45119
## Operation
@@ -51,20 +125,43 @@ Please note the fail example as this shows that in the intern math overflow can
51125

52126
## Future
53127

54-
#### must
128+
#### Must
129+
55130
- improve documentation
56131

57-
#### should
58-
- Investigate class implementation
132+
133+
#### Should
134+
135+
- investigate multiMapCache behaviour
136+
- determine overhead.
137+
- investigate binary search multiMapBS behaviour
138+
- expect a constant time
139+
- where is the tipping point between linear and binary search.
140+
(expect around size = 8)
141+
- extend unit tests
142+
143+
144+
#### Could
145+
146+
- Investigate class implementation
147+
- basic call out = mm.map(value);
148+
- runtime adjusting input and output array **begin(in[], out[])**
59149
- performance / footprint
60150
- less parameter passing
61-
62-
#### could
63-
- flag if input value was "IN_MIN" < input < "IN_MAX",
64-
now it is constrained without user being informed.
65-
- extend unit tests
151+
- **isInRange(value)**?
152+
- caching last value / position / index (does that help?)
153+
- flag if input value was "IN_MIN" < input < "IN_MAX",
154+
now it is constrained without user being informed.
155+
- Investigate a 2D multiMap e.g. for complex numbers?
156+
- is it possible / feasible?
157+
- data type input array does not need to be equal to the output array.
158+
- template<typename T1, typename T2>
159+
```T2 multiMapBS(T1 value, T1* _in, T2* _out, uint16_t size)```
160+
161+
162+
#### Wont
66163

67-
#### wont
68164
- should the lookup tables be merged into one array of pairs?
69-
- you cannot reuse e.g. the input array then. (memory footprint)
165+
- you cannot reuse e.g. the input array or the output array then.
166+
this would not improve the memory footprint.
70167

0 commit comments

Comments
 (0)