text: /* ********************************************************************************************
   *                                                                                            *
   * Plese read the following tutorial before implementing tasks:                               *
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array     *
   *                                                                                            *
   * NOTE : Please do not use loops! All tasks can be implmeneted using standard Array methods  *
   *                                                                                            *
   ******************************************************************************************** */
  
  
  /**
   * Returns an index of the specified element in array or -1 if element is not found
   *
   * @param {array} arr
   * @param {any} value
   * @return {number}
   *
   * @example
   *    ['Ace', 10, true], 10    => 1
   *    ['Array', 'Number', 'string'], 'Date'    => -1
   *    [0, 1, 2, 3, 4, 5], 5    => 5
   */
  function findElement(arr, value) {
    return arr.indexOf(value);
  }
  
  /**
   * Generates an array of odd numbers of the specified length
   *
   * @param {number} len
   * @return {array}
   *
   * @example
   *    1 => [ 1 ]
   *    2 => [ 1, 3 ]
   *    5 => [ 1, 3, 5, 7, 9 ]
   */
  function generateOdds(len) {
    return (new Array(len * 2)).fill(0).map((vl, idx) => (idx + 1)).filter((vl) => vl % 2 !== 0);
  }
  
  
  /**
   * Returns the doubled array - elements of the specified array
   * are repeated twice using original order
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *    ['Ace', 10, true]  => ['Ace', 10, true,   'Ace', 10, true]
   *    [0, 1, 2, 3, 4, 5] => [0, 1, 2, 3, 4, 5,   0, 1, 2, 3, 4, 5]
   *    [] => []
   */
  function doubleArray(arr) {
    arr.splice(0, 0, ...arr);
    return arr;
  }
  
  
  /**
   * Returns an array of positive numbers from the specified array in original order
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *    [ 0, 1, 2, 3, 4, 5 ] => [ 1, 2, 3, 4, 5 ]
   *    [-1, 2, -5, -4, 0] => [ 2 ]
   *    [] => []
   */
  function getArrayOfPositives(arr) {
    return arr.filter((e) => e > 0);
  }
  
  /**
   * Returns the array with strings only in the specified array (in original order)
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *    [ 0, 1, 'cat', 3, true, 'dog' ] => [ 'cat', 'dog' ]
   *    [ 1, 2, 3, 4, 5 ] => []
   *    [ 'cat, 'dog', 'raccoon' ] => [ 'cat', 'dog', 'raccoon' ]
   */
  function getArrayOfStrings(arr) {
    return arr.filter((e) => typeof e === 'string');
  }
  
  /**
   * Removes falsy values from the specified array
   * Falsy values: false, null, 0, "", undefined, and NaN.
   * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#Description)
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *    [ 0, false, 'cat', NaN, true, '' ] => [ 'cat', true ]
   *    [ 1, 2, 3, 4, 5, 'false' ]         => [ 1, 2, 3, 4, 5, 'false' ]
   *    [ false, 0, NaN, '', undefined ]   => [ ]
   */
  function removeFalsyValues(arr) {
    return arr.filter((e) => e);
  }
  
  /**
   * Returns the array of uppercase strings from the specified array
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *    [ 'permanent-internship', 'glutinous-shriek', 'multiplicative-elevation' ]
   *    => [ 'PERMANENT-INTERNSHIP', 'GLUTINOUS-SHRIEK', 'MULTIPLICATIVE-ELEVATION' ],
   *    [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ]  => [ 'A', 'B', 'C', 'D', 'E', 'F', 'G' ]
   */
  function getUpperCaseStrings(arr) {
    return arr.map((e) => e.toUpperCase());
  }
  
  
  /**
   * Returns the array of string lengths from the specified string array.
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *    [ '', 'a', 'bc', 'def', 'ghij' ]  => [ 0, 1, 2, 3, 4 ]
   *    [ 'angular', 'react', 'ember' ] => [ 7, 5, 5 ]
   */
  function getStringsLength(arr) {
    return arr.map((e) => e.length);
  }
  
  /**
   * Inserts the item into specified array at specified index
   *
   * @param {array} arr
   * @param {any} item
   * @param {number} index
   *
   * @example
   *    [ 1, 3, 4, 5 ], 2, 1  => [ 1, 2, 3, 4, 5 ]
   *    [ 1, 'b', 'c'], 0, 'x'  => [ 'x', 1, 'b', 'c' ]
   */
  function insertItem(arr, item, index) {
    arr.splice(index, 0, item);
    return arr;
  }
  
  /**
   * Returns the n first items of the specified array
   *
   * @param {array} arr
   * @param {number} n
   *
   * @example
   *    [ 1, 3, 4, 5 ], 2 => [ 1, 3 ]
   *    [ 'a', 'b', 'c', 'd'], 3  => [ 'a', 'b', 'c' ]
   */
  function getHead(arr, n) {
    return arr.slice(0, n);
  }
  
  
  /**
   * Returns the n last items of the specified array
   *
   * @param {array} arr
   * @param {number} n
   *
   * @example
   *    [ 1, 3, 4, 5 ], 2  => [ 4, 5 ]
   *    [ 'a', 'b', 'c', 'd'], 3  => [ 'b', 'c', 'd' ]
   */
  function getTail(arr, n) {
    return arr.slice(-n);
  }
  
  
  /**
   * Returns CSV represebtation of two-dimentional numeric array.
   * https://en.wikipedia.org/wiki/Comma-separated_values
   *
   * @param {array} arr
   * @return {string}
   *
   * @example
   *    [
   *       [  0, 1, 2, 3, 4 ],
   *       [ 10,11,12,13,14 ],
   *       [ 20,21,22,23,24 ],
   *       [ 30,31,32,33,34 ]
   *    ]
   *           =>
   *     '0,1,2,3,4\n'
   *    +'10,11,12,13,14\n'
   *    +'20,21,22,23,24\n'
   *    +'30,31,32,33,34'
   */
  function toCsvText(arr) {
    return arr.reduce((acc, cur, idx) => (acc + cur.join(',') + (idx === (arr.length - 1) ? '' : '\n')), '');
  }
  
  /**
   * Transforms the numeric array into the according array of squares:
   *   f(x) = x * x
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *   [ 0, 1, 2, 3, 4, 5 ] => [ 0, 1, 4, 9, 16, 25 ]
   *   [ 10, 100, -1 ]      => [ 100, 10000, 1 ]
   */
  function toArrayOfSquares(arr) {
    return arr.map((e) => e ** 2);
  }
  
  
  /**
   * Transforms the numeric array to the according moving sum array:
   *     f[n] = x[0] + x[1] + x[2] +...+ x[n]
   *  or f[n] = f[n-1] + x[n]
   *
   * @param {array} arr
   * @return {array}
   *
   * Example :
   *   [ 1, 1, 1, 1, 1 ]        => [ 1, 2, 3, 4, 5 ]
   *   [ 10, -10, 10, -10, 10 ] => [ 10, 0, 10, 0, 10 ]
   *   [ 0, 0, 0, 0, 0]         => [ 0, 0, 0, 0, 0]
   *   [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 ]
   */
  function getMovingSum(arr) {
    return arr.map((item, idx) => arr.slice(0, idx + 1).reduce((p, c) => p + c));
  }
  
  
  /**
   * Returns every second item from the specified array:
   *
   * @param {array} arr
   * @return {array}
   *
   * Example :
   * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] => [ 2, 4, 6, 8, 10 ]
   * [ 'a', 'b', 'c' , null ]  => [ "b", null ]
   * [ "a" ] => []
   */
  function getSecondItems(arr) {
    return arr.filter((item, idx) => (idx % 2));
  }
  
  
  /**
   * Propagates every item in sequence its position times
   * Returns an array that consists of: one first item, two second items, tree third items etc.
   *
   * @param {array} arr
   * @return {array}
   *
   * @example :
   *  [] => []
   *  [ 1 ] => [ 1 ]
   *  [ 'a', 'b' ] => [ 'a', 'b','b' ]
   *  [ 'a', 'b', 'c', null ] => [ 'a', 'b','b', 'c','c','c',  null,null,null,null ]
   *  [ 1,2,3,4,5 ] => [ 1, 2,2, 3,3,3, 4,4,4,4, 5,5,5,5,5 ]
   */
  function propagateItemsByPositionIndex(arr) {
    return arr.reduce((acc, cur, idx) => [...acc, ...(new Array(idx + 1)).fill(cur)], []);
  }
  
  
  /**
   * Returns the 3 largest numbers from the specified array
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *   [] => []
   *   [ 1, 2 ] => [ 2, 1 ]
   *   [ 1, 2, 3 ] => [ 3, 2, 1 ]
   *   [ 1,2,3,4,5,6,7,8,9,10 ] => [ 10, 9, 8 ]
   *   [ 10, 10, 10, 10 ] => [ 10, 10, 10 ]
   */
  function get3TopItems(arr) {
    return arr.sort((a, b) => a - b).slice(-3).reverse();
  }
  
  
  /**
   * Returns the number of positive numbers from specified array
   *
   * @param {array} arr
   * @return {number}
   *
   * @example
   *   [ ]          => 0
   *   [ -1, 0, 1 ] => 1
   *   [ 1, 2, 3]   => 3
   *   [ null, 1, 'elephant' ] => 1
   *   [ 1, '2' ] => 1
   */
  function getPositivesCount(arr) {
    return arr.filter((e) => typeof e === 'number' && parseFloat(e) > 0).length;
  }
  
  /**
   * Sorts digit names
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *   [] => []
   *   [ 'nine','one' ]                 => [ 'one', 'nine' ]
   *   [ 'one','two','three' ]          => [ 'one','two', 'three' ]
   *   [ 'nine','eight','nine','eight'] => [ 'eight','eight','nine','nine']
   *   [ 'one','one','one','zero' ]     => [ 'zero','one','one','one' ]
   */
  function sortDigitNamesByNumericOrder(arr) {
    const ordered = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'];
  
    return arr.sort((a, b) => ordered.indexOf(a) - ordered.indexOf(b));
  }
  
  /**
   * Returns the sum of all items in the specified array of numbers
   *
   * @param {array} arr
   * @return {number}
   *
   * @example
   *   [] => 0
   *   [ 1, 2, 3 ]           => 6
   *   [ -1, 1, -1, 1 ]      => 0
   *   [ 1, 10, 100, 1000 ]  => 1111
   */
  function getItemsSum(arr) {
    return arr.reduce((acc, cur) => acc + cur, 0);
  }
  
  /**
   * Returns the number of all falsy value in the specified array
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *  [] => 0
   *  [ 1, '', 3 ] => 1
   *  [ -1, 'false', null, 0 ] => 2
   *  [ null, undefined, NaN, false, 0, '' ]  => 6
   */
  function getFalsyValuesCount(arr) {
    return arr.filter((e) => !e).length;
  }
  
  /**
   * Returns a number of all occurences of the specified item in an array
   *
   * @param {array} arr
   * @param {any} item
   * @return {number}
   *
   * @example
   *    [ 0, 0, 1, 1, 1, 2 ], 1 => 3
   *    [ 1, 2, 3, 4, 5 ], 0 => 0
   *    [ 'a','b','c','c' ], 'c'=> 2
   *    [ null, undefined, null ], null => 2
   *    [ true, 0, 1, 'true' ], true => 1
   */
  function findAllOccurences(arr, item) {
    return arr.reduce((acc, cur) => acc + (cur === item ? 1 : 0), 0);
  }
  
  /**
   * Concatenates all elements from specified array into single string with ',' delimeter
   *
   * @param {array} arr
   * @return {string}
   *
   * @example
   *    [0, false, 'cat', NaN, true, '']  => '0,false,cat,NaN,true,'
   *    [1, 2, 3, 4, 5]                   => '1,2,3,4,5'
   *    ['rock', 'paper', 'scissors']     => 'rock,paper,scissors'
   */
  function toStringList(arr) {
    return arr.join(',');
  }
  
  
  /**
   * Sorts the specified array by country name first and city name
   * (if countries are equal) in ascending order.
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *    [
   *      { country: 'Russia',  city: 'Moscow' },
   *      { country: 'Belarus', city: 'Minsk' },
   *      { country: 'Poland',  city: 'Warsaw' },
   *      { country: 'Russia',  city: 'Saint Petersburg' },
   *      { country: 'Poland',  city: 'Krakow' },
   *      { country: 'Belarus', city: 'Brest' }
   *    ]
   *                      =>
   *    [
   *      { country: 'Belarus', city: 'Brest' },
   *      { country: 'Belarus', city: 'Minsk' },
   *      { country: 'Poland',  city: 'Krakow' },
   *      { country: 'Poland',  city: 'Warsaw' },
   *      { country: 'Russia',  city: 'Moscow' },
   *      { country: 'Russia',  city: 'Saint Petersburg' }
   *    ]
   */
  function sortCitiesArray(arr) {
    return arr.sort((a, b) => {
      if (a.country > b.country) return 1;
      if (a.country < b.country) return -1;
      if (a.country === b.country) {
        if (a.city > b.city) return 1;
        if (a.city < b.city) return -1;
      }
      return 0;
    });
  }
  
  /**
   * Creates an indentity matrix of the specified size
   *
   * @param {number} n
   * @return {array}
   *
   * @example
   *     1  => [[1]]
   *
   *     2 => [[1,0],
   *           [0,1]]
   *
   *          [[1,0,0,0,0],
   *           [0,1,0,0,0],
   *     5 =>  [0,0,1,0,0],
   *           [0,0,0,1,0],
   *           [0,0,0,0,1]]
   */
  function getIdentityMatrix(n) {
    return (new Array(n * n))
      .fill(0)
      .map((e, idx) => (idx % n === Math.floor(idx / n) ? 1 : 0))
      .reduce((prev, cur, idx, arr) => (
        (idx % n) ? prev : prev.concat([arr.slice(idx, idx + n)])
      ), []);
  }
  
  
  /**
   * Creates an array of integers from the specified start to end (inclusive)
   *
   * @param {number} start
   * @param {number} end
   * @return {array}
   *
   * @example
   *     1, 5  => [ 1, 2, 3, 4, 5 ]
   *    -2, 2  => [ -2, -1, 0, 1, 2 ]
   *     0, 100 => [ 0, 1, 2, ..., 100 ]
   *     3, 3   => [ 3 ]
   */
  function getIntervalArray(start, end) {
    return (new Array(end - start + 1)).fill(0).reduce((acc, vl, idx) => acc.concat(start + idx), []);
  }
  
  
  /**
   * Returns array containing only unique values from the specified array.
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *   [ 1, 2, 3, 3, 2, 1 ] => [ 1, 2, 3 ]
   *   [ 'a', 'a', 'a', 'a' ]  => [ 'a' ]
   *   [ 1, 1, 2, 2, 3, 3, 4, 4] => [ 1, 2, 3, 4]
   */
  function distinct(arr) {
    return Array.from(new Set(arr));
  }
  
  
  /**
   * Groups elements of the specified array by key.
   * Returns multimap of keys extracted from array elements via keySelector callback
   * and values extracted via valueSelector callback.
   * See: https://en.wikipedia.org/wiki/Multimap
   *
   * @param {array} array
   * @param {Function} keySelector
   * @param {Function} valueSelector
   * @return {Map}
   *
   * @example
   *   group([
   *      { country: 'Belarus', city: 'Brest' },
   *      { country: 'Russia', city: 'Omsk' },
   *      { country: 'Russia', city: 'Samara' },
   *      { country: 'Belarus', city: 'Grodno' },
   *      { country: 'Belarus', city: 'Minsk' },
   *      { country: 'Poland', city: 'Lodz' }
   *     ],
   *     item => item.country,
   *     item => item.city
   *   )
   *            =>
   *   Map {
   *    "Belarus" => ["Brest", "Grodno", "Minsk"],
   *    "Russia" => ["Omsk", "Samara"],
   *    "Poland" => ["Lodz"]
   *   }
   */
  function group(array, k, v) {
    return array.reduce((prev, cur) => (
      prev.has(k(cur)) ? prev.set(
        k(cur), prev.get(k(cur)).concat(v(cur)),
      ) : prev.set(k(cur), [v(cur)])
    ),
    new Map());
  }
  
  /**
   * Projects each element of the specified array to a sequence
   * and flattens the resulting sequences into one array.
   *
   * @param {array} arr
   * @param {Function} childrenSelector, a transform function to apply to each element
   *                                     that returns an array of children
   * @return {array}
   *
   * @example
   *   [[1, 2], [3, 4], [5, 6]], (x) => x     =>   [ 1, 2, 3, 4, 5, 6 ]
   *   ['one','two','three'], x=>x.split('')  =>   ['o','n','e','t','w','o','t','h','r','e','e']
   */
  function selectMany(arr, childrenSelector) {
    return arr.map(childrenSelector).flat(1);
  }
  
  
  /**
   * Returns an element from the multidimentional array by the specified indexes.
   *
   * @param {array} arr
   * @param {array} indexes
   * @return {any} element from array
   *
   * @example
   *   [[1, 2], [3, 4], [5, 6]], [0,0]  => 1        (arr[0][0])
   *   ['one','two','three'], [2]       => 'three'  (arr[2])
   *   [[[ 1, 2, 3]]], [ 0, 0, 1 ]      => 2        (arr[0][0][1])
   */
  function getElementByIndexes(arr, indexes) {
    return indexes.reduce((prev, cur) => prev[cur], arr);
  }
  
  
  /**
   * Swaps the head and tail of the specified array:
   * the head (first half) of array move to the end, the tail (last half) move to the start.
   * The middle element (if exists) leave on the same position.
   *
   *
   * @param {array} arr
   * @return {array}
   *
   * @example
   *   [ 1, 2, 3, 4, 5 ]   =>  [ 4, 5, 3, 1, 2 ]
   *    \----/   \----/
   *     head     tail
   *
   *   [ 1, 2 ]  => [ 2, 1 ]
   *   [ 1, 2, 3, 4, 5, 6, 7, 8 ]   =>  [ 5, 6, 7, 8, 1, 2, 3, 4 ]
   *
   */
  function swapHeadAndTail(arr) {
    return arr.map((cur, idx) => {
      const a = arr;
      if (Math.floor(a.length / 2) > idx) {
        const tmp = a[Math.floor(a.length / 2) + idx + (a.length % 2 ? 1 : 0)];
        a[Math.floor(arr.length / 2) + idx + (a.length % 2 ? 1 : 0)] = cur;
        return tmp;
      }
  
      return cur;
    });
  }
  
  
  module.exports = {
    findElement,
    generateOdds,
    doubleArray,
    getArrayOfPositives,
    getArrayOfStrings,
    removeFalsyValues,
    getUpperCaseStrings,
    getStringsLength,
    insertItem,
    getHead,
    getTail,
    toCsvText,
    toStringList,
    toArrayOfSquares,
    getMovingSum,
    getSecondItems,
    propagateItemsByPositionIndex,
    get3TopItems,
    getPositivesCount,
    sortDigitNamesByNumericOrder,
    getItemsSum,
    getFalsyValuesCount,
    findAllOccurences,
    sortCitiesArray,
    getIdentityMatrix,
    getIntervalArray,
    distinct,
    group,
    selectMany,
    getElementByIndexes,
    swapHeadAndTail,
  };
  