# `Scales.DataRange`
[🔗](https://github.com/giggleton/scales/blob/v0.1.0/lib/scales/data_range.ex#L1)

The range of the data a scale has seen, trained a batch of values at a time: a continuous range,
the lowest and highest finite number (`train_continuous/2`), or a discrete one, the levels seen,
in order (`train_discrete/3`); with the key that orders values that have no levels of their own
(`level_key/1`) and the key a value is matched by (`key/1`).

A range is `:none` until it has seen a value: a continuous one is then `{low, high}`, and a
discrete one `{levels, factor}`, `factor` saying whether its first values were a factor's,
whose levels keep the order they came in.

# `key`

```elixir
@spec key(term()) :: term()
```

The key a value is matched by: a number as a float, so an integer and the float equal to it
share one, and a negative zero is the zero it equals; any other value as it is.

## Examples

    iex> Scales.DataRange.key(4) === Scales.DataRange.key(4.0)
    true

    iex> Scales.DataRange.key(-0.0)
    0.0

# `level_key`

```elixir
@spec level_key(term()) :: {0, term()} | {1, nil}
```

The key that orders values with no levels of their own: `nil` after every value, and the values
in Erlang's term order, so numbers come first, strings sort by their bytes and `false` comes
before `true`. A factor's values are ordered by its levels instead.

## Examples

    iex> Enum.sort_by([nil, "b", 2, "a", true], &Scales.DataRange.level_key/1)
    [2, true, "a", "b", nil]

# `train_continuous`

```elixir
@spec train_continuous(:none | {number(), number()}, list()) ::
  :none | {number(), number()}
```

The range, `:none` for none, widened to cover the finite numbers among `values`, R's
`train_continuous()`: `nil`, `:infinity`, `:neg_infinity` and `:nan` widen nothing.

## Examples

    iex> Scales.DataRange.train_continuous(:none, [nil, 3, :infinity, -2])
    {-2, 3}

    iex> Scales.DataRange.train_continuous({-2, 3}, [7])
    {-2, 7}

# `train_discrete`

```elixir
@spec train_discrete({list(), :none | boolean()}, list(), list() | :none) ::
  {list(), :none | boolean()}
```

The discrete range `{levels, factor}`, `{[], :none}` before any, with `values` merged into its
levels, R's `train_discrete()` with `drop = TRUE`; `levels` are the levels of the factor
`values` are of, or `:none` for values with none of their own.

A factor's values bring the levels they hold, in the levels' order, the missing value after
them; other values bring their distinct values sorted by `level_key/1`, `nil` last. Values are
matched by `key/1`, so `4` and `4.0` are one level. A factor's levels lead the rest; and once a
range's first values were a factor's, new values follow its levels, where otherwise every merge
sorts them all again. No values merge nothing.

## Examples

    iex> Scales.DataRange.train_discrete({[], :none}, ["y", nil, "x"], :none)
    {["x", "y", nil], false}

    iex> Scales.DataRange.train_discrete({["x", "y", nil], false}, ["z", "x"], :none)
    {["x", "y", "z", nil], false}

    iex> Scales.DataRange.train_discrete({[], :none}, ["b", "a"], ["c", "b", "a"])
    {["b", "a"], true}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
