ArrayExtension
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © RicardoSantos

//@version=5

// @description Functions to extend Arrays.
library(title='ArrayExtension')

// imports:
import RicardoSantos/ArrayGenerate/1 as gen
// gen.sequence_int()
import RicardoSantos/DebugConsole/1 as console
[__T, __C] = console.init(35)


// @function returns the flatened one dimension index of a two dimension array.
// @param dimension_x int, dimension of X.
// @param dimension_y int, dimension of Y.
// @param index_x int, index of X.
// @param index_y int, index of Y.
// @returns int, index in 1 dimension
export index_2d_to_1d (int dimension_x, int dimension_y, int index_x, int index_y) => //{
    int _i = index_x * dimension_y + index_y
    if _i >= 0 and _i < (dimension_x * dimension_y)
        _i
    else
        runtime.error('ArrayExtension -> index_2d_to_1d(): index out of bounds.')
        int(na)
//}

// @function returns the flatened one dimension index of a three dimension array.
// @param dimension_x int, dimension of X.
// @param dimension_y int, dimension of Y.
// @param dimension_z int, dimension of Z.
// @param index_x int, index of X.
// @param index_y int, index of Y.
// @param index_z int, index of Z.
// @returns int, index in 1 dimension
export index_3d_to_1d (int dimension_x, int dimension_y, int dimension_z, int index_x, int index_y, int index_z) => //{
    _i = index_x * dimension_y + index_y * dimension_z + index_z
    if _i >= 0 and _i < (dimension_x * dimension_y * dimension_z)
        _i
    else
        runtime.error('ArrayExtension -> index_3d_to_1d(): index out of bounds.')
        int(na)
//}

// @function Down samples a array to a specified size.
// @param sample float array, array with source data.
// @param new_size new size of down sampled array.
// @returns float array with down sampled data.
export down_sample (float[] sample, int new_size) => //{
    int _size_old = array.size(id=sample)
    if new_size < 2 or new_size >= _size_old
        runtime.error('ArrayExtension -> down_sample(): "new_size" is out of bounds.')
        float[] _R = na
    else if _size_old < 1
        runtime.error('ArrayExtension -> down_sample(): "sample" is empty')
        float[] _R = na
    else
        float[] _R = array.new_float(size=new_size, initial_value=array.get(id=sample, index=0))
        int _step = math.floor(_size_old / new_size)
        for _i = 1 to new_size-2
            _index_from = _i * _step
            _index_to = (_i + 1) * _step
            if _i < new_size-2
                _s = array.slice(id=sample, index_from=_index_from, index_to=_index_to)
                _t = array.sum(id=_s)/array.size(id=_s)
                array.set(id=_R, index=_i, value=_t)
            else
                _s = array.slice(id=sample, index_from=_index_from, index_to=_size_old-2)
                _t = array.sum(id=_s)/array.size(id=_s)
                array.set(id=_R, index=_i, value=_t)
        array.set(id=_R, index=new_size-1, value=array.get(id=sample, index=_size_old-1))
        _R
// usage:
// old_length = input(20)
// new_length = input(10)

// float[] price = array.new_float(0)

// for _i = 0 to old_length-1
//     array.push(id=price, value=_i)

// float[] test = downsampling(price, new_length)

// if barstate.ishistory[1] and (barstate.isrealtime or barstate.islast)
//     label.new(bar_index, 0.0, str.format('{0}: {1}\n{2}: {3}', array.size(price), price, array.size(test), test))
//}


// @function Helper method for sort_indices()
sort_indices_function (sample, string order='forward') => //{
    int _size = array.size(id=sample)
    //
    switch
        (_size < 1) => runtime.error('ArrayExtension -> sort_indices_function(): "sample" is empty')
    //
    _ordered = array.copy(id=sample)
    int[] _indices = gen.sequence_int(start=0, end=_size-1)

    _tempv = array.get(_ordered, 0), int _tempi = na
    for _i = 0 to _size - 2
        _ai = array.get(_ordered, _i), int _iai = array.get(_indices, _i)
        for _j = _i + 1 to _size - 1
            _aj = array.get(_ordered, _j), int _iaj = array.get(_indices, _j)
            if _aj < _ai
                _tempv := _aj, _tempi := _iaj
                array.set(_ordered, _j, _ai), array.set(_indices, _j, _iai)
                array.set(_ordered, _i, _tempv), array.set(_indices, _i, _tempi)
                _ai := _tempv, _iai := _tempi

    if order == 'backward'
        array.reverse(id=_indices), array.reverse(id=_ordered)
    [_indices, _ordered]
//
// @function Sorts array and returns a extra array with sorting indices.
// @param sample (float, int) array with values to be sorted.
// @param order string, default='forward', options='forward', 'backward'.
// @returns
//      _indices int array with indices.
//      _ordered float array with ordered values.
export sort_indices (float[] sample, string order='forward') => sort_indices_function(sample, order)
export sort_indices (int[] sample, string order='forward') => sort_indices_function(sample, order)
// usage:
var float[] values_float = array.from(55.0, 66, 44, 77, 80, 90, 1)
var int[] values_int = array.from(55, 66, 44, 77, 80, 90, 1)
if barstate.islast
    [indices_float, sorted_float] = sort_indices(values_float, 'backward')
    [indices_int, sorted_int] = sort_indices(values_int, 'backward')
    console.queue_one(__C, str.format('sort_indices():\n    float:  {0} -> {1} : {2}', values_float, sorted_float, indices_float))
    console.queue_one(__C, str.format('    int:   {0} -> {1} : {2}\n', values_int, sorted_int, indices_int))
//}

// @function Helper method for sort_from_indices()
sort_from_indices_function (int[] indices, sample) => //{
    int _size_i = array.size(id=indices)
    int _size_s = array.size(id=sample)
    //
    switch
        (_size_i < 1  or _size_s < 1) => runtime.error('ArrayExtension -> sort_bool_from_indices(): "indices" or "sample" is empty.')
        (_size_i != _size_s ) => runtime.error('ArrayExtension -> sort_bool_from_indices(): "indices" and "sample" size not match.')
    //
    _ordered = array.copy(sample)
    for _i = 0 to _size_i - 1
        array.set(id=_ordered, index=_i, value=array.get(id=sample, index=array.get(id=indices, index=_i)))
    _ordered
//
// @function Sorts sample array using a array with indices.
// @param indices int array with positional indices.
// @param sample (bool, box, color, float, int, label, line, string, table) array with data sample to be sorted.
// @returns table array
export sort_from_indices (int[] indices, bool[] sample)     => sort_from_indices_function (indices, sample)
export sort_from_indices (int[] indices, box[] sample)      => sort_from_indices_function (indices, sample)
export sort_from_indices (int[] indices, color[] sample)    => sort_from_indices_function (indices, sample)
export sort_from_indices (int[] indices, float[] sample)    => sort_from_indices_function (indices, sample)
export sort_from_indices (int[] indices, int[] sample)      => sort_from_indices_function (indices, sample)
export sort_from_indices (int[] indices, label[] sample)    => sort_from_indices_function (indices, sample)
export sort_from_indices (int[] indices, line[] sample)     => sort_from_indices_function (indices, sample)
export sort_from_indices (int[] indices, string[] sample)   => sort_from_indices_function (indices, sample)
export sort_from_indices (int[] indices, table[] sample)    => sort_from_indices_function (indices, sample)
// usage:
if barstate.islast
    [indices_float, sorted_float] = sort_indices(values_float, 'backward')
    console.queue_one(__C, str.format('sort_from_indices(): {0}', indices_float))
    console.queue_one(__C, str.format('    bool:   {0}', sort_from_indices(indices_float, array.from(false, false, false, true, true, true, false))))
    console.queue_one(__C, str.format('    box:    {0}', 'n/a'))
    console.queue_one(__C, str.format('    color:   {0}', 'n/a'))
    console.queue_one(__C, str.format('    float:   {0}', sort_from_indices(indices_float, array.from(5, 4, 6, 3, 2, 1, 7.0))))
    console.queue_one(__C, str.format('    int:     {0}', sort_from_indices(indices_float, array.from(5, 4, 6, 3, 2, 1, 7))))
    console.queue_one(__C, str.format('    label:   {0}', 'n/a'))
    console.queue_one(__C, str.format('    line:    {0}', 'n/a'))
    console.queue_one(__C, str.format('    string:  {0}', sort_from_indices(indices_float, array.from('5', '4', '6', '3', '2', '1', '7'))))
    console.queue_one(__C, str.format('    table:   {0}', 'n/a\n'))
//}

// @function Helper method for sort_inplace_from_indices().
sort_inplace_from_indices_function (int[] indices, sample) => //{
    int _size_i = array.size(id=indices)
    int _size_s = array.size(id=sample)
    //
    switch
        (_size_i < 1  or _size_s < 1) => runtime.error('ArrayExtension -> sort_bool_inplace_from_indices(): "indices" or "sample" is empty.')
        (_size_i != _size_s) => runtime.error('ArrayExtension -> sort_bool_inplace_from_indices(): "indices" and "sample" size not match.')
    //
    _ordered = array.copy(id=sample)
    for _i = 0 to _size_i - 1
        array.set(id=sample, index=_i, value=array.get(id=_ordered, index=array.get(id=indices, index=_i)))
// @function Sorts sample array inplace using a array with indices.
// @param indices int array with positional indices.
// @param sample (bool, box, color, float, int, label, line, string, table) array with data sample to be sorted.
// @returns void updates sample
export sort_inplace_from_indices (int[] indices, bool[] sample)     => sort_inplace_from_indices_function (indices, sample)
export sort_inplace_from_indices (int[] indices, box[] sample)      => sort_inplace_from_indices_function (indices, sample)
export sort_inplace_from_indices (int[] indices, color[] sample)    => sort_inplace_from_indices_function (indices, sample)
export sort_inplace_from_indices (int[] indices, float[] sample)    => sort_inplace_from_indices_function (indices, sample)
export sort_inplace_from_indices (int[] indices, int[] sample)      => sort_inplace_from_indices_function (indices, sample)
export sort_inplace_from_indices (int[] indices, label[] sample)    => sort_inplace_from_indices_function (indices, sample)
export sort_inplace_from_indices (int[] indices, line[] sample)     => sort_inplace_from_indices_function (indices, sample)
export sort_inplace_from_indices (int[] indices, string[] sample)   => sort_inplace_from_indices_function (indices, sample)
export sort_inplace_from_indices (int[] indices, table[] sample)    => sort_inplace_from_indices_function (indices, sample)
// usage:
if barstate.islast
    [_indices, _] = sort_indices(values_float, 'backward')
    bool[] _bool_sample = array.from(false, false, false, true, true, true, false), sort_inplace_from_indices(_indices, _bool_sample)
    float[] _float_sample = array.from(5, 4, 6, 3, 2, 1, 7.0), sort_inplace_from_indices(_indices, _float_sample)
    int[] _int_sample = array.from(5, 4, 6, 3, 2, 1, 7), sort_inplace_from_indices(_indices, _int_sample)
    string[] _string_sample = array.from('5', '4', '6', '3', '2', '1', '7'), sort_inplace_from_indices(_indices, _string_sample)
    console.queue_one(__C, str.format('sort_inplace_from_indices(): {0}', _indices))
    console.queue_one(__C, str.format('    bool:   {0}', _bool_sample))
    console.queue_one(__C, str.format('    box:    {0}', 'n/a'))
    console.queue_one(__C, str.format('    color:   {0}', 'n/a'))
    console.queue_one(__C, str.format('    float:   {0}', _float_sample))
    console.queue_one(__C, str.format('    int:     {0}', _int_sample))
    console.queue_one(__C, str.format('    label:   {0}', 'n/a'))
    console.queue_one(__C, str.format('    line:    {0}', 'n/a'))
    console.queue_one(__C, str.format('    string:  {0}', _string_sample))
    console.queue_one(__C, str.format('    table:   {0}', 'n/a\n'))
//}

// @function to_float_method(). {
to_float_method_numeric(sample) => //{
    int _size = array.size(id=sample)
    switch
        (_size < 1) => runtime.error('ArrayExtension -> to_float(): "sample" is empty.')
    //
    float[] _new = array.new_float(size=_size)
    for _i=0 to _size-1
        array.set(id=_new, index=_i, value=float(array.get(id=sample, index=_i)))
    _new //}
to_float_method_string(sample) => //{
    int _size = array.size(id=sample)
    switch
        (_size < 1) => runtime.error('ArrayExtension -> to_float(): "sample" is empty.')
    //
    float[] _new = array.new_float(size=_size)
    for _i=0 to _size-1
        array.set(id=_new, index=_i, value=str.tonumber(array.get(id=sample, index=_i)))
    _new //}
to_float_method_bool(sample) => //{
    int _size = array.size(id=sample)
    switch
        (_size < 1) => runtime.error('ArrayExtension -> to_float(): "sample" is empty.')
    //
    float[] _new = array.new_float(size=_size)
    for _i=0 to _size-1
        array.set(id=_new, index=_i, value=array.get(id=sample, index=_i) ? 1.0 : 0.0)
    _new //}
//
// @function Transform a array into a float array
// @param sample array, sample data to transform.
// @returns float array
export to_float (int[] sample) => to_float_method_numeric(sample)
export to_float (float[] sample) => to_float_method_numeric(sample)
export to_float (string[] sample) => to_float_method_string(sample)
export to_float (bool[] sample) => to_float_method_bool(sample)
// { usage
if barstate.islast
    console.queue_one(__C, str.format('to_float:   {0}', to_float(array.from('0', '1', '0.5'))))
//}}

// @function to_int_method(). {
to_int_method_numeric(sample) => //{
    int _size = array.size(id=sample)
    switch
        (_size < 1) => runtime.error('ArrayExtension -> to_int(): "sample" is empty.')
    //
    int[] _new = array.new_int(size=_size)
    for _i=0 to _size-1
        array.set(id=_new, index=_i, value=int(array.get(id=sample, index=_i)))
    _new //}
to_int_method_string(sample) => //{
    int _size = array.size(id=sample)
    switch
        (_size < 1) => runtime.error('ArrayExtension -> to_int(): "sample" is empty.')
    //
    int[] _new = array.new_int(size=_size)
    for _i=0 to _size-1
        array.set(id=_new, index=_i, value=int(str.tonumber(array.get(id=sample, index=_i))))
    _new //}
to_int_method_bool(sample) => //{
    int _size = array.size(id=sample)
    switch
        (_size < 1) => runtime.error('ArrayExtension -> to_int(): "sample" is empty.')
    //
    int[] _new = array.new_int(size=_size)
    for _i=0 to _size-1
        array.set(id=_new, index=_i, value=array.get(id=sample, index=_i) ? 1 : 0)
    _new //}
//
// @function Transform a array into a int array
// @param sample array, sample data to transform.
// @returns int array
export to_int (int[] sample) => to_int_method_numeric(sample)
export to_int (float[] sample) => to_int_method_numeric(sample)
export to_int (string[] sample) => to_int_method_string(sample)
export to_int (bool[] sample) => to_int_method_bool(sample)
// { usage
if barstate.islast
    console.queue_one(__C, str.format('to_int:   {0}', to_int(array.from('0', '1', '0.5'))))
//}}

console.update(__T, __C)