#!/bin/sh
set -euf

##
#
# Num: number utilties for mathematics and statistics.
#
# Syntax:
#
#     num [ options ] [ file ... ]
#
# Full documentation and code is available:
#
#   * http://www.numcommand.com
#   * https://github.com/numcommand/num
#
# ## Tracking
#
# Author: Joel Parker Henderson (joel@joelparkerhenderson.com)
# License: GPL, BSD, MIT
# Created: 2015-03-28
# Updated: 2015-11-29
# Version: 1.2.2
#
##

AWK=${AWK:-$(command -v gawk || command -v awk || echo "awk")}
"$AWK" -v awk="$AWK" '


############################################################################
#
# num-help.awk
#
##

function num_help() {
    print "Num version 1.3.0."
    print "Update 2015-11-30."
    print ""
    print "Copyright (C) 2015 Joel Parker Henderson."
    print "Please see http://github.com/numcommand"
    print ""
    print "Num uses this Awk:"
    print awk
    print ""
    cmd = awk "-Wversion 2>/dev/null || awk --version"
    print cmd
    system(cmd)
    exit
}

function num_help_init() {
    num_function_init(\
        "help version usage", 0,
        "Print help, version, usage.",
        "")
}

############################################################################
#
# num-out-err.awk
#
##

##
#
# Print a message to stdout.
#
# Example:
#
#     num_out("hello")
#     => Print "hello" to STDOUT
#
function num_out(msg) {
    print msg
}

##
#
# Print a message to stderr.
#
# Example:
#
#     num_err("hello")
#     => Print "hello" to STDERR
#
# This is purposefully POSIX compatible.
#
function num_err(msg) {
    print msg | "cat 1>&2"
}

############################################################################
#
# num-function.awk
#
##

##
#
# Initialize function metadata for a given function.
#
# Example:
#
#    function hello() {
#        print "hello world"
#    }
#
#    function hello_init() {
#        num_function_init("hello hi hola", "Print a greeting", "http://example.com/hello.html")
#    }
#
# The example creates these:
#
#    num_functions["hello", "names"] = "hello hi hola"
#    num_functions["hello", "help"] = "Print a greeting"
#    num_functions["hello", "link"] = "http://example.com/hello.html"
#    num_synonyms["hello"] = "hello"
#    num_synonyms["hi"] = "hello"
#    num_synonyms["hola"] = "hello"
#
##

function num_function_init(names, arity, help, link,  f, i, name, name_list) {
    split(names, names_arr)
    f = names_arr[1]
    num_functions[f, "names"] = names
    num_functions[f, "arity"] = arity
    num_functions[f, "help"] = help
    num_functions[f, "link"] = link
    for (i in names_arr) {
        name = names_arr[i]
        gsub(/_/,"", name)
        num_synonyms[name] = f
    }
}

############################################################################
#
# num-absolute-value.awk
#
##

##
#
# Absolute value.
#
# Examples:
#
#    abs(1) => 1
#    abs(-1) => 1
#
##

function num_absolute_value(x) {
    return (x >= 0) ? x : -x
}

# Alias
function num_abs(x) { return num_absolute_value(x) }

############################################################################
#
# num-sign.awk
#
##

##
#
# Return the sign of the value, either 1, -1, or 0.
#
# Examples:
#
#    sign(8) => 1
#    sign(-8) => -1
#    sign(0) => 0
#
##

function num_sign(x) {
    return (x > 0) - (x < 0)
}

############################################################################
#
# num-increment.awk
#
##

##
#
# Increment a value, i.e. add 1.
#
# Examples:
#
#    increment(1) => 2
#
##

function num_increment(x) {
    return x + 1
}

############################################################################
#
# num-round.awk
#
# We provide four kinds of rounding:
#
#   * round a.k.a. nint.
#   * round off a.k.a. truncate.
#   * round up a.k.a. ceiling.
#   * round down a.k.a. floor.
#
##

##
#
# Round to the nearest integer, a.k.a. nint().
#
# Examples:
#
#    num_round(1.9) => 2
#    num_round(-1.9) => -2
#
#    num_nint(1.9) => 2
#    num_nint(-1.9) => -2
#
##

function num_round(x) {
    return (x >= 0) ? int(x + 0.5) : int(x - 0.5)
}

# Alias
function num_nint(x) {
    return num_round(x)
}

##
#
# Round off the fractional part, a.k.a. truncate().
#
# Examples:
#
#    num_round_off(1.9) => 1
#    num_round_off(-1.9) => -1
#
#    num_truncate(1.9) => 1
#    num_truncate(-1.9) => -1
#
##

function num_round_off(x) {
    return int(x)
}

# Alias
function num_truncate(x) {
    return num_round_off(x)
}

##
#
# Round up, a.k.a. ceiling().
#
# Examples:
#
#    num_round_up(1.9) => 2
#    num_round_up(-1.9) => -1
#
#    num_ceiling(1.9) => 2
#    num_ceiling(-1.9) => -1
#
function num_round_up(x,  y) {
    y = int(x)
    return (x == y) ? x : (x >= 0) ? y + 1 : y
}

# Alias
function num_ceiling(x) {
    return num_round_up(x)
}

##
#
# Round down, a.k.a. floor().
#
# Examples:
#
#    num_round_down(1.9) => 1
#    num_round_down(-1.9) => -2
#
#    num_floor(1.9) => 1
#    num_floor(-1.9) => -2
#
##

function num_round_down(x,  y) {
    y = int(x)
    return (x == y) ? x : (x >= 0) ? y : y - 1
}

# Alias
function num_floor(x) {
    num_round_down(x)
}

############################################################################
#
# num-sum.awk
#
##

##
#
# Sum, a.k.a. total.
#
# Example:
#
#     num_sum(1 2 4) => 7
##

function num_sum(arr,  i, x) {
    for (i in arr) x += arr[i]
    return x
}

function num_sum_(num, num_, opts,  f) {
    f = "num_sum"
    if (!(f in num_)) num_[f] = num_sum(num)  # TODO optimize linear
    return num_[f]
}

function num_sum_init() {
    num_function_init(\
        "num_sum sum total", 0,
        "Get the sum, a.k.a. total.",
        "https://en.wikipedia.org/wiki/Summation")
}

############################################################################
#
# num-product.awk
#
##

##
#
# Product.
#
# Example:
#
#     num_product(1 2 4) => 8
#
##

function num_product(arr,  x) {
    x = 1
    for (i in arr) x *= arr[i]
    return x
}

function num_product_(num, num_, opts,  f) {
    f = "num_product"
    if (!(f in num_)) num_[f] = num_product(num)
    return num_[f]
}

function num_product_init() {
    num_function_init(\
        "num_product product", 0,
        "Get the product.",
        "https://wikipedia.org/wiki/Product_(mathematics)")
}

############################################################################
#
# num-arr.awk
#
##

##
#
# Dump an array, suitable for debugging.
#
# Example:
#
#     num_arr_dump(arr)
#     1 a
#     2 b
#     3 d
#
function num_arr_dump(arr) {
    for (k in arr) print k, arr[k]
}

##
#
# Is an array empty?
#
# Example:
#
#   split("", arr)
#   num_arr_empty(arr) => TRUE
#
# This is POSIX compatible.
#
function num_arr_empty(arr,  i) {
    for (i in arr) return FALSE
    return TRUE
}

##
#
# Length of an array.
#
# Example:
#
#     num_arr_length(1 2 4) => 3
#
# TODO: benchmark this POSIX implementation with
# any Gawk implementation, and if Gawk is much faster,
# then research a way to use the Gawk implementation.
#
function num_arr_length(arr,  i, len) {
    for (i in arr) len++
    return len
}

##
#
# Swap array items.
#
# Example:
#
#     arr = 4 5 6
#     num_arr_swap(arr, 1, 3) => 6 5 4
#
##

function num_arr_swap(A, i, j,   t) {
    t = A[i]; A[i] = A[j]; A[j] = t
}

##
#
# Get the closest value to a target value in an array.
#
# Example:
#
#    arr = 1 2 4
#    target = 2.5
#    num_arr_closest_value(arr, target) => 2
#
# If multiple values are equidistant to the target,
# then return the earliest index.
#
# TODO optimize when the array is already sorted,
# by using quicksort or similar divide-and-conquer.
#
function num_arr_closest_value(arr, target,  _closest_value, _closest_delta, _delta, x, i) {
    for (i in arr) {
        _delta = num_abs(arr[i] - target)
        if (_closest_delta == "" || _delta < _closest_delta) {
            _closest_value = arr[i]
            _closest_delta = _delta
        }
    }
    return _closest_value
}

##
#
# Join an array to a string, with a separator string.
#
# Examples:
#
#     num_arr_join(1 2 3, OFS) => "1 2 3"
#     num_arr_join(1 2 3, ",") => "1,2,3"
#
##

function num_arr_join(arr, sep,  s, i) {
    s = ""
    for (i = 1; i <= num_arr_length(arr); i++) s = s arr[i] sep
    return substr(s, 1, length(s) - length(sep))
}

##
#
# Join an array to a string, with a separator string, prefix, and suffix.
#
# Examples:
#
#     num_arr_join(1 2 3, OFS, "<", ">") => "<1> <2> <4>"
#     num_arr_join(1 2 3, ",", "(", ")") => "(1),(2),(4)"
#
##

function num_arr_join_with_prefix_suffix(arr, sep, prefix, suffix,  s, i) {
    s = ""
    for (i = 1; i <= num_arr_length(arr); i++)  s = prefix arr[i] suffix sep
    return substr(s, 1, length(s) - length(sep))
}

############################################################################
#
# num-list.awk
#
##

##
#
# Number of items, a.k.a. count, length.
#
# Example:
#
#     num_n(1 2 4) => 3
#
##

function num_n(arr) {
    return num_arr_length(arr)
}

function num_n_(num, num_, opts,  f) {
    f = "n"
    if (!(f in num_)) num_[f] = num_n(num)
    return num_[f]
}

function num_n_init() {
    num_function_init(\
        "num_n n count length size", 0,
        "Get the number of items, a.k.a. count, length, size.",
        "https://en.wikipedia.org/wiki/Enumeration")
}

##
#
# First item.
#
# Example:
#
#     num_first(1 2 4) => 1
#
##

function num_first(arr) {
    return arr[1]
}

function num_first_(num, num_, opts,  f) {
    f = "num_first"
    if (!(f in num_)) num_[f] = num_first(num)
    return num_[f]
}

function num_first_init() {
    num_function_init(\
        "num_first first head", 0,
        "Get the first item.",
        "https://en.wikipedia.org/wiki/Enumeration")
}

##
#
# Last item.
#
# Example:
#
#   num_last(1 2 4) => 4
#
##

function num_last(arr) {
    return arr[num_arr_length(arr)]
}

function num_last_(num, num_, opts,  f) {
    f = "num_last"
    if (!(f in num_)) num_[f] = num_last(num)
    return num_[f]
}

function num_last_init() {
    num_function_init(\
        "num_last last tail",
        "Get the last item.",
        "https://en.wikipedia.org/wiki/Enumeration")
}

##
#
# Minimum value.
#
# Example:
#
#     num_min(1 2 4) => 1
#
# This implementation does a scan of the entire array.
#
##

function num_min(arr,  _min, i) {
    _min = ""
    for (i in arr) {
        if (_min == "" || arr[i] < _min) {
            _min = arr[i]
        }
    }
    return _min
}

function num_min_(num, num_, opts,  f) {
    f = "num_min"
    if (!(f in num_)) num_[f] = num_min(num)  # TODO optimize ascending & descending
    return num_[f]
}

function num_min_init() {
    num_function_init(\
        "num_min min minimum least lowest", 0,
        "Get the minimum value, a.k.a. least, lowest.",
        "https://en.wikipedia.org/wiki/Maxima_and_minima")
}

##
#
# Maximum value.
#
# Example:
#
#     num_max(1 2 4) => 4
#
# This implementation does a scan of the entire array.
#
##

function num_max(arr,  _max, i) {
    _max = ""
    for (i in arr) {
        if (_max == "" || arr[i] > _max) {
            _max = arr[i]
        }
    }
    return _max
}

function num_max_(num, num_, opts,  f) {
    f = "num_max"
    if (!(f in num_)) num_[f] = num_max(num)  # TODO optimize ascending & descending
    return num_[f]
}

function num_max_init() {
    num_function_init(\
        "num_max max maximum greatest highest", 0,
        "Get the maximum value, a.k.a. greatest, highest.",
        "https://en.wikipedia.org/wiki/Maxima_and_minima")
}

##
#
# Range, a.k.a. spread.
#
# Example:
#
#     num_range(1 2 4) => 3
#
##

function num_range(arr) {
    return num_max(arr) - num_min(arr)
}

function num_range_(num, num_, opts,  f) {
    f = "num_range"
    if (!(f in num_)) num_[f] = num_max_(num, num_, opts) - num_min_(num, num_, opts)
    return num_[f]
}

function num_range_init() {
    num_function_init(\
        "num_range range interval breadth spread", 0,
        "Get the range, a.k.a. interval, breadth, spread.",
        "https://en.wikipedia.org/wiki/Range_(statistics)")
}

##
# Filter a list by doing a comparison.
#
#   * eq: equal to
#   * ne: not equal to
#   * lt: less than
#   * le: less than or equal to
#   * gt: greater than
#   * ge: greater than or equal to
#   * in: intra-range, i.e. include [min, max].
#   * ex: extra-range, i.e. exclude [min, max].
#
# Examples of select:
#
#     num_select_eq(1 2 3 4 5, 3) => 3
#     num_select_ne(1 2 3 4 5, 3) => 1 2 4 5
#     num_select_lt(1 2 3 4 5, 3) => 1 2
#     num_select_le(1 2 3 4 5, 3) => 1 2 3
#     num_select_gt(1 2 3 4 5, 3) => 4 5
#     num_select_ge(1 2 3 4 5, 3) => 3 4 5
#     num_select_in(1 2 3 4 5, 2, 4) => 2 3 4
#     num_select_ex(1 2 3 4 5, 2, 4) => 1 5
#
# Examples of reject i.e. delete:
#
#     num_reject_eq(1 2 3 4 5, 3) => 1 2 4 5
#     num_reject_ne(1 2 3 4 5, 3) => 3
#     num_reject_lt(1 2 3 4 5, 3) => 3 4 5
#     num_reject_le(1 2 3 4 5, 3) => 4 5
#     num_reject_gt(1 2 3 4 5, 3) => 1 2 3
#     num_reject_ge(1 2 3 4 5, 3) => 1 2
#     num_reject_in(1 2 3 4 5, 2, 4) => 1 5
#     num_reject_ex(1 2 3 4 5, 2, 4) => 2 3 4
#
##

## Select EQ

function num_select_eq(arr, x,  n) {
    for (i in arr) if (arr[i] == x) { n++ } else { delete arr[i] }
    return n
}

function num_select_eq_(num, num_, opts, x, f) {
    f = "num_select_eq " x
    if (!(f in num_)) num_[f] = num_["n"] = num_select_eq(num, x)
    return num_[f]
}

function num_select_eq_init() {
    num_function_init(\
        "num_select_eq select_eq eq", 0,
        "Select all values equal to x",
        "")
}

## Select NE

function num_select_ne(arr, x,  n) {
    for (i in arr) if (arr[i] != x) { n++ } else { delete arr[i] }
    return n
}

function num_select_ne_(num, num_, opts, x, f) {
    f = "num_select_ne " x
    if (!(f in num_)) num_[f] = num_["n"] = num_select_ne(num, x)
    return num_[f]
}

function num_select_ne_init() {
    num_function_init(\
        "num_select_ne select_ne ne", 0,
        "Select all values not equal to x",
        "")
}

## Select LT

function num_select_lt(arr, x,  n) {
    for (i in arr) if (arr[i] < x) { n++ } else { delete arr[i] }
    return n
}

function num_select_lt_(num, num_, opts, x, f) {
    f = "num_select_lt select_lt lt " x
    if (!(f in num_)) num_[f] = num_["n"] = num_select_lt(num, x)
    return num_[f]
}

function num_select_lt_init() {
    num_function_init(\
        "num_select_lt", 0,
        "Select all values less than x",
        "")
}

## Select LE

function num_select_le(arr, x,  n) {
    for (i in arr) if (arr[i] <= x) { n++ } else { delete arr[i] }
    return n
}

function num_select_le_(num, num_, opts, x, f) {
    f = "num_select_le " x
    if (!(f in num_)) num_[f] = num_["n"] = num_select_le(num, x)
    return num_[f]
}

function num_select_le_init() {
    num_function_init(\
        "num_select_le select_le le", 0,
        "Select all values less than or equal to x",
        "")
}

## Select GT

function num_select_gt(arr, x,  n) {
    for (i in arr) if (arr[i] > x) { n++ } else { delete arr[i] }
    return n
}

function num_select_gt_(num, num_, opts, x, f) {
    f = "num_select_gt select_gt gt" x
    if (!(f in num_)) num_[f] = num_["n"] = num_select_gt(num, x)
    return num_[f]
}

function num_select_gt_init() {
    num_function_init(\
        "num_select_gt", 0,
        "Select all values greater than x",
        "")
}

## Select GE

function num_select_ge(arr, x,  n) {
    for (i in arr) if (arr[i] >= x) { n++ } else { delete arr[i] }
    return n
}

function num_select_ge_(num, num_, opts, x, f) {
    f = "num_select_ge " x
    if (!(f in num_)) num_[f] = num_["n"] = num_select_ge(num, x)
    return num_[f]
}

function num_select_ge_init() {
    num_function_init(\
        "num_select_ge select_ge ge", 0,
        "Select all values greater than or equal to x",
        "")
}

## Select IN

function num_select_in(arr, min, max,  n) {
    for (i in arr) if (arr[i] >= min && arr[i] <= max) { n++ } else { delete arr[i] }
    return n
}

function num_select_in_(num, num_, opts, x, f) {
    f = "num_select_in " x
    if (!(f in num_)) num_[f] = num_["n"] = num_select_in(num, x)
    return num_[f]
}

function num_select_in_init() {
    num_function_init(\
        "num_select_in select_in in", 0,
        "Select all values intra-range [min, max]",
        "")
}

## Select EX

function num_select_ex(arr, min, max) {
    for (i in arr) if (arr[i] < min || arr[i] > max) { n++ } else { delete arr[i] }
    return n
}

function num_select_ex_(num, num_, opts, x, f) {
    f = "num_select_ex " x
    if (!(f in num_)) num_[f] = num_["n"] = num_select_ex(num, x)
    return num_[f]
}

function num_select_ex_init() {
    num_function_init(\
        "num_select_ex select_ex ex", 0,
        "Select all values extra-range [min, max]",
        "")
}

## Reject EQ

function num_reject_eq(arr, x,  n) {
    for (i in arr) if (arr[i] == x) { delete arr[i] } else { n++ }
    return n
}

function num_reject_eq_(num, num_, opts, x, f) {
    f = "num_reject_eq " x
    if (!(f in num_)) num_[f] = num_["n"] = num_reject_eq(num, x)
    return num_[f]
}

function num_reject_eq_init() {
    num_function_init(\
        "num_reject_eq reject_eq", 0,
        "Reject all values equal to x",
        "")
}

## Reject NE

function num_reject_ne(arr, x,  n) {
    for (i in arr) if (arr[i] != x) { delete arr[i] } else { n++ }
    return n
}

function num_reject_ne_(num, num_, opts, x, f) {
    f = "num_reject_ne " x
    if (!(f in num_)) num_[f] = num_["n"] = num_reject_ne(num, x)
    return num_[f]
}

function num_reject_ne_init() {
    num_function_init(\
        "num_reject_ne reject_ne", 0,
        "Reject all values not equal to x",
        "")
}

## Reject LT

function num_reject_lt(arr, x,  n) {
    for (i in arr) if (arr[i] < x) { delete arr[i] } else { n++ }
    return n
}

function num_reject_lt_(num, num_, opts, x, f) {
    f = "num_reject_lt " x
    if (!(f in num_)) num_[f] = num_["n"] = num_reject_lt(num, x)
    return num_[f]
}

function num_reject_lt_init() {
    num_function_init(\
        "num_reject_lt reject_lt", 0,
        "Reject all values less than x",
        "")
}

## Reject LE

function num_reject_le(arr, x,  n) {
    for (i in arr) if (arr[i] <= x) { delete arr[i] } else { n++ }
    return n
}

function num_reject_le_(num, num_, opts, x, f) {
    f = "num_reject_le " x
    if (!(f in num_)) num_[f] = num_["n"] = num_reject_le(num, x)
    return num_[f]
}

function num_reject_le_init() {
    num_function_init(\
        "num_reject_le reject_le", 0,
        "Reject all values less than or equal to x",
        "")
}

## Reject GT

function num_reject_gt(arr, x,  n) {
    for (i in arr) if (arr[i] > x) { delete arr[i] } else { n++ }
    return n
}

function num_reject_gt_(num, num_, opts, x, f) {
    f = "num_reject_gt " x
    if (!(f in num_)) num_[f] = num_["n"] = num_reject_gt(num, x)
    return num_[f]
}

function num_reject_gt_init() {
    num_function_init(\
        "num_reject_gt reject_gt", 0,
        "Reject all values greater than x",
        "")
}

## Reject GE

function num_reject_ge(arr, x,  n) {
    for (i in arr) if (arr[i] >= x) { delete arr[i] } else { n++ }
    return n
}

function num_reject_ge_(num, num_, opts, x, f) {
    f = "num_reject_ge reject_ge" x
    if (!(f in num_)) num_[f] = num_["n"] = num_reject_ge(num, x)
    return num_[f]
}

function num_reject_ge_init() {
    num_function_init(\
        "num_reject_ge", 0,
        "Reject all values greater than or equal to x",
        "")
}

## Reject IN

function num_reject_in(arr, min, max,  n) {
    for (i in arr) if (arr[i] >= min && arr[i] <= max) { delete arr[i] } else { n++ }
    return n
}

function num_reject_in_(num, num_, opts, x, f) {
    f = "num_reject_in " x
    if (!(f in num_)) num_[f] = num_["n"] = num_reject_in(num, x)
    return num_[f]
}

function num_reject_in_init() {
    num_function_init(\
        "num_reject_in reject_in", 0,
        "Reject all values intra-range [min, max]",
        "")
}

## Reject EX

function num_reject_ex(arr, min, max) {
    for (i in arr) if (arr[i] < min || arr[i] > max) { delete arr[i] } else { n++ }
    return n
}

function num_reject_ex_(num, num_, opts, x, f) {
    f = "num_reject_ex " x
    if (!(f in num_)) num_[f] = num_["n"] = num_reject_ex(num, x)
    return num_[f]
}

function num_reject_ex_init() {
    num_function_init(\
        "num_reject_ex reject_ex", 0,
        "Reject all values extra-range [min, max]",
        "")
}

############################################################################
#
# num-frequency.awk
#
##

##
#
# Frequency minimum.
#
# Example:
#
#     num_frequency_min(10 10 11 11 11) => 2
#
##

function num_frequency_min(arr,  min, i, seen) {
    for (i in arr) ++seen[arr[i]]
    return num_min(seen)
}

function num_frequency_min_(num, num_, opts,  f) {
    f = "num_frequency_min"
    if (!(f in num_)) num_[f] = num_frequency_min(num)  # TODO optimize when min and min are both wanted
    return num_[f]
}

function num_frequency_min_init() {
    num_function_init(\
        "num_frequency_min frequency_minimum freq_min", 0,
        "Get the frequency minimum: count the occurances of each value, and get the minimum count.",
        "https://en.wikipedia.org/wiki/Frequency_Minimum_(statistics)")
}

##
#
# Frequency maximum.
#
# Example:
#
#     num_frequency_max(10 10 11 11 11) => 3
#
##

function num_frequency_max(arr,  max, i, seen) {
    for (i in arr) ++seen[arr[i]]
    return num_max(seen)
}

function num_frequency_max_(num, num_, opts,  f) {
    f = "num_frequency_max"
    if (!(f in num_)) num_[f] = num_frequency_max(num)  # TODO optimize when min and max are both wanted
    return num_[f]
}

function num_frequency_max_init() {
    num_function_init(\
        "num_frequency_max frequency_maximum freq_max", 0,
        "Get the frequency maximum: count the occurances of each value, and get the maximum count.",
        "https://en.wikipedia.org/wiki/Frequency_Maximum_(statistics)")
}

############################################################################
#
# num-shift.awk
#
##

##
#
# Shift one item from the head of a list.
#
# Example:
#
#     arr = 1 2 4
#     num_shift(arr, 4)
#     => 4
#     => arr == 1 2
#
# Return the item.
#
function num_shift(arr,  item, i) {
    len = num_arr_length(arr)
    item = arr[1]
    for (i = 1; i < len; i++) arr[i] = arr[i+1]
    delete arr[len]
    return item
}

##
#
# Unshift one item onto the head of a list.
#
# Example:
#
#     arr = 1 2
#     num_unshift(arr, 4)
#     => 4
#     => arr == 4 1 2
#
# Return the item for chainability.
#
function num_unshift(arr, item,  i) {
    len = num_arr_length(arr)
    for (i = 1; i < len; i++) arr[i+1] = arr[i]
    arr[1] = item
    return item
}

############################################################################
#
# num-stack.awk
#
##

##
#
# Push one item on an array list.
#
# Example:
#
#     arr = 1 2
#     num_push(arr, 4)
#     => 4
#     => arr == 1 2 4
#
# Return the item for chainability.
#
function num_push(arr, item,  i) {
    i = num_arr_length(arr) + 1
    arr[i] = item
    return item
}

##
#
# Pop one item from an array list.
#
# Example:
#
#     arr = 1 2 4
#     num_pop(arr)
#     => 4
#     => arr == 1 2
#
# Return the item.
#
function num_pop(arr,  item, i) {
    i = num_arr_length(arr)
    item = arr[i]
    delete arr[i]
    return item
}

############################################################################
#
# num-queue.awk
#
##

##
#
# Enqueue one item to an array queue.
#
# Example:
#
#     arr = 1 2
#     num_enqueue(arr, 4)
#     => 4
#     => arr == 1 2 4
#
# Return the item for chainability.
#
function num_enqueue(arr, item,  i) {
    i = num_arr_length(arr) + 1
    arr[i] = item
    return item
}

##
#
# Dequeue one item from an array queue.
#
# Example:
#
#     arr = 1 2 4
#     num_dequeue(arr)
#     => 1
#     => arr == 2 4
#
# Return the item.
#
function num_dequeue(arr,  item, i) {
    return num_shift(arr)
}

############################################################################
#
# num-sort.awk
#
# Caution: This implementation requires the `asort` function,
# which we believe is available in current `gawk` implementations,
# but may not be POSIX-compliant. This calls the C qsort library.
#
# TODO: Research if `asort` is POSIX or if there are alternatives.
#
# TODO: Research if `asort` has more flexibility that we can use.
#
# TODO: Consider using Timsort, which tends to be faster for real
# world data that may already be sorted or partially sorted.
#
##

##
#
# Initialize.
#
# This tracks which metadata fields to preserve during a sort.
# For example, a sort will not affect the sum of the numbers,
# so the metadata can continue to cache the sum throughout a sort.
#
##

function num_sort_awk_init() {
    #TODO refactor
    NUM_SORT_MEMO["n"] = \
    NUM_SORT_MEMO["num_sum"] = \
    NUM_SORT_MEMO["num_mean"] = \
    NUM_SORT_MEMO["num_variance"] = \
    NUM_SORT_MEMO["num_sample_variance"] = \
    NUM_SORT_MEMO["num_population_variance"] = \
    NUM_SORT_MEMO["num_skewness"] = \
    NUM_SORT_MEMO["num_sample_skewness"] = \
    NUM_SORT_MEMO["num_population_skewness"] = \
    NUM_SORT_MEMO["num_kurtosis"] = \
    NUM_SORT_MEMO["num_sample_kurtosis"] = \
    NUM_SORT_MEMO["num_population_kurtosis"] = \
    NUM_SORT_MEMO["num_standard_deviation"] = \
    NUM_SORT_MEMO["num_sample_standard_deviation"] = \
    NUM_SORT_MEMO["num_population_standard_deviation"] = \
    NUM_SORT_MEMO["num_sum_of_squares"] = \
    NUM_SORT_MEMO["num_sum_of_cubes"] = \
    NUM_SORT_MEMO["num_sum_of_quads"] = \
    TRUE
}

##
#
# Remember metadata, then restore it.
#
##

function num_sort_before_(num, num_, opts, memo) {
    split("", memo)
    for (k in NUM_SORT_MEMO) if (k in num_) memo[k] = num_[k]
}

function num_sort_after_(num, num_, opts, memo) {
    split("", num_)
    for (k in NUM_SORT_MEMO) if (k in memo) num_[k] = memo[k]
}

##
#
# Sort ascending in place.
#
# Example:
#
#     num_sort_ascending(3 1 2) => 1 2 3
#
##

function num_sort_ascending(arr) {
    if (AWK_HAS_ASORT) {
        asort(arr, arr, "@val_num_asc")
    } else {
        num_err("Num needs a function for sort ascending. We expect to add a solution in Num version 2.")
    }
}

function num_sort_ascending_(num, num_, opts,  f, memo) {
    f = "num_sort_ascending"
    if (num_[f] != TRUE) {
        num_sort_before_(num, num_, opts, memo)
        num_sort_ascending(num)
        num_sort_after_(num, num_, opts, memo)
        num_[f] = TRUE
        num_["ascending"] = TRUE
        num_["strictly_descending"] = FALSE
    }
}

function num_sort_ascending_init() {
    num_function_init(\
        "num_sort_ascending sort_ascending sort_asc sort_up sort", 0,
        "Sort the values in ascending order.",
        "https://wikipedia.org/wiki/Sorting_algorithm")
}

##
#
# Sort descending in place.
#
# Example:
#
#     num_sort_descending(3 1 2) => 3 2 1
#
##

function num_sort_descending(arr) {
    if (AWK_HAS_ASORT) {
        asort(arr, arr, "@val_num_desc")
    } else {
        num_err("Num needs a function for sort descending. We expect to add a solution in Num version 2.")
    }
}

function num_sort_descending_(num, num_, opts,  f, memo) {
    f = "num_sort_descending"
    if (num_[f] != TRUE) {
        num_sort_before_(num, num_, opts, memo)
        num_sort_descending(num)
        num_sort_after_(num, num_, opts, memo)
        num_[f] = TRUE
        num_["descending"] = TRUE
        num_["strictly_ascending"] = FALSE
    }
}

function num_sort_descending_init() {
    num_function_init(\
        "num_sort_descending sort_descending sort_desc sort_down", 0,
        "Sort the values in descending order.",
        "https://wikipedia.org/wiki/Sorting_algorithm")
}

############################################################################
#
# num-sort.awk booleans
#
##

##
#
# Is the list sorted in ascending order?
#
# Examples:
#
#    1 2 3 => TRUE
#    3 2 1 => FALSE
#    2 2 2 => TRUE
#
# A.k.a. non-descending.
#
# Return TRUE iff each successive number is greater or equal.
#
function num_is_ascending_(num, num_, opts,  f, x, i, flag) {
    f = "num_ascending"
    if (!(f in num_)) {
        if (num_["strictly_ascending"] == TRUE) {
            num_[f] = TRUE
        } else if (num_["descending"] == TRUE || num_["strictly_descending"] == TRUE) {
            num_[f] = FALSE
        } else {
            flag = TRUE
            for (i in num) {
                if (x == "" || num[i] >= x) {
                    x = num[i]
                } else {
                    flag = FALSE
                    break
                }
            }
            if (flag == TRUE) {
                num_["strictly_descending"] = FALSE
            }
            num_[f] = flag
        }
    }
    return num_[f]
}

function num_is_ascending_init() {
    num_function_init(\
        "num_is_ascending is_ascending is_asc", 0,
        "Is the list sorted in ascending order?",
        "https://wikipedia.org/wiki/Sorting_algorithm")
}

##
#
# Is the list sorted in strictly ascending order?
#
# Examples:
#
#    1 2 3 => TRUE
#    3 2 1 => FALSE
#    2 2 2 => FALSE
#
# This is TRUE iff each successive number is greater.
#
function num_is_strictly_ascending_(num, num_, opts,  f, x, i, flag) {
    f = "num_strictly_ascending"
    if (!(f in num_)) {
        if (num_["descending"] == TRUE || num_["strictly_descending"] == TRUE) {
            num_[f] = FALSE
        } else {
            flag = TRUE
            for (i in num) {
                if (x == "" || num[i] > x) {
                    x = num[i]
                } else {
                    flag = FALSE
                    break
                }
            }
            if (flag == TRUE) {
                num_["ascending"] = TRUE
                num_["desending"] = num_["strictly_desending"] = FALSE
            }
            num_[f] = flag
        }
    }
    return num_[f]
}

function num_is_strictly_ascending_init() {
    num_function_init(\
        "num_is_strictly_ascending is_strictly_ascending is_strict_asc", 0,
        "Is the list sorted in strictly ascending order?",
        "https://wikipedia.org/wiki/Sorting_algorithm")
}

##
#
# Is the list sorted in descending order?
#
# Examples:
#
#    3 2 1 => TRUE
#    1 2 3 => FALSE
#    2 2 2 => TRUE
#
# A.k.a. non-ascending.
#
# Return TRUE when each successive number is lesser or equal.
#
function num_is_descending_(num, num_, opts,  f, x, i, flag) {
    f = "num_descending"
    if (!(f in num_)) {
        if (num_["strictly_descending"] == TRUE) {
            num_[f] = TRUE
        } else if (num_["ascending"] == TRUE || num_["strictly_ascending"] == TRUE) {
            num_[f] = FALSE
        } else {
            flag = TRUE
            for (i in num) {
                if (x == "" || num[i] <= x) {
                    x = num[i]
                } else {
                    flag = FALSE
                    break
                }
            }
            if (flag == TRUE) {
               num_["strictly_ascending"] = FALSE
            }
            num_[f] = flag
        }
    }
    return num_[f]
}

function num_is_descending_init() {
    num_function_init(\
        "num_is_descending is_descending is_desc", 0,
        "Is the list sorted in descending order?",
        "https://wikipedia.org/wiki/Sorting_algorithm")
}

##
#
# Is the list sorted in strictly descending order?
#
# Examples:
#
#    3 2 1 => TRUE
#    1 2 3 => FALSE
#    2 2 2 => FALSE
#
# Return TRUE when each successive number is lesser.
#
function num_is_strictly_descending_(num, num_,   f, x, i, flag) {
    f = "num_strictly_descending"
    if (!(f in num_)) {
        if ("ascending" in num) {
            num_[f] = ! num_["ascending"]
        } else {
            flag = TRUE
            for (i in num) {
                if (x == "" || num[i] < x) {
                    x = num[i]
                } else {
                    flag = FALSE
                    break
                }
            }
            if (flag == TRUE) {
                num_["ascending"] = num_["strictly_ascending"] = FALSE
                num_["desending"] = TRUE
            }
            num_[f] = flag
        }
    }
    return num_[f]
}

function num_is_strictly_descending_init() {
    num_function_init(\
        "num_is_strictly_descending is_strictly_descending is_strict_desc", 0,
        "Is the list sorted in strictly descending order?",
        "https://wikipedia.org/wiki/Sorting_algorithm")
}

############################################################################
#
# num-insertion-sort.awk
#
##

##
#
# Insertion sort.
#
# This implementation is a slightly faster version that moves A[i]
# to its position in one go and only performs one assignment in the
# inner loop body.
#
# Thanks to https://en.wikipedia.org/wiki/Insertion_sort
#
##

function num_insertion_sort(A) {
    num_insertion_sort_slice(A, 1, num_arr_length(A))
}

function num_insertion_sort_slice(A, lo, hi,  i, j, x) {
    if (lo >= hi) return
    for (i = lo + 1; i <= hi; i++) {
        x = A[i]
        j = i
        while (j > lo && A[j-1] > x) {
            A[j] = A[j-1]
            j--
        }
        A[j] = x
    }
}

############################################################################
#
# num-quicksort.awk
#
##

function num_quicksort_awk_init() {
    NUM_QUICKSORT_INSERTION_SORT_THRESHOLD = 12
}

##
#
# Quicksort.
#
# Quicksort selects a pivot and divides the data into values above and
# below the pivot. Sorting then recurses on these sub-lists.
#
# From http://awk.info/?doc/quicksort.html
#
# TODO: research implementing the pivot by using Tukeys ninther,
# which is a median of medians, and may be a faster heuristic.
# See http://www.johndcook.com/blog/2009/06/23/tukey-median-ninther/
#
# TODO: research implementing the small size sort using Shell sort,
# which is similar to insertion sort yet better for typical data.
# See https://en.wikipedia.org/wiki/Shellsort
#
# TODO: research upgrading from single pivot to dual pivot.
# http://stackoverflow.com/questions/20917617/whats-the-difference-of-dual-pivot-quick-sort-and-quick-sort
# http://stackoverflow.com/questions/32001841/how-to-implement-dual-pivot-quicksort-in-python
# http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/DualPivotQuicksort.java
#
##

function num_quicksort(A) {
    num_quicksort_slice(A, 1, num_arr_length(A))
}

function num_quicksort_slice(A, lo, hi,   pivot_index) {
    if (lo >= hi) return
    if (hi - lo < NUM_QUICKSORT_INSERTION_SORT_THRESHOLD) {
        num_insertion_sort_slice(A, lo, hi)
        return
    }
    pivot_index = num_quicksort_pivot_index_via_median_of_three_sort_slice(A, lo, hi)
    pivot_index = num_partition_slice(A, lo, hi, pivot_index)
    num_quicksort_slice(A, lo, pivot_index - 1)
    num_quicksort_slice(A, pivot_index + 1, hi)
}

##
#
# Partition an array slice using a given pivot index.
#
# Return the new pivot index.
#
##

function num_partition_slice(A, lo, hi, pivot_index,  left, right, pivot_value, t) {
    if (lo >= hi) return lo
    left = lo
    right = hi
    pivot_value = A[pivot_index]
    num_arr_swap(A, left, pivot_index)
    while (left < right) {
        while (left <= hi && A[left] <= pivot_value) left++
        while (right >= lo && A[right] > pivot_value) right--
        if (left < right) num_arr_swap(A, left, right)
    }
    pivot_index = right
    num_arr_swap(A, lo, pivot_index)
    return pivot_index
}

##
#
# Choose a quicksort pivot index by using a random number.
# This is a naive implemenation and is here for benchmarking.
# Typically you will never need this function for real-world data.
#
##

function num_quicksort_pivot_index_via_rand(A, lo, hi) {
    return lo + int((hi - lo + 1) * rand())
}

##
#
# Choose a quicksort pivot index by using the "median of three" heuristic
# with a swap sort of the three items for efficiency on the next pivot.
#
# Compared to picking the pivot randomly, the median of three heuristic:
#
#   * Ensures that a common case of fully sorted data remains optimal.
#   * Is more difficult for an attacker to manipulate into the worst case.
#   * Is often faster than a PRNG, which is often relatively slow.
#
# The median of three looks at the first, middle and last elements of
# the array, and choose the median of those as the pivot.
#
# To get the "full effect" of the median of three, it is also important
# to sort those three items, not just use the median as the pivot --
# this does not affect what is chosen as the pivot in the current
# iteration, but can/will affect what is used as the pivot in the next
# recursive call, which helps to limit the bad behavior for a few
# initial orderings (one that turns out to be particularly bad in many
# cases is an array that is sorted, except for having the smallest
# element at the high end of the array (or largest element at the low
# end)).
#
# Thanks to http://stackoverflow.com/questions/7559608/median-of-three-values-strategy
#
# To calculate the midpoint, we prefer (lo+(hi−lo)/2) instead of the naive
# simpler ((hi+lo)/2) because the former does not risk integer overflow.
#
# Return the pivot index.
#
##

function num_quicksort_pivot_index_via_median_of_three_sort(A) {
    num_quicksort_pivot_index_via_median_of_three_sort_slice(A, 1, num_arr_length(A))
}

function num_quicksort_pivot_index_via_median_of_three_sort_slice(A, lo, hi,  mid) {
    if (lo == hi) return lo
    mid = lo + int((hi - lo) / 2)
    if (A[hi]  < A[lo])  num_arr_swap(A, lo, hi)
    if (A[mid] < A[lo])  num_arr_swap(A, mid, lo)
    if (A[hi]  < A[mid]) num_arr_swap(A, hi, mid)
    return mid
}

############################################################################
#
# num-map.awk
#
# Map helpers.
#
# Call `map_before` to remember the number of items.
#
# Call `map_after` to invalidate all metadata then
# restore the number of items.
#
##

function map_before_(num, num_, opts, memo) {
    memo["n"] = num_["n"]
}

function map_after_(num, num_, opts, memo) {
    split("",num_)
    num_["n"] = memo["n"]
}

############################################################################
#
# num-unique.awk
#
##

##
#
# Is the list all unique items?
#
# Examples:
#
#    1 2 3 => TRUE
#    1 2 2 => FALSE
#
function num_is_unique_(num, num_, opts,  f, i, seen, flag) {
    f = "num_unique"
    if (!(f in num_)) {
        flag = TRUE
        split("", seen)
        for (i in num) {
            if (num[i] in seen) {
               flag = FALSE
               break
            } else {
              seen[num[i]] = TRUE
            }
        }
        num_[f] = flag
    }
    return num_[f]
}

function num_is_unique_init() {
    num_function_init(\
        "num_is_unique is_unique is_uniq", 0,
        "Is the list all unique items?",
        "https://en.wikipedia.org/wiki/Uniqueness_quantification")
}

############################################################################
#
# num-map-increment.awk
#
##

##
#
# Map increment.
#
# Example:
#
#     num_map_increment_(1 2 3) => -2 3 4
#
##

function num_map_increment(arr) {
    for (i in arr) arr[i] = num_increment(arr[i])
}

function num_map_increment_(num, num_, opts,  f, i, memo) {
    f = "num_map_increment"
    if (num_[f] != TRUE) {
        map_before_(num, num_, opts, memo)
        num_map_increment(num)
        map_after_(num, num_, opts, memo)
        num_[f] = TRUE
    }
    return ""
}

function num_map_increment_init() {
    num_function_init(\
        "num_map_increment increment", 0,
        "Map using increment.",
        "https://en.wikipedia.org/wiki/Increment_and_decrement_operators")
}

############################################################################
#
# num-map-absolute-value.awk
#
##

##
#
# Map absolute value.
#
# Example:
#
#     num_map_absolute_value_(1 -2 3) => 1 2 3
#
##

function num_map_absolute_value(arr) {
    for (i in arr) arr[i] = num_absolute_value(arr[i])
}

function num_map_absolute_value_(num, num_, opts,  f, i, memo) {
    f = "num_map_absolute_value"
    if (num_[f] != TRUE) {
        map_before_(num, num_, opts, memo)
        num_map_absolute_value(num)
        map_after_(num, num_, opts, memo)
        num_[f] = TRUE
    }
    return ""
}

function num_map_absolute_value_init() {
    num_function_init(\
        "num_map_absolute_value absolute_value abs magnitude", 0,
        "Map using absolute value.",
        "https://en.wikipedia.org/wiki/Absolute_value_(algebra)")
}

# Alias
function num_map_abs(arr) { return num_map_absolute_value(arr) }
function num_map_abs_(num, num_, opts) { return num_map_absolute_value_(num, num_, opts) }

############################################################################
#
# num-map-sign.awk
#
##

##
#
# Map sign.
#
# Example:
#
#     num_map_sign_(-8 0 8) => -1 0 1
#
##

function num_map_sign(arr) {
    for (i in arr) arr[i] = num_sign(arr[i])
}

function num_map_sign_(num, num_, opts,  f, i, memo) {
    f = "num_map_sign"
    if (num_[f] != TRUE) {
        map_before_(num, num_, opts, memo)
        num_map_sign(num)
        map_after_(num, num_, opts, memo)
        num_[f] = TRUE
    }
    return ""
}

function num_map_sign_init() {
    num_function_init(\
        "num_map_sign sign sgn signum", 0,
        "Map using sign.",
        "https://en.wikipedia.org/wiki/Sign_function")
}

############################################################################
#
# num-map-round.awk
#
##

##
#
# Initialize.
#
##

function num_map_round_awk_init() {
    #TODO refactor
    NUM_MAP_ROUND_MEMO["n"] = \
    NUM_MAP_ROUND_MEMO["sorted"] = \
    NUM_MAP_ROUND_MEMO["ascending"] = \
    NUM_MAP_ROUND_MEMO["descending"] = \
    TRUE
}

##
#
# Map round, a.k.a. round towards nearest integer, nint.
#
# Example:
#
#     num_map_round(-1.9 1.9) => -2 2
#
##

function num_map_round(arr) {
    for (i in arr) arr[i] = num_round(arr[i])
}

function num_map_round_(num, num_, opts,  f, memo, i) {
    if (num_["integer"] == TRUE) return
    f = "num_map_round"
    if (num_[f] != TRUE) {
        num_map_round_before_(num, num_, opts, f, memo)
        num_map_round(num)
        num_map_round_after_(num, num_, opts, f, memo)
    }
    return ""
}

function num_map_round_init() {
    num_function_init(\
        "num_map_round round round_towards_nearest nearest_integer n_int", 0,
        "Map using round, a.k.a. round towards nearest, nint.",
        "https://en.wikipedia.org/wiki/Rounding")
}

# Alias
function num_map_nint(arr) {
    return num_map_round(arr)
}

# Alias
function num_map_nint_(num, num_, opts) {
    return num_map_round_(num, num_, opts)
}

##
#
# Map: round off, a.k.a. round towards zero, truncate.
#
# Example:
#
#     num_map_round_off(-1.9 1.9) => -1 1
#
##

function num_map_round_off(arr) {
    for (i in arr) arr[i] = num_round_off(arr[i])
}

function num_map_round_off_(num, num_, opts,  f, memo) {
    if (num_["integer"] == TRUE) return ""
    f = "num_map_round_off"
    if (num_[f] != TRUE) {
        num_map_round_before_(num, num_, opts, f, memo)
        num_map_round_off(num)
        num_map_round_after_(num, num_, opts, f, memo)
    }
    return ""
}

function num_map_round_off_init() {
    num_function_init(\
        "num_map_round_off round_off round_towards_zero truncate", 0,
        "Map using round off, a.k.a. round towards zero, truncate.",
        "https://en.wikipedia.org/wiki/Rounding")
}

# Alias
function num_map_truncate(arr) {
    return num_map_round_off(arr)
}

# Alias
function num_map_truncate_(num, num_, opts) {
    return num_map_round_off_(num, num_, opts)
}

##
#
# Map: round up, a.k.a. round towards positive infinity, ceiling.
#
# Example:
#
#     num_map_round_up(-1.9 1.9) => -1 2
#
##

function num_map_round_up(arr) {
    for (i in arr) arr[i] = num_round_up(arr[i])
}

function num_map_round_up_(num, num_, opts,  f, memo) {
    if (num_["integer"] == TRUE) return ""
    f = "num_map_round_up"
    if (num_[f] != TRUE) {
        num_map_round_before_(num, num_, opts, f, memo)
        num_map_round_up(num)
        num_map_round_after_(num, num_, opts, f, memo)
    }
    return ""
}

function num_map_round_up_init() {
    num_function_init(\
        "num_map_round_up round_up ceiling", 0,
        "Map using round up, a.k.a. round towards positive infinity, ceiling.",
        "https://en.wikipedia.org/wiki/Rounding")
}

# Alias
function num_map_ceiling(arr) {
    return num_map_round_up(arr)
}

# Alias
function num_map_ceiling_(num, num_, opts) {
    return num_map_round_up_(num, num_, opts)
}

##
#
# Map: round down, a.k.a. round towards negative infinity, floor.
#
# Example:
#
#     num_map_round_down(-1.9 1.9) => -2 1
#
##

function num_map_round_down(arr) {
    for (i in arr) arr[i] = num_round_down(arr[i])
}

function num_map_round_down_(num, num_, opts,  f, memo) {
    if (num_["integer"] == TRUE) return ""
    f = "num_map_round_down"
    if (num_[f] != TRUE) {
        num_map_round_before_(num, num_, opts, f, memo)
        num_map_round_down(num)
        num_map_round_after_(num, num_, opts, f, memo)
    }
    return ""
}

function num_map_round_down_init() {
    num_function_init(\
        "num_map_round_down round_down floor", 0,
        "Map using round down, a.k.a. round towards negative infinity, floor.",
        "https://en.wikipedia.org/wiki/Rounding")
}

# Alias
function num_map_floor(arr) {
    return num_map_round_down(arr)
}

# Alias
function num_map_floor_(num, num_, opts) {
    return num_map_round_down_(num, num_, opts)
}

##
#
# Map round after helper: call this function only from within
# each of the rounding functions, before the work begins.
# This function saves as much metadata as possible.
#
##

function num_map_round_before_(num, num_, opts, f, memo) {
    for (k in NUM_MAP_ROUND_MEMO) if (k in num_) memo[k] = num_[k]
}

##
#
# Map round after helper: call this function only from within
# each of the rounding functions, after the work ends.
# This function restores as much metadata as possible.
#
##

function num_map_round_after_(num, num_, opts, f, memo) {
    split("",num_)
    for (k in NUM_MAP_ROUND_MEMO) if (k in memo) num_[k] = memo[k]
    num_[f] = TRUE
    num_["integer"] = TRUE
}

############################################################################
#
# num-map-normalize.awk
#
##

##
#
# Map: normalize each value to be 0 to 1.
#
# Example:
#
#     num_map_normalize(1 2 4) => 0 0.33333 1
#
##

function num_map_normalize_with_min_max(arr, min_old, max_old, min_new, max_new) {
    multiply = (max_new - min_new) / (max_old - min_old)
    add = min_new - (multiply * min_old)
    for (i in arr) arr[i] = arr[i] * multiply + add
}

function num_map_normalize(arr) {
    return num_map_normalize_with_min_max(arr, num_min(arr), num_max(arr), 0, 1)
}

function num_map_normalize_(num, num_, opts,  f, min_old, max_old, min_new, max_new, multiply, add) {
    f = "num_map_normalize"
    if (num_[f] != TRUE) {
        map_before_(num, num_, opts, memo)
        x = num_map_normalize_with_min_max(num, num_min_(num, num_, opts), num_max_(num, num_, opts), 0, 1)
        map_after_(num, num_, opts, memo)
        num_[f] = TRUE
    }
    return ""
}

function num_map_normalize_init() {
    num_function_init(\
        "num_map_normalize normalize norm", 0,
        "Map using normalize.",
        "https://wikipedia.org/wiki/Normalization_(statistics)")
}

############################################################################
#
# num-mean.awk
#
##

##
#
# Mean, a.k.a. arithmetic mean, average.
#
# Example:
#
#     num_mean(1 2 4) => 2.33333
#
##

function num_mean(arr) {
    return num_sum(arr) / num_n(arr)
}

function num_mean_(num, num_, opts,  f, _n, _min, _max, _sum) {
    f = "num_mean"
    if (!(f in num_)) {
        if (num_["linear"]) {
            _n = num_n_(num, num_, opts)
            _min = num_min_(num, num_, opts)
            _max = num_max_(num, num_, opts)
            num_[f] = _min + (_max - _min) / _n
        } else {
            _n =  num_n_(num, num_, opts)
            _sum = num_sum_(num, num_, opts)
            num_[f] = _sum / _n
        }
    }
    return num_[f]
}

function num_mean_init() {
    num_function_init(\
        "num_mean mean average avg", 0,
        "Get the mean, a.k.a artihmetic mean, average.",
        "https://en.wikipedia.org/wiki/Mean")
}

############################################################################
#
# num-mean-absolute-deviation.awk
#
##

##
#
# Mean absolute deviation.
#
# The average distance between each value and the mean.
#
# Example:
#
#     num_mean_absolute_deviation(1 2 4) => 1.11111
##

function num_mean_absolute_deviation(arr,  _mean, _n, x) {
    _mean = num_mean(arr)
    _n = num_n(arr)
    for (i in arr) x += num_absolute_value(arr[i] - _mean)
    return x / _n
}

function num_mean_absolute_deviation_(num, num_, opts,  f, _n, _mean, i, x) {
    f = "num_mean_absolute_deviation"
    if (!(f in num_)) {
        _n = num_n_(num, num_, opts)
        _mean = num_mean_(num, num_, opts)
        for (i in num) x += num_absolute_value(num[i] - _mean)
        num_[f] = x / _n
    }
    return num_[f]
}

function num_mean_absolute_deviation_init() {
    num_function_init(\
        "num_mean_absolute_deviation mean_absolute_deviation mad", 0,
        "Get the average distance between each value and the mean.",
        "https://en.wikipedia.org/wiki/Average_absolute_deviation")
}

# Alias
function num_mad(arr) { return num_mean_absolute_deviation(arr) }
function num_mad_(num, num_, opts) { return num_mean_absolute_deviation_(num, num_, opts) }

############################################################################
#
# num-sum-of-mean-deviation.awk
#
##

##
#
# Sum of mean deviation exp.
#
# Example:
#
#     arr = 1 2 4
#     exponent = 3
#     sum_of_mean_deviation_exp(arr, exponent) => sum of cubes
#
# Typically useful to calculate variance, skewness, kurtosis.
#
##

function num_sum_of_mean_deviation_exp(arr, mean, exponent, i, x) {
    for (i in arr) x += (arr[i] - mean) ^ exponent
    return x
}

function num_sum_of_mean_deviation_exp_(num, num_, opts, mean, exponent) {
    f = "num_sum_of_mean_deviation_exp" ":mean:" mean ":exponent:" exponent
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp(num, mean, exponent)
    return num_[f]
}

function num_sum_of_mean_deviation_exp_init() {
    num_function_init(\
        "num_sum_of_mean_deviation_exp", 0,
        "Get the sum of mean deviation for a given exponent.",
        "https://en.wikipedia.org/wiki/Deviation_(statistics)")
}

##
#
# Sum of Squares, a.k.a. the sum of each deviation to the power of 2, a.k.a. SS.
#
# Example:
#
#     num_sum_of_squares(1 2 4) => 4.66667
#
##

function num_sum_of_squares(arr) {
    return num_sum_of_mean_deviation_exp(arr, num_mean(arr), 2)
}

function num_sum_of_squares_(num, num_, opts) {
    f = "num_sum_of_squares"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, opts, num_mean_(num, num_, opts), 2)
    return num_[f]
}

function num_sum_of_squares_init() {
    num_function_init(\
        "num_sum_of_squares sum_of_squares sum_squares ss mean_squared_error mse", 0,
        "Get the sum of squares, a.k.a. sum of each mean deviation to the power of 2, a.k.a. SS",
        "https://en.wikipedia.org/wiki/Deviation_(statistics)")
}

##
#
# Sum of Cubes, a.k.a. sum of each mean deviation to the power of 3.
#
# Example:
#
#     1 2 4 => 2.22222
#
##

function num_sum_of_cubes(arr) {
    return num_sum_of_mean_deviation_exp(arr, num_mean(arr), 3)
}

function num_sum_of_cubes_(num, num_, opts) {
    f = "num_sum_of_cubes"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, opts, num_mean_(num, num_, opts), 3)
    return num_[f]
}

function num_sum_of_cubes_init() {
    num_function_init(\
        "num_sum_of_cubes sum_of_cubes sum_cubes", 0,
        "Get the  sum of cubes, a.k.a. sum of each mean deviation to the power of 3.",
        "https://en.wikipedia.org/wiki/Mean_squared_error")
}

##
#
# Sum of Quads, a.k.a. sum of each mean deviation to the power of 4.
#
# Example:
#
#     1 2 4 => TODO
#
##

function num_sum_of_quads(arr) {
    return num_sum_of_mean_deviation_exp(arr, num_mean(arr), 4)
}

function num_sum_of_quads_(num, num_, opts) {
    f = "num_sum_of_quads"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, opts, num_mean_(num, num_, opts), 4)
    return num_[f]
}

function num_sum_of_quads_init() {
    num_function_init(\
        "num_sum_of_quads sum_of_quads sum_quads", 0,
        "Get the  sum of quads, a.k.a. sum of each mean deviation to the power of 4.",
        "https://en.wikipedia.org/wiki/Mean_squared_error")
}

############################################################################
#
# num-meanest.awk
#
##

##
#
# Meanest, i.e. the value closest to the mean.
#
# Example:
#
#     num_meanest(1 2 4) => 2
#
##

function num_meanest(arr) {
    return num_arr_closest_value(arr, num_mean(arr))
}

function num_meanest_(num, num_, opts,  f, _n, _mean, i, x) {
    f = "num_meanest"
    if (!(f in num_)) num_[f] = num_arr_closest_value(num, num_mean_(num, num_, opts))
    return num_[f]
}

function num_meanest_init() {
    num_function_init(\
        "num_meanest meanest", 0,
        "Get the value that is closest to the mean.",
        "https://en.wikipedia.org/wiki/Mean")
}

############################################################################
#
# num-trimean.awk
#
##

##
#
# Trimean.
#
# Example:
#
#    num_trimean(1 1.75 3 27.75 99) => 8.875
#
# Requirement: the array is sorted.
#
##

function num_trimean(arr,  _q1, _q2, _q3) {
    _q1 = num_quartile_1(arr)
    _q2 = num_quartile_2(arr)
    _q3 = num_quartile_3(arr)
    return (_q1 + _q2 + _q2 + _q3) / 4
}

function num_trimean_(num, num_, opts,  f, _q1, _q2, _q3) {
    f = "num_trimean"
    if (!(f in num_)) {
        _q1 = num_quartile_1_(num, num_, opts)
        _q2 = num_quartile_2_(num, num_, opts)
        _q3 = num_quartile_3_(num, num_, opts)
        num_[f] = (_q1 + _q2 + _q2 + _q3) / 4
    }
    return num_[f]
}

function num_trimean_init() {
    num_function_init(\
        "num_trimean trimean", 0,
        "Calculate the trimean.",
        "https://wikipedia.org/wiki/Trimean")
}

############################################################################
#
# num-trimmed-mean.awk
#
##

##
#
# Trimmed mean. This implemention deletes values that are not in the IQR.
#
# Example:
#
#     num_trimmed_mean(1 2 3 4 5) => 2 3 4
#
# TODO: implement, and also upgrade to enable custom ranges.
#
##

function num_trimmed_mean_min_max(arr,  q1, q3) {
    q1 = num_quartile_1(arr)
    q3 = num_quartile_3(arr)
    return TODO
}

function num_trimmed_mean_(num, num_, opts,  f) {
    f = "num_trimmed_mean"
    if (!(f in num_)) {
        q1 = num_quartile_1_(num, num_, opts)
        q3 = num_quartile_3_(num, num_, opts)
        num_[f] = TODO
    }
    return num_[f]
}

function num_trimmed_mean_init() {
    num_function_init(\
        "num_trimmed_mean trimmed_mean truncated_mean", 0,
        "Calculate the trimmed mean",
        "https://en.wikipedia.org/wiki/Truncated_Mean")
}

############################################################################
#
# num-median.awk
#
##

##
#
# Median of an array slice.
#
# Example:
#
#     num_median_slice((1 2 4), 1, 3) => 2
#     num_median_slice((1 2 4 9 9), 1, 3) => 3
#
##

function num_median_of_slice(arr, start, stop,  _n, i) {
    _n = 1 + stop - start
    if (_n % 2) {
        i = (start - 1) + ((_n + 1) / 2)
        return arr[i]
    } else {
        i = (start - 1) + (_n / 2)
        return (arr[i] + arr[i+1]) / 2
    }
}

##
#
# Median.
#
# Example:
#
#     num_median(1 2 4) => 2
#     num_median(1 2 4 99) => 3
#
# Requirement: the array is sorted.
#
##

function num_median(arr,  _n, i) {
    _n = num_n(arr)
    if (_n % 2) {
        i = (_n + 1) / 2
        return arr[i]
    } else {
        i = _n / 2
        return (arr[i] + arr[i+1]) / 2.0
    }
}

function num_median_(num, num_, opts,  f, i, _n) {
    f = "num_median"
    if (!(f in num_)) {
        _n = num_n_(num, num_, opts)
        num_sort_ascending_(num, num_, opts)
        if (_n % 2) {
            i = (_n + 1) / 2
            num_[f] = num_["num_median_low"] = num_["num_median_high"] = num[i]
        } else {
            i = _n / 2
            num_["num_median_low"] = num[i]
            num_["num_median_high"] = num[i+1]
            num_[f] = (num[i] + num[i+1]) / 2.0
        }
    }
    return num_[f]
}

function num_median_init() {
    num_function_init(\
        "num_median median med", 0,
        "Get the median.",
        "https://en.wikipedia.org/wiki/Median")
}

##
#
# Median low: get the lesser median.
#
# Example:
#
#     num_median_low(1 2 4) => 2
#     num_median_low(1 2 4 99) => 2
#
function num_median_low(arr,  _n, i) {
    _n = num_n(arr)
    if (_n % 2) {
        i = (_n + 1) / 2
    } else {
        i = _n / 2
    }
    return arr[i]
}

function num_median_low_(num, num_, opts,  f, _n) {
    f = "num_median_low"
    if (!(f in num_)) {
        num_median_(num, num_, opts)  # n.b. median sets median_low
    }
    return num_[f]
}

function num_median_low_init() {
    num_function_init(\
        "num_median_low median_low med_low", 0,
        "Get the median that is lower a.k.a. lesser.",
        "https://en.wikipedia.org/wiki/Median")
}

##
#
# Median high: get the greater median.
#
# Example:
#
#     num_median_high(1 2 4) => 2
#     num_median_high(1 2 4 99) => 4
#
##

function num_median_high(arr,  _n, i) {
    _n = num_n(arr)
    if (_n % 2) {
        i = (_n + 1) / 2
    } else {
        i = (_n / 2) + 1
    }
    return arr[i]
}

function num_median_high_(num, num_, opts,  f, _n) {
    f = "num_median_high"
    if (!(f in num_)) {
        num_median_(num, num_, opts)  # n.b. median sets median_high
    }
    return num_[f]
}

function num_median_high_init() {
    num_function_init(\
        "num_median_high median_high med_high", 0,
        "Get the median that is higher a.k.a. greater.",
        "https://en.wikipedia.org/wiki/Median")
}

############################################################################
#
# num-mode.awk
#
##

##
#
# Modes: get the modes, which may be a number, or list, or UNDEF.
#
# The modes are:
#
#   * The value that appears most often in a set of data.
#   * If mutlipe values appear as often, there are multiple modes.
#   * If each value occurs only once, then there are no modes.
#
# Examples:
#
#     1 2 2 3 => 2
#     1 1 2 3 3 => 1 3
#     1 2 3 => UNDEF
#
# Output the `modes` array.
# Return the `modes` array length.
#
# Examples:
#
#     num_modes(1 2 3, modes)     => 0, modes == []
#     num_modes(1 2 2 3, modes)   => 1, modes == [2]
#     num_modes(1 1 2 3 3, modes) => 2, modes == [1, 3]
#
##

function num_modes(arr, modes,  modes_i, i, n, seen, max) {
    for (i in arr) {
        # Optimization: use one scan
        n = ++seen[arr[i]]
        if (max == "" || max < n) max = n
    }
    split("", out); out_i = 0
    if (max > 1) {
        for (i in seen) {
            if (seen[i] == max) {
                out[++out_i] = i
            }
        }
    }
    return out_i
}

function num_modes_(num, num_, opts, modes,  f, modes_i) {
    f = "num_modes"
    if (!(f in num_)) {
        if (num_["unique"]) {
            num_[f] = num_["num_mode_min"] = num_["num_mode_max"] = UNDEF
        } else {
            # TODO: optimize if we know the array is sorted
            num_[f] = modes_i = num_modes(num, modes)
            if (modes_i == 0) {
                num_["unique"] = TRUE
                num_["num_mode_min"] = num_["num_mode_max"] = UNDEF
            } else {
                num_["unique"] = FALSE
            }
            num_[f] = modes_i
        }
    }
    return num_[f]
}

function num_modes_init() {
    num_function_init(\
        "num_modes modes", 0,
        "Get the modes, which is a list.",
        "https://en.wikipedia.org/wiki/Mode_(statistics)")
}

##
#
# Mode min: get the minimum mode, if any, or UNDEF.
#
# TODO: IMPLEMENT
#
# Examples:
#
#     num_mode_min(1 2 3) => UNDEF
#     num_mode_min(1 2 2 3) => 2
#     num_mode_min(1 1 2 4 4) => 1
#
##

function num_mode_min(arr) {
    return TODO
}

function num_mode_min_(num, num_, opts,  f) {
    f = "num_mode_min"
    if (!(f in num_)) {
        num_[f] = TODO
    }
    return num_[f]
}

function num_mode_min_init() {
    num_function_init(\
        "num_mode_min mode_min", 0,
        "Get the minimum mode, if any, or UNDEF.",
        "https://en.wikipedia.org/wiki/Mode_(statistics)")
}

##
#
# Mode max: get the maximum mode, if any, or UNDEF.
#
# TODO: IMPLEMENT
#
# Examples:
#
#     num_mode_max(1 2 3) => UNDEF
#     num_mode_max(1 2 2 3) => 2
#     num_mode_max(1 1 2 4 4) => 4
#
##

function num_mode_max(arr) {
    return TODO
}

function num_mode_max_(num, num_, opts,  f) {
    f = "num_mode_max"
    if (!(f in num_)) {
        num_[f] = TODO
    }
    return num_[f]
}

function num_mode_max_init() {
    num_function_init(\
        "num_mode_max mode_max", 0,
        "Get the maximum mode, if any, or UNDEF.",
        "https://en.wikipedia.org/wiki/Mode_(statistics)")
}

############################################################################
#
# num-variance.awk
#
##

# Alias
function num_variance(arr) { num_sample_variance(arr) }
function num_variance_(num, num_, opts) { num_sample_variance_(num, num_, opts) }

# Alias
function num_var(arr) { num_sample_variance(arr) }
function num_var_(num, num_, opts) { num_sample_variance_(num, num_, opts) }

##
#
# Sample Variance.
#
# Example:
#
#     num_population_variance(1 2 4) => 2.33333
#
##

function num_sample_variance(arr) {
    return TODO
}

function num_sample_variance_(num, num_, opts,  f) {
    f = "num_sample_variance"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, _opts, num_mean_(num, num_, opts), 2) / (num_n_(num, num_, opts) - 1)
    return num_[f]
}

function num_sample_variance_init() {
    num_function_init(\
        "num_sample_variance sample_variance s_var variance var sample_second_moment_about_the_mean s_second_moment_about_the_mean s_2_m_a_t_m second_moment_about_the_mean 2_m_a_t_m", 0,
        "Get the sample variance, a.k.a. sample second moment about the mean.",
        "https://wikipedia.org/wiki/Variance")
}

# Alias
function num_svar(arr) { return num_sample_variance(arr) }
function num_svar_(num, num_, opt) { return num_sample_variance_(num, num_, opt) }

##
#
# Population Variance.
#
# Example:
#
#     num_population_variance(1 2 4) => 1.55556
#
##

function num_population_variance(arr) {
    return TODO
}

function num_population_variance_(num, num_, opts) {
    f = "num_population_variance"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, opts, num_mean_(num, num_, opts), 2) / num_n_(num, num_, opts)
    return num_[f]
}

function num_population_variance_init() {
    num_function_init(\
        "num_population_variance population_variance p_var population_second_moment_about_the_mean p_second_moment_about_the_mean p_2_m_a_t_m", 0,
        "Get the population variance, a.k.a. sample second moment about the mean.",
        "https://wikipedia.org/wiki/Variance")
}

# Alias
function num_pvar(arr) { return num_population_variance(arr) }
function num_pvar_(num, num_, opt) { return num_population_variance_(num, num_, opt) }

############################################################################
#
# num-skewness.awk
#
##

# Alias
function num_skewness(arr) { num_sample_skewness(arr) }
function num_skewness_(num, num_, opts) { num_sample_skewness_(num, num_, opts) }

# Alias
function num_skew(arr) { num_sample_skewness(arr) }
function num_skew_(num, num_, opts) { num_sample_skewness_(num, num_, opts) }

##
#
# Sample skewness
#
# Example:
#
#     num_sample_skewness(1 2 4) => 1.11111
#
# A.k.a. population third moment about the mean.
#
# Calculation:
#
#   * Sum each value deviation from the mean cubed.
#   * Divide by the number of items - 1.
#
##

function num_sample_skewness(arr) {
    return num_sum_of_mean_deviation_exp(arr, num_mean(arr), 3) / (num_n(arr) - 1)
}

function num_sample_skewness_(num, num_, opts,  f) {
    f = "num_sample_skewness"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, opts, num_mean_(num, num_, opts), 3) / (num_n_(num, num_, opts) - 1)
    return num_[f]
}

function num_sample_skewness_init() {
    num_function_init(\
        "num_sample_skewness sample_skewness s_skew skewness skew sample_third_moment_about_the_mean s_third_moment_about_the_mean s_3_m_a_t_m third_moment_about_the_mean 3_m_a_t_m", 0,
        "Get the sample skewness, a.k.a. sample third moment about the mean.",
        "https://en.wikipedia.org/wiki/Skewness")
}

# Alias
function num_sskew(arr) { num_sample_skewness(arr) }
function num_sskew_(num, num_, opts) { num_sample_skewness_(num, num_, opts) }

##
#
# Population skewness
#
# Example:
#
#     num_population_skewness(1 2 4) => 0.740741
#
# A.k.a. population third moment about the mean.
#
# If skewness is greater than zero, the distribution is positively skewed.
# If it is less than zero, it is negatively skewed.
# Zero means it is symmetric.
#
# Calculation:
#
#   * Sum each value deviation from the mean cubed.
#   * Divide by the number of items.
#
##

function num_population_skewness(arr) {
    return num_sum_of_mean_deviation_exp(arr, num_mean(arr), 3) / num_n(arr)
}

function num_population_skewness_(num, num_, opts,  f) {
    f = "num_population_skewness"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, opts, num_mean_(num, num_, opt), 3) / num_n_(num, num_, opts)
    return num_[f]
}

function num_population_skewness_init() {
    num_function_init(\
        "num_population_skewness population_skewness p_skew population_third_moment_about_the_mean p_third_moment_about_the_mean p_3_m_a_t_m", 0,
        "Get the population skewness, a.k.a. population third moment about the mean.",
        "https://en.wikipedia.org/wiki/Skewness")
}

# Alias
function num_pskew(arr) { num_population_skewness(arr) }
function num_pskew_(num, num_, opts) { num_population_skewness_(num, num_, opts) }

############################################################################
#
# num-kurtosis.awk
#
# A.k.a. fourth moment about the mean.
#
# Calculation:
#
#   * Sum each value’s deviation from the mean quaded.
#   * Divide by the number of items.
#
# The kurtosis formula measures the degree of peak.
#
# Kurtosis measures the heaviness of tails,
# relative to a normal distribution.
#
#   * Positive kurtosis (peakness) is termed leptokurtic.
#   * Negative kurtosis (flatness) is termed platykurtic.
#   * In-between is termed mesokurtic.
#
# Kurtosis equals 3 for a normal distribution.
#
# Kurtosis is a nondimensional quantity.
#
##

# Alias
function num_kurtosis(arr) { num_sample_kurtosis(arr) }
function num_kurtosis_(num, num_, opts) { num_sample_kurtosis_(num, num_, opts) }

# Alias
function num_kurt(arr) { num_sample_kurtosis(arr) }
function num_kurt_(num, num_, opts) { num_sample_kurtosis_(num, num_, opts) }

##
#
# Sample kurtosis
#
# Example:
#
#     num_sample_kurtosis(1 2 4) => 5.44444
#
##

function num_sample_kurtosis(arr) {
    return num_sum_of_mean_deviation_exp(arr, num_mean(arr), 4) / (num_n(arr) - 1)
}

function num_sample_kurtosis_(num, num_, opts,  f) {
    f = "num_sample_kurtosis"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, opts, num_mean_(num, num_, opts), 4) / (num_n_(num, num_, opts) - 1)
    return num_[f]
}

function num_sample_kurtosis_init() {
    num_function_init(\
        "num_sample_kurtosis sample_kurtosis s_kurt kurtosis kurt sample_fourth_moment_about_the_mean s_fourth_moment_about_the_mean s_4_m_a_t_m fourth_moment_about_the_mean 4_m_a_t_m", 0,
        "Get the kurtosis, a.k.a. sample fourth moment about the mean.",
        "https://en.wikipedia.org/wiki/Kurtosis")
}

# Alias
function num_skurt(arr) { num_sample_kurtosis(arr) }
function num_skurt_(num, num_, opts) { num_sample_kurtosis_(num, num_, opts) }

##
#
# Population kurtosis
#
# Example:
#
#     num_population_kurtosis(1 2 4) => 3.62963
#
##

function num_population_kurtosis(arr) {
    return num_sum_of_mean_deviation_exp(arr, num_mean(arr), 4) / num_n(arr)
}

function num_population_kurtosis_(num, num_, opts,  f) {
    f = "num_population_kurtosis"
    if (!(f in num_)) num_[f] = num_sum_of_mean_deviation_exp_(num, num_, opts, num_mean_(num, num_, opts), 4) / num_n_(num, num_, opts)
    return num_[f]
}

function num_population_kurtosis_init() {
    num_function_init(\
        "num_population_kurtosis population_kurtosis p_kurt population_fourth_moment_about_the_mean p_fourth_moment_about_the_mean p_4_m_a_t_m", 0,
        "Get the kurtosis, a.k.a. population fourth moment about the mean.",
        "https://en.wikipedia.org/wiki/Kurtosis")
}

# Alias
function num_pkurt(arr) { num_population_kurtosis(arr) }
function num_pkurt_(num, num_, opts) { num_population_kurtosis_(num, num_, opts) }

############################################################################
#
# num-standard-deviation.awk
#
##

# Alias
function num_standard_deviation(arr) { num_sample_standard_deviation(arr) }
function num_standard_deviation_(num, num_, opts) { num_sample_standard_deviation_(num, num_, opts) }

# Alias
function num_stddev(arr) { num_sample_standard_deviation(arr) }
function num_stddev_(num, num_, opts) { num_sample_standard_deviation_(num, num_, opts) }

##
#
# Sample Standard Deviation.
#
# Example:
#
#     num_sample_standard_deviation(1 2 4) => 1.52753
#
##

function num_sample_standard_deviation(arr) {
    return sqrt(num_sample_variance(arr))
}

function num_sample_standard_deviation_(num, num_, opts,  f) {
    f = "num_sample_standard_deviation"
    if (!(f in num_)) num_[f] = sqrt(num_sample_variance_(num, num_, opts))
    return num_[f]
}

function num_sample_standard_deviation_init() {
    num_function_init(\
        "num_sample_standard_deviation sample_standard_deviation s_st_dev s_s_d standard_deviation std_dev sd", 0,
        "Get the sample standard deviation",
        "https://wikipedia.org/wiki/Standard_deviation")
}

# Alias
function num_sstddev(arr) { num_sample_standard_deviation(arr) }
function num_sstddev_(num, num_, opts) { num_sample_standard_deviation_(num, num_, opts) }

##
#
# Population Standard Deviation.
#
# Example:
#
#     num_population_standard_deviation(1 2 4) => 1.24722
#
##

function num_population_standard_deviation(arr) {
    return sqrt(num_population_variance(arr))
}

function num_population_standard_deviation_(num, num_, opts,  f) {
    f = "num_population_standard_deviation"
    if (!(f in num_)) num_[f] = sqrt(num_population_variance_(num, num_, opts))
    return num_[f]
}

function num_population_standard_deviation_init() {
    num_function_init(\
        "num_population_standard_deviation population_standard_deviation p_st_dev p_s_d", 0,
        "Get the population standard deviation.",
        "https://wikipedia.org/wiki/Standard_deviation")
}

# Alias
function num_pstddev(arr) { num_population_standard_deviation(arr) }
function num_pstddev_(num, num_, opts) { num_population_standard_deviation_(num, num_, opts) }

############################################################################
#
# num-coefficient-of-variance.awk
#
##

# Alias
function num_coefficient_of_variance(arr) { num_sample_coefficient_of_variance(arr) }
function num_coefficient_of_variance_(num, num_, opts) { num_sample_coefficient_of_variance_(num, num_, opts) }

# Alias
function num_covar(arr) { num_sample_coefficient_of_variance(arr) }
function num_covar_(num, num_, opts) { num_sample_coefficient_of_variance_(num, num_, opts) }

##
#
# Sample Coefficient of Variance.
#
# Example:
#
#     num_sample_coefficient_of_variance(1 2 4) => 0.654654
#
##

function num_sample_coefficient_of_variance(arr) {
    return num_sample_standard_deviation(arr) / num_mean(arr)
}

function num_sample_coefficient_of_variance_(num, num_, opts,  f) {
    f = "num_sample_coefficient_of_variance"
    if (!(f in num_)) num_[f] = num_sample_standard_deviation_(num, num_, opts) / num_mean_(num, num_, opts)
    return num_[f]
}

function num_sample_coefficient_of_variance_init() {
    num_function_init(\
        "num_sample_coefficient_of_variance sample_coefficient_of_variance s_co_var s_c_v coefficient_of_variance co_var c_v  sample_relative_standard_deviation s_r_s_d relative_standard_deviation r_s_d", 0,
        "Get the sample coefficient of variance",
        "https://en.wikipedia.org/wiki/Coefficient_of_variation")
}

# Alias
function num_scovar(arr) { num_sample_coefficient_of_variance(arr) }
function num_scovar_(num, num_, opts) { num_sample_coefficient_of_variance_(num, num_, opts) }

##
#
# Population Coefficient of Variance.
#
# Example:
#
#     1 2 4 => 0.534522
#
##

function num_population_coefficient_of_variance(arr) {
    return num_population_standard_deviation(arr) / num_mean(arr)
}

function num_population_coefficient_of_variance_(num, num_, opts,  f) {
    f = "num_population_coefficient_of_variance"
    if (!(f in num_)) num_[f] = num_population_standard_deviation_(num, num_, opts) / num_mean_(num, num_, opts)
    return num_[f]
}

function num_population_coefficient_of_variance_init() {
    num_function_init(\
        "num_population_coefficient_of_variance population_coefficient_of_variance p_co_var p_c_v population_relative_standard_deviation p_r_s_d", 0,
        "Get the population coefficient of variance.",
        "https://en.wikipedia.org/wiki/Coefficient_of_variation")
}

# Alias
function num_pcovar(arr) { num_population_coefficient_of_variance(arr) }
function num_pcovar_(num, num_, opts) { num_population_coefficient_of_variance_(num, num_, opts) }

############################################################################
#
# num-quartiles.awk
#
# This implemention uses the smoothing method for discrete distrubtions:
#
#   * If there are an even number of data points, then the median is no
#     single datum point. Do not include the median in either half.
#
#   * If there are (4x+1) data points, then the lower quartile is 25% of the
#     xth data value plus 75% of the (x+1)th data value; the upper quartile
#     is 75% of the (3x+1)th data point plus 25% of the (3x+2)th data point.
#
#   * If there are (4x+3) data points, then the lower quartile is 75% of the
#     (x+1)th data value plus 25% of the (x+2)th data value; the upper
#     quartile is 25% of the (3x+2)th data point plus 75% of the (3x+3)th
#     data point.
#
#   * This ensures that the median value is given its correct weight,
#     and thus quartile values change as smoothly as possible as additional
#     data points are added.
#
#   * For more see https://en.wikipedia.org/wiki/Quartile
#
##

##
#
# Interquartile Range, a.k.a. IQR.
#
# Example:
#
#     num_interquartile_range(1 2 3 4 5) => 2.5
#
##

function num_interquartile_range(arr) {
    return num_quartile_3(arr) - num_quartile_1(arr)
}

function num_interquartile_range_(num, num_, opts,  f) {
    f = "num_interquartile_range"
    if (!(f in num_)) num_[f] = num_quartile_3_(num, num_, opts) - num_quartile_1_(num, num_, opts)
    return num_[f]
}

function num_interquartile_range_init() {
    num_function_init(\
        "num_interquartile_range interquartile_range i_q_r mid_spread middle_fifty", 0,
        "Get the interquartile range, a.k.a. IQR.",
        "https://en.wikipedia.org/wiki/Interquartile_range")
}

# Alias
function num_iqr(arr) { return num_interquartile_range(arr) }
function num_iqr_(num, num_, opts) { return num_interquartile_range_(num, num_, opts) }

##
#
# Quartile 0, a.k.a. Q0, 0th percentile, minimum.
#
# Example:
#
#     num_quartile_0(1 2 3 4 5) => 1
#
##

function num_quartile_0(arr) {
    return num_min(arr)
}

function num_quartile_0_(num, num_, opts,  f) {
    f = "num_quartile_0"
    if (!(f in num_)) num_[f] = num_min_(num, num_, opts)
    return num_[f]
}

function num_quartile_0_init() {
    num_function_init(\
        "num_quartile_0 quartile_0 q_0 0_percent", 0,
        "Get the quartile 0, a.k.a. Q0, 0th percentile, minimum.",
        "https://en.wikipedia.org/wiki/Quartile")
}

# Alias
function num_q0(arr) { return num_quartile_0(arr) }
function num_q0_(num, num_, opts) { return num_quartile_0_(num, num_, opts) }

##
#
# Quartile 1, a.k.a. Q1, 25th percentile, lower quartile.
#
# Example:
#
#     num_quartile_1(1 2 3 4 5) => 1.75
#
# Requires sorted array.
#
##

function num_quartile_1(arr,  _n, i, x, q1) {
    _n = num_n(arr)
    if ((_n % 2) == 0) {
        i = (_n / 2) - 1
        q1 = num_median_of_slice(arr, 1, i)
    } else if ((_n % 4) == 1) {
        x = ((_n - 1) / 4)
        q1 = (0.25 * arr[x]) + (0.75 * arr[x+1])
    } else if ((_n % 4) == 3) {
        x = ((_n - 3) / 4)
        q1 = (0.75 * arr[x+1]) + (0.25 * arr[x+2])
    } else {
        q1 = ""
    }
    return q1
}

function num_quartile_1_(num, num_, opts,  f, _n, i, x) {
    f = "num_quartile_1"
    if (!(f in num_)) {
        _n = num_n_(num, num_, opts)
        num_median_(num, num_, opts)
        if ((_n % 2) == 0) {
            i = (_n / 2) - 1
            num_[f] = num_median_of_slice(num, 1, i)
        }
        else if ((_n % 4) == 1) {
            x = ((_n - 1) / 4)
            num_[f] = (0.25 * num[x]) + (0.75 * num[x+1])
        }
        else if ((_n % 4) == 3) {
            x = ((_n - 3) / 4)
            num_[f] = (0.75 * num[x+1]) + (0.25 * num[x+2])
        }
        else {
           num_[f] = ERROR
        }
    }
    return num_[f]
}

function num_quartile_1_init() {
    num_function_init(\
        "num_quartile_1 quartile_1 q_1 25_percent", 0,
        "Get the quartile 1, a.k.a. Q1, 25th percentile, lower quartile.",
        "https://en.wikipedia.org/wiki/Quartile")
}

# Alias
function num_q1(arr) { return num_quartile_1(arr) }
function num_q1_(num, num_, opts) { return num_quartile_1_(num, num_, opts) }

##
#
# Quartile 2, a.k.a. Q2, 50th percentile, median.
#
# Example:
#
#     num_quartile_1(1 2 3 4 5) => 3
#
##

function num_quartile_2(arr,  f) {
    return num_median(arr)
}

function num_quartile_2_(num, num_, opts,  f) {
    f = "num_quartile_2"
    if (!(f in num_)) num_[f] = num_median_(num, num_, opts)
    return num_[f]
}

function num_quartile_2_init() {
    num_function_init(\
        "num_quartile_2 quartile_2 q_2 50_percent", 0,
        "Get the quartile 2, a.k.a. Q2, 50th percentile, median.",
        "https://en.wikipedia.org/wiki/Quartile")
}

# Alias
function num_q2(arr) { return num_quartile_2(arr) }
function num_q2_(num, num_, opts) { return num_quartile_2_(num, num_, opts) }

##
#
# Quartile 3, a.k.a. Q3, 75th percentile, upper quartile.
#
# Example:
#
#     num_quartile_1(1 2 3 4 5) => 4.25
#
# Requires sorted array.
#
##

function num_quartile_3(arr,  _n, i, x, q3) {
    _n = num_n(arr)
    if ((_n % 2) == 0) {
        i = (_n % 2) + 1
        q3 = num_median_of_slice(arr, i, _n)
    }
    else if ((_n % 4) == 1) {
        x = (_n - 1) / 4
        q3 = (0.75 * arr[3 * x + 1]) + (0.25 * arr[3 * x + 2])
    }
    else if ((_n % 4) == 3) {
        x = (_n - 3) / 4
        q3 = (0.25 * arr[3 * x + 2]) + (0.75 * arr[3 * x + 3])
    }
    else {
        q3 = ""
    }
    return q3
}

function num_quartile_3_(num, num_, opts,  f, _n, i, x) {
    f = "num_quartile_3"
    if (!(f in num_)) {
        _n = num_n_(num, num_, opts)
        num_median_(num, num_, opts)
        if ((_n % 2) == 0) {
            i = (_n % 2) + 1
            num_[f] = num_median_of_slice(num, i, _n)
        }
        else if ((_n % 4) == 1) {
            x = (_n - 1) / 4
            num_[f] = (0.75 * num[3 * x + 1]) + (0.25 * num[3 * x + 2])
        }
        else if ((_n % 4) == 3) {
            x = (_n - 3) / 4
            num_[f] = (0.25 * num[3 * x + 2]) + (0.75 * num[3 * x + 3])
        }
        else {
           num_[f] = ERROR
        }
    }
    return num_[f]
}

function num_quartile_3_init() {
    num_function_init(\
        "num_quartile_3 quartile_3 q_3 75_percent", 0,
        "Get the quartile 3, a.k.a. Q3, 75th percentile, upper quartile.",
        "https://en.wikipedia.org/wiki/Quartile")
}

# Alias
function num_q3(arr) { return num_quartile_3(arr) }
function num_q3_(num, num_, opts) { return num_quartile_3_(num, num_, opts) }

##
#
# Quartile 4, a.k.a. Q4, 100th percentile, maximum.
#
# Example:
#
#     num_quartile_1(1 2 3 4 5) => 5
#
##

function num_quartile_4(arr) {
    return num_max(arr)
}

function num_quartile_4_(num, num_, opts,  f) {
    f = "num_quartile_4"
    if (!(f in num_)) num_[f] = num_max_(num, num_, opts)
    return num_[f]
}

function num_quartile_4_init() {
    num_function_init(\
        "num_quartile_4 quartile_4 q_4 100_percent", 0,
        "Get the quartile 4, a.k.a. Q4, 100th percentile, maximum.",
        "https://en.wikipedia.org/wiki/Quartile")
}

# Alias
function num_q4(arr) { return num_quartile_4(arr) }
function num_q4_(num, num_, opts) { return num_quartile_4_(num, num_, opts) }

############################################################################
#
# num-init.awk
#
##

##
#
# Initialize everything.
#
# This calls various `init_*` functions.
#
##

function num_init() {
    #init_lint()
    num_init_constants()
    num_init_conf()
    num_init_word_argv()
    num_init_word_list()
    num_function_manager_init()
}

##
#
# Initialize constants that we use; these are essentially like defines.
#
##

function num_init_constants() {

    # Boolean
    FALSE = 0
    TRUE = 1

    # Math
    PI = 3.141592653589797  # also atan2(0,-1)

    # Content
    TODO = "TODO"
    UNDEF = "UNDEF"
    ERROR = "ERROR"
    NAN = "NAN"

    # Function kinds: a way to track what a function will do. #TODO
    FUN_KIND_CALC = "CALC"
    FUN_KIND_SORT = "SORT"
    FUN_KIND_MAP  = "MAP"
    FUN_KIND_FLAG = "FLAG"
    FUN_KIND_CONF = "CONF"

    # Feature detection. Currently this script requires Gawk.
    AWK_HAS_ASORT = TRUE
    AWK_HAS_LENGTH = TRUE

    # The scope controls whether the user wants input and output
    # to be all items (the default), or per record, or per field.
    # The scope per field is a special case, because it lets the
    # the awk loop read one record, calculate, then flush data.
    NUM_CONF_SCOPE_ALL    = "NUM_CONF_SCOPE_ALL"     # Default
    NUM_CONF_SCOPE_RECORD = "NUM_CONF_SCOPE_RECORD"  # Triggers optimized loop
    NUM_CONF_SCOPE_FIELD  = "NUM_CONF_SCOPE_FIELD"   # TODO implement
}

##
#
# Initialize the configuration dictionary.
#
##

function num_init_conf() {
    split("", global_conf)
    global_conf["scope"] = NUM_CONF_SCOPE_ALL
}

##
#
# Initialize the word argv list.
#
# The word argv list holds the argv items that this script cares about.
# We keep them in order, so we can output results in order.
#
##

function num_init_word_argv() {
    split("", global_word_argv)
}

##
#
# Initialize the global word list lookup array.
#
# This is to recognize words that a user types on the command line.
#
# TODO: research if there is a better way to initialize a dictionary.
#
##

function num_init_word_list() {

    num_synonyms["overall"] = \
    "num_conf_scope_all"

    num_synonyms["records"] = \
    num_synonyms["rows"] = \
    "num_conf_scope_record"

    num_synonyms["fields"] = \
    num_synonyms["colums"] = \
    "num_conf_scope_field"

    # Convention: default is sample, not population.
    num_synonyms["secondmomentaboutthemean"] = \
    num_synonyms["secondmoment"] = \
    "num_sample_variance"

    # Convention: default is sample, not population.
    num_synonyms["thirdmomentaboutthemean"] = \
    num_synonyms["thirdmoment"] = \
    "num_sample_skewness"

    # Convention: default is sample, not population.
    num_synonyms["fourthmomentaboutthemean"] = \
    num_synonyms["fourthmoment"] = \
    "num_sample_kurtosis"

    ## Booleans

    num_synonyms["isunique"] = \
    "num_is_unique"

    num_synonyms["isascending"] = \
    num_synonyms["isasc"] = \
    num_synonyms["isnondescending"] = \
    num_synonyms["isnondesc"] = \
    "num_is_ascending"

    num_synonyms["isstrictlyascending"] = \
    num_synonyms["isstrictasc"] = \
    "num_is_strictly_ascending"

    num_synonyms["isdescending"] = \
    num_synonyms["isdesc"] = \
    num_synonyms["isnonascending"] = \
    num_synonyms["isnonasc"] = \
    "num_is_descending"

    num_synonyms["isstrictlydescending"] = \
    num_synonyms["isstrictdesc"] = \
    "num_is_strictly_descending"

    ### Configurations

        num_synonyms["commaseparatedvalues"] = \
    num_synonyms["csv"] = \
    "num_io_comma_separated_values"

    num_synonyms["inputcommaseparatedvalues"] = \
    num_synonyms["inputcsv"] = \
    num_synonyms["incsv"] = \
    "num_input_comma_separated_values"

    num_synonyms["outputcommaseparatedvalues"] = \
    num_synonyms["outputcsv"] = \
    num_synonyms["outcsv"] = \
    "num_output_comma_separated_values"

    num_synonyms["tabseparatedvalues"] = \
    num_synonyms["tsv"] = \
    "num_io_tab_separated_values"

    num_synonyms["inputtabseparatedvalues"] = \
    num_synonyms["inputtsv"] = \
    num_synonyms["intsv"] = \
    "num_input_tab_separated_values"

    num_synonyms["outputtabseparatedvalues"] = \
    num_synonyms["outputtsv"] = \
    num_synonyms["outtsv"] = \
    "num_output_tab_separated_values"

    num_synonyms["unitseparatedvalues"] = \
    num_synonyms["usv"] = \
    "num_io_unit_separated_values"

    num_synonyms["inputunitseparatedvalues"] = \
    num_synonyms["inputusv"] = \
    num_synonyms["inusv"] = \
    "num_input_unit_separated_values"

    num_synonyms["outputunitseparatedvalues"] = \
    num_synonyms["outputusv"] = \
    num_synonyms["outusv"] = \
    "num_output_unit_separated_values"

}

############################################################################
#
# num-conf.awk
#
##

##
#
# Configure
#
##

function num_conf() {
    num_conf_words(global_word_argv)
    num_conf_ofmt()
    num_conf_scope()
}

##
#
# num_configure the output format string, a.k.a. AWK OFMT.
# This controls number formatting as float, decimal, int, etc.
#
##

function num_conf_ofmt() {
    OFMT = (ENVIRON["OFMT"]) ? ENVIRON["OFMT"] : "%.6g"
}

##
# num_configure scope flags to optimize for tight inner loops.
#
# This enables us to speed up code by doing this:
#
#     - if (global_conf["scope"] == NUM_CONF_SCOPE_ALL)
#     + if (NUM_CONF_SCOPE_ALL_FLAG)
##

function num_conf_scope() {
    NUM_CONF_SCOPE_ALL_FLAG    = (global_conf["scope"] == NUM_CONF_SCOPE_ALL)
    NUM_CONF_SCOPE_RECORD_FLAG = (global_conf["scope"] == NUM_CONF_SCOPE_RECORD)
    NUM_CONF_SCOPE_FIELD_FLAG  = (global_conf["scope"] == NUM_CONF_SCOPE_FIELD)
}

##
#
# Given a word, set a num_configuration setting.
#
# Call this function for each option word a.k.a. flag,
# before any calculation happens and any work happens.
#
# This function must only set `conf` keys and values.
# This function must NOT do any calculations, work, etc.
#
# TODO: consider changing these to functions.
#
##

function num_conf_word(word) {
    if (word == "")
        return
    else if (word == "help")
         num_help()
    else if (word == "num_conf_scope_all")
        global_conf["scope"] = NUM_CONF_SCOPE_ALL
    else if (word == "num_conf_scope_record")
        global_conf["scope"] = NUM_CONF_SCOPE_RECORD
    else if (word == "num_conf_scope_field")
        global_conf["scope"] = NUM_CONF_SCOPE_FIELD
    else if (word == "num_io_comma_separated_values")     { FS = ","; RS = "\n"; OFS = ",";  ORS = "\n" }
    else if (word == "num_input_comma_separated_values")  { FS = ","; RS = "\n" }
    else if (word == "num_output_comma_separated_values") { OFS = ",";  ORS = "\n" }
    else if (word == "num_io_tab_separated_values" )      { FS = "\t"; RS = "\n"; OFS = "\t"; ORS = "\n"}
    else if (word == "num_input_tab_separated_values" )   { FS = "\t"; RS = "\n" }
    else if (word == "num_output_tab_separated_values")   { OFS = "\t"; ORS = "\n" }
    else if (word == "num_io_unit_separated_values")      { FS = "␟"; RS = "␞"; OFS = "␟"; ORS = "␞" }
    else if (word == "num_input_unit_separated_values")   { FS = "␟"; RS = "␞" }
    else if (word == "num_output_unit_separated_values")  { OFS = "␟"; ORS = "␞" }
    else
        return ""
}

##
#
# Given a list of words, set all num_configuration settings.
#
##

function num_conf_words(words,  imax) {
    for (i=1; i <= num_arr_length(words); i++) num_conf_word(words[i])
}

############################################################################
#
# num-scope.awk
#
##

##
#
# Start receiving input.
#
# Ready the global number array for new input and new metadata.
#
##

function scope_start() {
    global_num_scope_n++
    global_num_scope_output_n = 0
    global_num_n = 0
    split("", global_num)
    split("", global_num_)
}

##
#
# Stop receiving input.
#
# Set any work in progress here.
#
##

function scope_stop() {
    global_num_["n"] = global_num_n
    num_print_record(global_num, global_num_, global_opts)
}

############################################################################
#
# num-function-manager.awk
#
##

##
#
# Initialize every file and function.
#
##

function num_function_manager_init() {
    num_n_init()
    num_first_init()
    num_last_init()
    num_min_init()
    num_max_init()
    num_range_init()
    num_frequency_min_init()
    num_frequency_max_init()
    num_select_eq_init()
    num_select_ne_init()
    num_select_lt_init()
    num_select_le_init()
    num_select_gt_init()
    num_select_ge_init()
    num_select_in_init()
    num_select_ex_init()
    num_reject_eq_init()
    num_reject_ne_init()
    num_reject_lt_init()
    num_reject_le_init()
    num_reject_gt_init()
    num_reject_ge_init()
    num_reject_in_init()
    num_reject_ex_init()
    num_sum_init()
    num_product_init()
    num_mean_init()
    num_mean_absolute_deviation_init()
    num_meanest_init()
    num_trimean_init()
    num_trimmed_mean_init()
    num_median_init()
    num_median_low_init()
    num_median_high_init()
    num_modes_init()
    num_mode_min_init()
    num_mode_max_init()
    num_sum_of_squares_init()
    num_sum_of_cubes_init()
    num_sum_of_quads_init()
    num_sample_variance_init()
    num_population_variance_init()
    num_sample_standard_deviation_init()
    num_population_standard_deviation_init()
    num_sample_coefficient_of_variance_init()
    num_population_coefficient_of_variance_init()
    num_sample_skewness_init()
    num_population_skewness_init()
    num_sample_kurtosis_init()
    num_population_kurtosis_init()
    num_interquartile_range_init()
    num_quartile_0_init()
    num_quartile_1_init()
    num_quartile_2_init()
    num_quartile_3_init()
    num_quartile_4_init()
    num_sort_awk_init()
    num_sort_ascending_init()
    num_sort_descending_init()
    num_map_increment_init()
    num_map_absolute_value_init()
    num_map_sign_init()
    num_map_normalize_init()
    num_map_round_awk_init()
    num_map_round_init()
    num_map_round_off_init()
    num_map_round_up_init()
    num_map_round_down_init()
    num_is_unique_init()
    num_is_ascending_init()
    num_is_strictly_ascending_init()
    num_is_descending_init()
    num_is_strictly_descending_init()
    num_help_init()
}

##
#
# Function manager call: given a function name, call its function.
#
# Example:
#
#     num = 1 2 4
#     num_ = []
#     function_name = "num_sum"
#     num_function_manager_call(num, num_, opts, function_name)
#     => 7 (by calling the `sum` function)
#
# Note: this implementation uses if..else instead of
# any switch or case, because we want POSIX usability.
#
# TODO: Research if it is possible to simultaneously support
# gawk indirect functions, to do a function call via `@f()`.
#
##

function num_function_manager_call(num, num_, opts, f) {
    if (f == "") return ("")
    else if (f == "num_n") return num_n_(num, num_, opts)
    else if (f == "num_first") return num_first_(num, num_, opts)
    else if (f == "num_last") return num_last_(num, num_, opts)
    else if (f == "num_min") return num_min_(num, num_, opts)
    else if (f == "num_max") return num_max_(num, num_, opts)
    else if (f == "num_range") return num_range_(num, num_, opts)
    else if (f == "num_frequency_min") return num_frequency_min_(num, num_, opts)
    else if (f == "num_frequency_max") return num_frequency_max_(num, num_, opts)
    else if (f == "num_select_eq") return num_select_eq_(num, num_, opts) # x
    else if (f == "num_select_ne") return num_select_ne_(num, num_, opts) # x
    else if (f == "num_select_lt") return num_select_lt_(num, num_, opts) # x
    else if (f == "num_select_le") return num_select_le_(num, num_, opts) # x
    else if (f == "num_select_gt") return num_select_gt_(num, num_, opts) # x
    else if (f == "num_select_ge") return num_select_ge_(num, num_, opts) # x
    else if (f == "num_select_in") return num_select_in_(num, num_, opts) # min, max
    else if (f == "num_select_ex") return num_select_ex_(num, num_, opts) # min, max
    else if (f == "num_reject_eq") return num_reject_eq_(num, num_, opts) # x
    else if (f == "num_reject_ne") return num_reject_ne_(num, num_, opts) # x
    else if (f == "num_reject_lt") return num_reject_lt_(num, num_, opts) # x
    else if (f == "num_reject_le") return num_reject_le_(num, num_, opts) # x
    else if (f == "num_reject_gt") return num_reject_gt_(num, num_, opts) # x
    else if (f == "num_reject_ge") return num_reject_ge_(num, num_, opts) # x
    else if (f == "num_reject_in") return num_reject_in_(num, num_, opts) # min, max
    else if (f == "num_reject_ex") return num_reject_ex_(num, num_, opts) # min, max
    else if (f == "num_sum") return num_sum_(num, num_, opts)
    else if (f == "num_product") return num_product_(num, num_, opts)
    else if (f == "num_mean") return num_mean_(num, num_, opts)
    else if (f == "num_mean_absolute_deviation") return num_mean_absolute_deviation_(num, num_, opts)
    else if (f == "num_meanest") return num_meanest_(num, num_, opts)
    else if (f == "num_trimean") return num_trimean_(num, num_, opts)
    else if (f == "num_trimmed_mean") return num_trimmed_mean_(num, num_, opts)
    else if (f == "num_median") return num_median_(num, num_, opts)
    else if (f == "num_median_low") return num_median_low_(num, num_, opts)
    else if (f == "num_median_high") return num_median_high_(num, num_, opts)
    else if (f == "num_modes") return num_modes_(num, num_, opts)
    else if (f == "num_mode_min") return num_mode_min_(num, num_, opts)
    else if (f == "num_mode_max") return num_mode_max_(num, num_, opts)
    else if (f == "num_sum_of_squares") return num_sum_of_squares_(num, num_, opts)
    else if (f == "num_sum_of_cubes") return num_sum_of_cubes_(num, num_, opts)
    else if (f == "num_sum_of_quads") return num_sum_of_quads_(num, num_, opts)
    else if (f == "num_sample_variance") return num_sample_variance_(num, num_, opts)
    else if (f == "num_population_variance") return num_population_variance_(num, num_, opts)
    else if (f == "num_sample_standard_deviation") return num_sample_standard_deviation_(num, num_, opts)
    else if (f == "num_population_standard_deviation") return num_population_standard_deviation_(num, num_, opts)
    else if (f == "num_sample_coefficient_of_variance") return num_sample_coefficient_of_variance_(num, num_, opts)
    else if (f == "num_population_coefficient_of_variance") return num_population_coefficient_of_variance_(num, num_, opts)
    else if (f == "num_sample_skewness") return num_sample_skewness_(num, num_, opts)
    else if (f == "num_population_skewness") return num_population_skewness_(num, num_, opts)
    else if (f == "num_sample_kurtosis") return num_sample_kurtosis_(num, num_, opts)
    else if (f == "num_population_kurtosis") return num_population_kurtosis_(num, num_, opts)
    else if (f == "num_interquartile_range") return num_interquartile_range_(num, num_, opts)
    else if (f == "num_quartile_0") return num_quartile_0_(num, num_, opts)
    else if (f == "num_quartile_1") return num_quartile_1_(num, num_, opts)
    else if (f == "num_quartile_2") return num_quartile_2_(num, num_, opts)
    else if (f == "num_quartile_3") return num_quartile_3_(num, num_, opts)
    else if (f == "num_quartile_4") return num_quartile_4_(num, num_, opts)
    else if (f == "num_sort_ascending") return num_sort_ascending_(num, num_, opts)
    else if (f == "num_sort_descending") return num_sort_descending_(num, num_, opts)
    else if (f == "num_map_increment") return num_map_increment_(num, num_, opts)
    else if (f == "num_map_absolute_value") return num_map_absolute_value_(num, num_, opts)
    else if (f == "num_map_sign") return num_map_sign_(num, num_, opts)
    else if (f == "num_map_normalize") return num_map_normalize_(num, num_, opts)
    else if (f == "num_map_round") return num_map_round_(num, num_, opts)
    else if (f == "num_map_round_off") return num_map_round_off_(num, num_, opts)
    else if (f == "num_map_round_up") return num_map_round_up_(num, num_, opts)
    else if (f == "num_map_round_down") return num_map_round_down_(num, num_, opts)
    else if (f == "num_is_unique") return num_is_unique_(num, num_, opts)
    else if (f == "num_is_ascending") return num_is_ascending_(num, num_, opts)
    else if (f == "num_is_strictly_ascending") return num_is_strictly_ascending_(num, num_, opts)
    else if (f == "num_is_descending") return num_is_descending_(num, num_, opts)
    else if (f == "num_is_strictly_descending") return num_is_strictly_descending_(num, num_, opts)
    else return ""
}

##
#
# Function name to s: given a function name, call its function,
# and convert the return value to a string using formatting.
#
# Example:
#
#     num = 1 2 4
#     num_ = []
#     num_function_manager_call_to_s(num, num_, opts, "sum")
#     => 7.00 (by calling the `sum` function and using OFMT)
#
##

function num_function_name_to_s(num, num_, opts, function_name,  s) {
    s = num_function_manager_call(num, num_, opts, function_name)
    if (s != "") {
        num_scope_output_n++
        if (s + 0 == s) s = sprintf(OFMT, s)
    }
    return s
}

##
#
# Function names to s: given a list of function names, call each function,
# and convert all the return values to a string using formatting.
#
# Example:
#
#     num = 1 2 4
#     num_ = []
#     function_names = "sum", "max"
#     function_names_to_s(num, opts, ("sum", "max"))
#     => "7.00,4.00" (by calling the functions and using OFMT and OFS)
#
##

function num_function_names_to_s(num, num_, opts, function_names,  word, i, n, s, build) {
    build = ""
    for (i=1; i <= num_arr_length(function_names); i++) {
        function_name = function_names[i]
        s = num_function_name_to_s(num, num_, opts, function_name)
        if (s != "") {
            n++
            if (n > 1) build = build OFS
            build = build s
        }
    }
    return build
}

############################################################################
#
# num-print.awk
#
##

##
#
# Print a record.
#
# This is the core output function, and the only one that should ever
# print any normal result to the screen, during any normal operation.
#
##

function num_print_record(num, num_, opts,   s, i) {
    s = num_function_names_to_s(global_num, global_num_, global_opts, global_word_argv)
    if (global_num_scope_n > 1) printf ORS
    if (s != "") {
        printf s
    } else {
        num_print_record_fields(global_num, global_num_, global_opts)
    }
}

##
#
# Print all record fields.
#
# This is the fallback if the record has no output so far.
# A typical example would be for a sort, or filter, or map.
#
##

function num_print_record_fields(num, num_, opts) {
    for (i = 1; i <= num_["n"]; i++) {
        if (i > 1) printf OFS
        printf(OFMT, num[i])
    }
}

############################################################################
#
# num-argv.awk
#
##

##
#
# Parse words by iterating: when a word is recognized,
# move it from the inputs array to the outputs array.
#
# Example to parse ARGV:
#
#     parse_words(ARGV, global_word_argv, synonyms)
#
##

function num_argv_parse(inputs, outputs, synonyms,  i, imax, item, outputs_n) {
    split("", outputs)
    outputs_n = 0
    imax = length(inputs)
    for (i=1; i<=imax; i++) {
        item = tolower(inputs[i])
        gsub(/[-_]/, "", item)
        if (item in synonyms) {
            item = synonyms[item]
            delete inputs[i]
            outputs[++outputs_n] = item
        }
    }
}

##
#
# Parse the command line ARGV inputs to recognized word outputs.
#
##

function num_argv() {
    num_argv_parse(ARGV, global_word_argv, num_synonyms)
}

############################################################################
#
# num-main.awk
#
##

BEGIN{
    num_init()
    num_argv()
    num_conf()
    if (NUM_CONF_SCOPE_ALL_FLAG) scope_start()
}

{
    if (NUM_CONF_SCOPE_ALL_FLAG) {
        for(i=1;i<=NF;i++) global_num[++global_num_n] = $i # Inline
    } else if (NUM_CONF_SCOPE_RECORD_FLAG) {
        scope_start()
        for(i=1;i<=NF;i++) global_num[++global_num_n] = $i # Inline
        scope_stop()
    } else {
        num_err("Num configuration scope is not recognized")
    }
}

END{
    if (NUM_CONF_SCOPE_ALL_FLAG) scope_stop()
    printf "\n"  # TODO add conf flag equivalent to echo -n
}

' "$@"
