tools/utils/src/modules/array-array.js

/**
* @module @svizzle/utils/array-array
*/

import areEquals from 'just-compare';
import * as _ from 'lamb';

import {getKey, getValue} from './object-any.js';

/**
 * Return an array containing the first and the last element of the provided array.
 *
 * @function
 * @arg {array} array
 * @return {array}
 *
 * @example getFirstAndLast([0, 1, 2, 3, 4]) // [0, 4]
 *
 * @since 0.3.0
 */
export const getFirstAndLast = _.collect([_.head, _.last]);

/**
 * Return the range within the provided limits, both limits being included.
 *
 * @function
 * @arg {array} args
 * @arg {number} args.0 - start
 * @arg {number} args.1 - end
 * @arg {number} [args.2 = 1] - step
 * @return {array}
 *
 * @example
> inclusiveRange([2, 5])
[2, 3, 4, 5]
> inclusiveRange([2, 12])
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
> inclusiveRange([2, 12, 2])
[2, 4, 6, 8, 10, 12]
> inclusiveRange([2, 11, 2])
[2, 4, 6, 8, 10];
 *
 * @since 0.7.0
 * @see https://ascartabelli.github.io/lamb/module-lamb.html#range
 */
export const inclusiveRange = ([start, end, step = 1]) => {
	if (start === end) {
		return _.isUndefined(start) ? [] : [start];
	}

	const range = _.range(start, end, step);

	return range.length === 0 || _.last(range) + step > end
		? range
		: _.appendTo(range, end);
}

/**
 * Return the permutations of pairs of the provided items.
 *
 * @function
 * @arg {array} items
 * @return {array} permutations - permutations of pairs of items
 *
 * @example
> array = [
	{foo: 'a'},
	{foo: 'b'},
	{bar: 'c'},
	{bar: 'd'}
]
> makeBiPermutations(array)
[
	[{foo: 'a'}, {foo: 'b'}],
	[{foo: 'a'}, {bar: 'c'}],
	[{foo: 'a'}, {bar: 'd'}],
	[{foo: 'b'}, {bar: 'c'}],
	[{foo: 'b'}, {bar: 'd'}],
	[{bar: 'c'}, {bar: 'd'}]
]
 *
 * @since 0.5.0
 */
export const makeBiPermutations = items =>
	_.reduce(items, (acc, item, index, array) => {
		for (let cursor = index + 1; cursor < array.length; cursor++) {
			acc.push([item, array[cursor]]);
		}
		return acc;
	}, []);

/**
 * Pluck the `key` property value from an array of objects.
 *
 * @function
 * @arg {object[]} - key/value items
 * @return {string[]} array of `key`s.
 * @example
> pluckKey([{key: 'John', value: 'Foo'}, {key: 'Jane', value: 'Bar'}])
['John', 'Jane']
> pluckKey([{key: 'b', value: 2}, {key: 'a', value: 1}])
['b', 'a']
 */
export const pluckKey = _.mapWith(getKey);

/**
 * Pluck the `value` property value from an array of objects.
 *
 * @function
 * @arg {object[]} - key/value items
 * @return {string[]} array of `value`s.
 * @example
> pluckValue([{key: 'John', value: 'Foo'}, {key: 'Jane', value: 'Bar'}])
['Foo', 'Bar']
> pluckValue([{key: 'b', value: 2}, {key: 'a', value: 1}])
[2, 1]
 * @since 0.21.0
 */
export const pluckValue = _.mapWith(getValue);

/**
 * Return a copy of the provided array of objects
 * assigning each object index to a property with the provided key
 * (defaulting to `index`)
 *
 * @function
 * @arg {object[]} - Array of objects
 * @return {object[]} - Array of objects
 *
 * @example
> setIndex = setIndexAsKey()
> setIndex([{a: 2}, {c: 5}])
[{a: 2, index: 0}, {c: 5, index: 1}]

> setIndexAsIdx = setIndexAsKey('idx')
> setIndexAsIdx([{a: 2}, {c: 5}])
[{a: 2, idx: 0}, {c: 5, idx: 1}]
 *
 * @since 0.9.0
 */
export const setIndexAsKey = (key = 'index') => _.pipe([
	_.zipWithIndex,
	_.mapWith(([obj, index]) => _.setIn(obj, key, index))
]);

/**
 * Return a copy of the provided array with items
 * sorted by `value` (ascending) then by `key` (ascending)
 *
 * @function
 * @arg {array} - key/value items
 * @return {array} - sorted items
 *
 * @example
> items = [
	{key: 'b', value: 1},
	{key: 'a', value: 4},
	{key: 'a', value: -30},
	{key: 'a', value: 1},
]
> sortValueAscKeyAsc(items)
[
	{key: 'a', value: -30},
	{key: 'a', value: 1},
	{key: 'b', value: 1},
	{key: 'a', value: 4},
]
 *
 * @since 0.5.0
 */
export const sortValueAscKeyAsc = _.sortWith([getValue, getKey]);

/**
 * Return a copy of the provided array with items
 * sorted by `value` (ascending) then by `key` (descending)
 *
 * @function
 * @arg {array} - key/value items
 * @return {array} - sorted items
 *
 * @example
> items = [
	{key: 'b', value: 1},
	{key: 'a', value: 4},
	{key: 'a', value: -30},
	{key: 'a', value: 1},
]
> sortValueAscKeyAsc(items)
[
	{key: 'a', value: -30},
	{key: 'b', value: 1},
	{key: 'a', value: 1},
	{key: 'a', value: 4},
]
 *
 * @since 0.5.0
 */

export const sortValueAscKeyDesc = _.sortWith([
	getValue,
	_.sorterDesc(getKey)
]);

/**
 * Return a copy of the provided array with items
 * sorted by `value` (descending) then by `key` (ascending)
 *
 * @function
 * @arg {array} - key/value items
 * @return {array} - sorted items
 *
 * @example
> items = [
	{key: 'b', value: 1},
	{key: 'a', value: 4},
	{key: 'a', value: -30},
	{key: 'a', value: 1},
]
> sortValueDescKeyAsc(items)
[
	{key: 'a', value: 4},
	{key: 'a', value: 1},
	{key: 'b', value: 1},
	{key: 'a', value: -30},
]
 *
 * @since 0.5.0
 */
export const sortValueDescKeyAsc = _.sortWith([
	_.sorterDesc(getValue),
	getKey
]);

/**
 * Return a copy of the provided array with items
 * sorted by `value` (descending) then by `key` (descending)
 *
 * @function
 * @arg {array} - key/value items
 * @return {array} - sorted items
 *
 * @example
> items = [
	{key: 'b', value: 1},
	{key: 'a', value: 4},
	{key: 'a', value: -30},
	{key: 'a', value: 1},
]
> sortValueDescKeyDesc(items)
[
	{key: 'a', value: 4},
	{key: 'b', value: 1},
	{key: 'a', value: 1},
	{key: 'a', value: -30},
]
 *
 * @since 0.5.0
 */
export const sortValueDescKeyDesc = _.sortWith([
	_.sorterDesc(getValue),
	_.sorterDesc(getKey)
]);

/**
 * Return a copy of the array with values at the provided indices swapped
 *
 * @function
 * @arg {array} array
 * @arg {integer} index1
 * @arg {integer} index2
 * @return {array}
 *
 * @example swap([0, 1, 2, 3, 4, 5], 1, 4) // [0, 4, 2, 3, 1, 5]
 *
 * @since 0.1.0
 */
export const swap = (array, indexA, indexB) => _.reduce(
	array,
	(acc, item, index, _array) => {
		if (index === indexA) {
			acc.push(_array[indexB]);
		} else if (index === indexB) {
			acc.push(_array[indexA]);
		} else {
			acc.push(item);
		}

		return acc;
	},
	[]
);

/**
 * Return a copy of the provided array without all instances of the provided item
 * if the items is in the array or appending the item otherwise.
 *
 * @function
 * @arg {array} array
 * @arg {*} item
 * @return {array}
 *
 * @example
> arr0 = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
> arrNo0 = [1, 2, 3, 4, 1, 2, 3, 4]
> arrTail0 = [1, 2, 3, 4, 1, 2, 3, 4, 0]
> arrTailObj = [1, 2, 3, 4, 1, 2, 3, 4, {a: 1}]

> toggleItem(arr0, 0)
[1, 2, 3, 4, 1, 2, 3, 4]
> toggleItem(arrNo0, 0)
[1, 2, 3, 4, 1, 2, 3, 4, 0]
> toggleItem(arrNo0, {a: 1})
[1, 2, 3, 4, 1, 2, 3, 4, {a: 1}]
> toggleItem(arrTailObj, {a: 1})
[1, 2, 3, 4, 1, 2, 3, 4]
 *
 * @since 0.1.0
 */
export const toggleItem = (array, item) => {
	let found = false;

	let result = _.reduce(array, (acc, x) => {
		if (areEquals(x, item)) {
			found = true;
		} else {
			acc.push(x);
		}

		return acc;
	}, []);

	if (!found) {
		result.push(item);
	}

	return result;
};