The OpenD Programming Language

MapRange

Implements the homonym function (also known as transform) present in many languages of functional flavor. The call map!(fun)(slice) returns a slice of which elements are obtained by applying fun for all elements in slice. The original slices are not changed. Evaluation is done lazily.

Note: transposed and pack can be used to specify dimensions.

@fmamath
struct MapRange (
alias fun
Range
) {
Range _input;
static if(isInfinite!Range)
enum bool empty;
}

Parameters

fun

One or more functions.

Examples

import mir.ndslice.topology : iota;
auto s = iota(2, 3).map!(a => a * 3);
assert(s == [[ 0,  3,  6],
             [ 9, 12, 15]]);

String lambdas

import mir.ndslice.topology : iota;
assert(iota(2, 3).map!"a * 2" == [[0, 2, 4], [6, 8, 10]]);

Input ranges

import mir.algorithm.iteration: filter, equal;
assert (6.iota.filter!"a % 2".map!"a * 10".equal([10, 30, 50])); 

Packed tensors

import mir.ndslice.topology : iota, windows;
import mir.math.sum: sum;

//  iota        windows     map  sums ( reduce!"a + b" )
//                --------------
//  -------      |  ---    ---  |      ------
// | 0 1 2 |  => || 0 1 || 1 2 ||  => | 8 12 |
// | 3 4 5 |     || 3 4 || 4 5 ||      ------
//  -------      |  ---    ---  |
//                --------------
auto s = iota(2, 3)
    .windows(2, 2)
    .map!sum;

assert(s == [[8, 12]]);

Zipped tensors

import mir.ndslice.topology : iota, zip;

// 0 1 2
// 3 4 5
auto sl1 = iota(2, 3);
// 1 2 3
// 4 5 6
auto sl2 = iota([2, 3], 1);

auto z = zip(sl1, sl2);

assert(zip(sl1, sl2).map!"a + b" == sl1 + sl2);
assert(zip(sl1, sl2).map!((a, b) => a + b) == sl1 + sl2);

Multiple functions can be passed to map. In that case, the element type of map is a tuple containing one element for each function.

import mir.ndslice.topology : iota;

auto sl = iota(2, 3);
auto s = sl.map!("a + a", "a * a");

auto sums     = [[0, 2, 4], [6,  8, 10]];
auto products = [[0, 1, 4], [9, 16, 25]];

assert(s.map!"a[0]" == sl + sl);
assert(s.map!"a[1]" == sl * sl);

map can be aliased to a symbol and be used separately:

import mir.ndslice.topology : iota;

alias halfs = map!"double(a) / 2";
assert(halfs(iota(2, 3)) == [[0.0, 0.5, 1], [1.5, 2, 2.5]]);

Type normalization

import mir.functional : pipe;
import mir.ndslice.topology : iota;
auto a = iota(2, 3).map!"a + 10".map!(pipe!("a * 2", "a + 1"));
auto b = iota(2, 3).map!(pipe!("a + 10", "a * 2", "a + 1"));
assert(a == b);
static assert(is(typeof(a) == typeof(b)));

Use map with byDim/alongDim to apply functions to each dimension

import mir.ndslice.topology: byDim, alongDim;
import mir.ndslice.fuse: fuse;
import mir.math.stat: mean;
import mir.algorithm.iteration: all;
import mir.math.common: approxEqual;

auto x = [
    [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
    [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
].fuse;

// Use byDim/alongDim with map to compute mean of row/column.
assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));

Use map with a lambda and with byDim/alongDim, but may need to allocate result. This example uses fuse, which allocates. Note: fuse!1 will transpose the result.

import mir.ndslice.topology: iota, byDim, alongDim, map;
import mir.ndslice.fuse: fuse;
import mir.ndslice.slice: sliced;

auto x = [1, 2, 3].sliced;
auto y = [1, 2].sliced;

auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse;
assert(s1 == [[ 0, 2,  6],
              [ 3, 8, 15]]);
auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1;
assert(s2 == [[ 0, 1,  2],
              [ 6, 8, 10]]);
auto s3 = iota(2, 3).alongDim!1.map!(a => a * x).fuse;
assert(s1 == [[ 0, 2,  6],
              [ 3, 8, 15]]);
auto s4 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1;
assert(s2 == [[ 0, 1,  2],
              [ 6, 8, 10]]);
import mir.algorithm.iteration: reduce;
import mir.math.common: fmax;
import mir.math.stat: mean;
import mir.math.sum;
/// Returns maximal column average.
auto maxAvg(S)(S matrix) {
    return reduce!fmax(0.0, matrix.alongDim!1.map!mean);
}
// 1 2
// 3 4
auto matrix = iota([2, 2], 1);
assert(maxAvg(matrix) == 3.5);

See Also

Meta