Source: arrays.js

  1. /*!
  2. * Module Arrays
  3. */
  4. /**
  5. * @namespace Arrays
  6. */
  7. const MODULE_NAME = 'Arrays';
  8. //###[ IMPORTS ]########################################################################################################
  9. import {assert, isArray, isNumber, isString, isMap, isSet, isPlainObject, orDefault} from './basic.js';
  10. //###[ EXPORTS ]########################################################################################################
  11. /**
  12. * @namespace Arrays:removeFrom
  13. */
  14. /**
  15. * Removes Elements from an Array, where to and from are inclusive.
  16. * If you only provide "from", that exact index is removed.
  17. *
  18. * Does not modify the original.
  19. *
  20. * Keep in mind, that "from" should normally be smaller than "to" to slice from left to right. This means that the elements
  21. * indexed by "from" and "to" should have the right order. For example: [1,2,3,4]. Here from=-1 and to=-3 are illegal since
  22. * "from" references a later element than "to", but there are also viable examples where "from" is numerically bigger. If we
  23. * use from=2 and to=-1, "from" is numerically bigger, but references an earlier element than -1 (which is the last element),
  24. * which is totally okay. Just make sure "from" comes before "to" in the element's order.
  25. *
  26. * If you provide a string for "from" everything matching that string with its string representation will be removed
  27. * (you can provide stringification for objects via the toString method, see:
  28. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString).
  29. *
  30. * Everything else (except collections) provided for "from" will determine the element(s) to remove by identity (===).
  31. *
  32. * If you want to remove several elements at once, you may provide an iterable collection as "from" and
  33. * This also makes it possible to actually remove numbers from an array by identity: define the number(s) as an array,
  34. * to signal, that these are no indices. If you need to remove a collection from the array by reference, either put
  35. * the collection in an array or set "to" to true, to force the collection being treated as a reference.
  36. *
  37. * Iterable collections, usable in "from" in the sense of this method are:
  38. * - PlainObject (values)
  39. * - Array
  40. * - Set
  41. * - Map (values)
  42. * To remove these by reference from target, instead of iterating them, set "to" to true.
  43. *
  44. * @param {Array} target - the array to remove elements from
  45. * @param {*} from - numerical index to start removing from (can also be negative to start counting from back), a string to identify elements to remove by their string representation or any other value identifying elements to remove by identity, if this is an iterable collection, the collection is iterated instead of being treated as a reference, enforce treatment as a reference, by setting "to" to true
  46. * @param {?Number|Boolean} [to=null] - index to end removing (can also be negative to end counting from back), if true, "from" defines a given iterable collection to be removed by reference, instead of removing the contained values, with this you can remove an array from an array for example
  47. * @throws error if target is not an array
  48. * @returns {Array} new array without index/range/matches
  49. *
  50. * @memberof Arrays:removeFrom
  51. * @alias removeFrom
  52. * @example
  53. * removeFrom([1, 2, 3, 4, 5], 0, 2);
  54. * => [4, 5]
  55. * removeFrom([1, 2, 3, 4, 5], -3, -1);
  56. * => [1, 2]
  57. * removeFrom([{a : 'b', toString(){ return 'b'; }}, 'b', b, 1], 'b');
  58. * => [b, 1]
  59. * removeFrom([{a : 'b', toString(){ return 'b'; }}, 'b', b, 1], b);
  60. * => [{a : 'b', toString(){ return 'b'; }}, 'b', 1]
  61. * removeFrom([true, true, false, true, true], true)
  62. * => [false]
  63. * removeFrom([{a : 'b', toString(){ return 'b'; }}, 'b', b, 1, 2], ['b', b, 2]);
  64. * => [1]
  65. */
  66. export function removeFrom(target, from, to=null){
  67. const __methodName__ = 'removeFrom';
  68. assert(isArray(target), `${MODULE_NAME}:${__methodName__} | target is no array`);
  69. if( isNumber(from) && (to !== true) ){
  70. from = parseInt(from, 10);
  71. to = orDefault(to, null, 'int');
  72. target = target.slice(0);
  73. const rest = target.slice((to || from) + 1 || target.length);
  74. target.length = (from < 0) ? (target.length + from) : from;
  75. return target.concat(rest);
  76. } else if( isString(from) ){
  77. return target.reduce((reducedArray, item) => {
  78. if( `${item}` !== from ){
  79. reducedArray.push(item);
  80. }
  81. return reducedArray;
  82. }, []);
  83. } else {
  84. let fromList;
  85. if( isPlainObject(from) ){
  86. fromList = Object.values(from);
  87. } else if( isMap(from) ){
  88. fromList = Array.from(from.values());
  89. } else if( isSet(from) ){
  90. fromList = Array.from(from.values());
  91. } else {
  92. fromList = Array.from(from);
  93. }
  94. if( (fromList.length > 0) && (to !== true) ){
  95. return fromList.reduce((reducedArray, item) => {
  96. reducedArray = removeFrom(reducedArray, item, true);
  97. return reducedArray;
  98. }, [...target]);
  99. } else {
  100. return target.reduce((reducedArray, item) => {
  101. if( item !== from ){
  102. reducedArray.push(item);
  103. }
  104. return reducedArray;
  105. }, []);
  106. }
  107. }
  108. }
  109. /**
  110. * @namespace Arrays:generateRange
  111. */
  112. /**
  113. * To remove these by reference from target, instead of iterating them, set "to" to true.
  114. *
  115. * @param {Number|String} from - the start of the range, may also be negative, if larger than "to", the range is created in reverse, as a string, this can be a single character
  116. * @param {Number|String} to - the end of the range, may also be negative, if smaller than "from", the range is created in reverse, as a string, this can be a single character
  117. * @param {?Number} [step=null] - the (positive) step length in the range to use between individual range values, if the step length does not hit "to" exactly, the range will end at the last possible value before "to"
  118. * @throws error if "from" or "to" are empty strings
  119. * @returns {Array} new array with generated range
  120. *
  121. * @memberof Arrays:generateRange
  122. * @alias generateRange
  123. * @example
  124. * generateRange(0, 10, 2)
  125. * => [0, 2, 4, 6, 8, 10]
  126. * generateRange(5, -5, 3.5)
  127. * => [5, 1.5, -2]
  128. * generateRange('a', 'g, 3)
  129. * => ['a', 'd', 'g']
  130. */
  131. export function generateRange(from, to, step=1){
  132. const
  133. __methodName__ = 'generateRange',
  134. cannotBeEmptyMessage = 'cannot be empty'
  135. ;
  136. const generateChars = !isNumber(from) || !isNumber(to);
  137. if( generateChars ){
  138. if( !isNumber(from) ){
  139. from = `${from}`;
  140. assert((from.length > 0), `${MODULE_NAME}:${__methodName__} | "from" ${cannotBeEmptyMessage}`);
  141. from = from.charCodeAt(0);
  142. }
  143. if( !isNumber(to) ){
  144. to = `${to}`;
  145. assert((to.length > 0), `${MODULE_NAME}:${__methodName__} | "to" ${cannotBeEmptyMessage}`);
  146. to = to.charCodeAt(0);
  147. }
  148. }
  149. step = orDefault(step, 1, 'float');
  150. const
  151. factor = ((to - from) < 0) ? -1 : 1,
  152. range = []
  153. ;
  154. let
  155. diff = Math.abs(to - from),
  156. current = from
  157. ;
  158. while( diff >= 0 ){
  159. range.push(generateChars ? String.fromCharCode(current) : current);
  160. current += step * factor;
  161. diff -= step;
  162. }
  163. return range;
  164. }