# Map: 1-in, 1-out

**Notational Conventions**

The *j*-th element of a stream *s* is s[*j*]. The suffix *e* to refers to functions that operate on an element of a stream, and *w* for functions on sliding windows of streams. For example, *map_e* refers to the map function element by element whereas *map_w* refers to functions that operate on sliding windows of the stream.

We convert a terminating function *f *that operates on conventional Python bounded-sized to a non-terminating function that operates on endless streams by decorating *f*. IoTPy has many decorators. We start with decorators *map_e *and* map_w *for procedures, and their functional variants *fmap_e *and* fmap_w. *The decorators, *map_e *and* map_w, *are used to get procedures that operate on streams whereas *fmap_e *and* fmap_w* are used to get functions that return streams.* *

**MAP**

Suppose you are given a conventional Python function, *g*, that terminates and operates on a conventional Python bounded-size object such as a list or an array. You want to create a procedure, *f*, that reads a (possibly endless) stream, *x*, and which does the following: Whenever a value *v* is appended to stream *x*, the procedure appends *g*(*v*) to stream *y*. You can define *f* as follows:

```
@map_e
def f(v): return g(v)
f(in_stream=x, out_stream=y)
```

The decorated function, *f*, has two parameters, an input stream, *in_stream,* and an output stream, *out_stream.* As more values are appended to *x*, more values are appended to *y*, and so the lengths of the streams can get arbitrarily large. Let *Y*[*n*] be the *n*-th value appended by the procedure to stream *y*; then:

`Y[n] = f(x[n])`

For example, given streams *x* and *y*, the decorated function *double* (below) appends 2**v* to stream *y* for every value *v* in stream *x*. So, in this example *Y*[*n*] = 2**X*[*n*].

```
@map_e
def double(v): return 2*v
double(in_stream=x, out_stream=y)
```

**STATE**

You can pass a state to a function by including the parameter called *state* in the call and providing its initial value, as in:

```
@map_e
def f(v, state): return g(v,state), h(v,state)
f(in_stream=x, out_stream=y, state=initial_state)
```

Let *state*[*n*] be the *n*-th value of *state*, for n = 0, 1, 2,… Then:

```
state[0] = initial_state
Y[n] = g(x[n], state[n])
state[n+1] = h(x[n], state[n])
```

where *Y*[*n*] is the *n*-th value appended by the procedure to stream *y*.

**EXAMPLE**

```
@map_e
def deltas(input_value, state):
difference = input_value - state
next_state = input_value
return difference, next_state
deltas(in_stream=x, out_stream=y, state=0)
```

The above code ensures *state*[0] = 0 because the call to *deltas* has the term, *state*=0. When the procedure starts its *n*-th step, for n = 0, 1, 2, …, *input_value = x*[*n*], and *state*[*n*]=*x*[*n-1*] for n > 0, and *state*[*0*]=*0.*

The second value returned by the function is the next state* *of the procedure. *state*[*n*+1]=*x*[*n*] because the second value returned by the function is *next_state* which has value *input_value* or *x*[*n*]*. *

The first value, *difference,* returned by the function is appended to the output stream. At the *n*-th step, the value *Y*[*n*] appended to stream *y*, is given by *Y*[*n*] = *x*[*n*] - *state*[*n-1*]. This is because *difference* = *input_value* - *state, *which is *x*[*n*] - *state*[*n-1*] on the *n*-th step.

Therefore, *Y*[0] = *x*[0], and *Y*[*n*] = *x*[*n*] - *x*[*n*-1].

A less verbose style of writing the above function is:

```
@map_e
def deltas(u, v): return u - v, u
deltas(x, y, state=0)
```

Note that **you must explicitly use the keyword** *state* in the call if the procedure has state. This is because *state* is a keyword parameter.

**KEYWORD PARAMETERS OTHER THAN STATE**

You can pass an arbitrary number of keyword parameters to a function as shown in the following example which has a keyword parameter called *multiplicand*. The call ensures *y*[*n*] = 3**x*[*n*].

```
@map_e
def multiply(v, multiplicand): return v*multiplicand
multiply(in_stream=x, out_stream=y, multiplicand=3)
```

Note that the call to multiply must have the keyword *multiplicand*, though *in_stream* and *out_stream *can be positional arguments as in:

`multiply(x, y, multiplicand=3)`

### KEYWORD PARAMETERS WITH STATE

You can have both keyword parameters and state as in the following example.

```
@map_e
def anomalous_change(v, state, max_change):
delta = v - state
next_state = v
next_output = True if delta > max_change else False
return next_output, next_state
```

Given streams x and y, if you call:

`anomalous_change(in_stream=x, out_stream=y, state=0, max_change=10)`

Then the initial state, *s*[0] is 0 and the state *s*[*n*+1] after n elements of *x* have been read is *x*[*n*]. The output stream *y* contains Boolean values where *y*[*n*] is *True* to indicate that the change, *x*[*n*+1] - *x*[*n*], in x exceeds the keyword parameter *max_change*.

`y[0] = (x[0] > max_change)`

`y[n+1] = ((x[n+1] - x[n]) > max_change)`

Click here to download examples of @map_e and @fmap_e* *or download it from GitHub at *IoTPy/examples/op/examples_map_e.py. *

## WINDOWS

IoTPy has multiple windowing operations. We begin with windows specified by a fixed size and fixed step size. A window is a list. (Later, we will describe windows that are NumPy arrays.) A window is specified by a *window_size* and *step_size. *The length of a window is its *window_size. *A sliding window is moved forward by its *step_size. *The *j*-th window specified of a stream *x* is:

`x[j*step_size : j*step_size + window_size] for j >= 0`

For example, if *window_size* is 3 and *step_size* is 2 then the sequence of windows is the following sequence of lists:

`x[0, 1, 2], x[2, 3, 4], x[4, 5, 6], …`

Use the decorator @map_w for mapping operations on windows.

```
@map_w
def f(window): return g(window)
f(in_stream=x, out_stream=y, window_size=w, step_size=s)
```

This call sets:

`y[n] = g(x[n*s : n*s+w])`

For example,

```
@map_w
def sum_window(window): return sum(window)
sum_window(in_stream=x, out_stream=y, window_size=2, step_size=2)
```

sets

`y[n] = x[2*n] + x[2*n+1]`

You can use keywords and state for *@map_w* in exactly the same way as for *@map_e.*

## Functional Form

The functional form obtained with the decorator *@fmap_e* can be more convenient in some cases than the procedural form obtained by *@map_e, *as illustrated in the following example.

```
@fmap_e
def f(v): return g(v)
y=f(x)
```

and

```
@map_e
def h(v): return g(v)
h(in_stream=x, out_stream=z)
```

The relationship between *@fmap_e *and *@map_e *is similar to the relationship between *@f_add *and *@r_add*.

In the functional form with

*@fmap_e*the**output stream is created**by the call to*f*whereas in the procedural form with*@map_e**,*the output stream must be**declared before**the call to*f*and is passed as a parameter to*f*.The

*n*-th value*Z*[*n*] appended to the output stream*z*by the procedure is the*n*-th value returned by the function*f*.

The functional form gives you the convenience of using functional notation such as:

`z = f(g(x,y) * h(w,x,y))`

where *w, x, y, z* are streams.

You can use keyword parameters and state with the functional form in the exactly the same way as for the procedural form.

### Functional Form with Windows

The decorator *@fmap_w *is the functional form of *@map_w *in exactly the same way that *@fmap_e *is the functional form of *@map_e.*

```
@fmap_w
def f(window): return g(window)
y = f(x, window_size=w, step_size=s)
```

is the functional version of the procedure:

```
@map_w
def f(window): return g(window)
f(in_stream=x, out_stream=y, window_size=w, step_size=s)
```

Click here to download examples of @map_w and @fmap_w or download the examples from GitHub at *IoTPy/examples/op/examples_map_w.py. *Another simple example is in *IoTPy/examples/signal_processing_examples/window_dot_product.py.*

Let’s look at an example from acoustics: generating an echo: Example. Acoustics Echo.