How to push values into array using recursive function? - javascript

I'm trying to push values into array by recursive function, but I get the error message "Uncaught TypeError: Array.push() is not a function", although the push() method is a regular array method. Where the problem could be?
let someArr = ['a', 'b', 'c'];
let someNum = 3;
let fillArr = (arr, num) => x = (num != 0) ? fillArr(arr.push(num), num -1) : arr;
console.log(fillArr(someArr, someNum))
// expected output: ['a', 'b', 'c', 3, 2, 1];
// actual output: Uncaught TypeError: arr.push is not a function

The comma operator is often overlooked; it could provide a nice escape hatch:
const fillArr =
(arr, num) =>
(num === 0
? arr
: fillArr((arr.push(num), arr), num - 1));
//..............^^^^^^^^^^^^^^^^^^^^
fillArr(['a', 'b', 'c'], 3)
//=> ["a", "b", "c", 3, 2, 1]
However you shouldn't work on the original array with Array#push as it will mutate it (unless it was your intention!)
const arr = ['a', 'b', 'c'];
fillArr(arr, 3);
arr;
//=> ["a", "b", "c", 3, 2, 1] Oops!
Instead you should work with a new array:
const arr = ['a', 'b', 'c'];
const fillArr =
(arr, num, nums = []) =>
(num === 0
? arr.concat(nums)
: fillArr(arr, num - 1, (nums.push(num), nums)));
fillArr(arr, 3);
//=> ["a", "b", "c", 3, 2, 1]
arr;
//=> ["a", "b", "c"]

What Mr.#deceze said is correct. If you still want to do that..
Do something like
let fillArr = (arr, num) => x = (num != 0) ? fillArr(arr, num -1, arr.push(num)) : arr;
fillArr will receive someArr got num pushed and the last argument will be ignored

Related

How can I optimize the combination that contains repeated values?

I have an array with three values.
["a","b","c"]
I'm trying to create the following combination with the above array.
0: ["a", "a", "a"]
1: ["a", "a", "b"]
2: ["a", "a", "c"]
3: ["a", "b", "b"]
4: ["a", "b", "c"]
5: ["a", "c", "c"]
6: ["b", "b", "b"]
7: ["b", "b", "c"]
8: ["b", "c", "c"]
9: ["c", "c", "c"]
I wrote the code that was successful. But the code is not optimized. How can I make this code simple.
function test(arr, arr2=[], result=[]) {
if (arr2.length < 3) {
let proxy_arr = [...arr];
Object.keys(proxy_arr).forEach(index=>{
if (!test(arr, [...arr2].concat(proxy_arr[index]), result)) {
result.push([...arr2].concat(proxy_arr[index]));
} else {
//debugger;
arr = arr.slice(1);
}
}
);
return result;
}
return false;
}
result = test(["a", "b", "c"]);
You can use a recursive generator function to do most of the work. Array.from a generator will stuff the results in an array.
let vec = ['a', 'b', 'c'];
function* combo(n, k = 0, prefix = []) {
if (n == 0) yield prefix;
else for (let i = k; i < vec.length; ++i) {
yield* combo(n - 1, i, [...prefix, vec[i]]);
}
}
let test = Array.from(combo(3));
console.log(JSON.stringify(test));
Updated Version
Inspired by Wyck's solution, I made another version. This one is much cleaner. It uses much the same technique as Wyck did, but skips the generator code.
const makeBags = (n, xs, prefix = []) =>
n == 0
? [prefix]
: xs .flatMap ((v, i) => makeBags (n - 1, xs .slice (i), [...prefix, v]))
console .log (
JSON .stringify (makeBags (3, ['a', 'b', 'c']))
)
Note that although the additional default parameter looks like it might be for tail-call optimization, this code is not ready for TCO.
My First Solution
Here is a straightforward recursive solution, returning the empty list if the list of letters is empty and otherwise determining how many of the initial letter to include and recurring on the remaining letters. I have no idea if this is more optimal in any sense than the original except in terms of code cleanliness. But it's more generic, accepting an argument to tell how many items are in the output separate from the number of items in the list.
const range = (lo, hi) =>
[...Array (hi + 1 - lo)] .map ((_, i) => i + lo)
const prefixAll = (p, xs) =>
xs .map (x => [...p, ...x])
const groupsOf = (n, [x = undefined, ...xs]) =>
x == undefined
? []
: [
Array (n) .fill (x),
...range (1, n) .flatMap (i => prefixAll (Array (n - i) .fill (x), groupsOf (i, xs)))
]
console .log (
groupsOf (3, ['a', 'b', 'c'])
)
range is a simple utility function: range(3, 10) //=> [3, 4, 5, 6, 7, 8, 9, 10]
prefixAll is a helper, which could be inlined if preferred. It simply prefixes each array in the second argument with the values in the first one.
prefixAll(['a', 'b'], [['c'], ['c', 'c'], ['c', 'd']])
//=> [['a', 'b', 'c'], ['a', 'b', 'c', 'c'], ['a', 'b', 'c', 'd']]
While this isn't overly complex, there is almost certainly a better solution which does not involve Array (n) .fill (x), doing the recursive step as one simple flatMap. But I don't have time now to figure that out.
you are taking about the permutation algorithm.
here is solution without repitation
function permutateWithoutRep(permutationOptions) {
if (permutationOptions.length === 1) {
return [permutationOptions];
}
// Init permutations array.
const permutations = [];
// Get all permutations for permutationOptions excluding the first element.
const smallerPermutations = permutateWithoutRep(permutationOptions.slice(1));
// Insert first option into every possible position of every smaller permutation.
const firstOption = permutationOptions[0];
for (let permIndex = 0; permIndex < smallerPermutations.length; permIndex += 1) {
const smallerPermutation = smallerPermutations[permIndex];
// Insert first option into every possible position of smallerPermutation.
for (let positionIndex = 0; positionIndex <= smallerPermutation.length; positionIndex += 1) {
const permutationPrefix = smallerPermutation.slice(0, positionIndex);
const permutationSuffix = smallerPermutation.slice(positionIndex);
permutations.push(permutationPrefix.concat([firstOption], permutationSuffix));
}
}
return permutations;
}
console.log(permutateWithoutRep(['a', 'b', 'c']))
With Repitation:
function permutateWithRep(
permutationOptions,
permutationLength = permutationOptions.length,
) {
if (permutationLength === 1) {
return permutationOptions.map(permutationOption => [permutationOption]);
}
// Init permutations array.
const permutations = [];
// Get smaller permutations.
const smallerPermutations = permutateWithRep(
permutationOptions,
permutationLength - 1,
);
// Go through all options and join it to the smaller permutations.
permutationOptions.forEach((currentOption) => {
smallerPermutations.forEach((smallerPermutation) => {
permutations.push([currentOption].concat(smallerPermutation));
});
});
return permutations;
}
console.log(permutateWithRep(['a', 'b', 'c']))

Grouping same values from JavaScript array [duplicate]

This question already has answers here:
How to count duplicate value in an array in javascript
(35 answers)
Closed 4 years ago.
let array = ['a', 'a', 'a', 'b', 'b', 'c'];
i want to group double values from array, to get result like this:
result:
['3a, 2b, c']
(or something similar)
Any idea?
You can use .reduce() and .map() methods:
let array = ['a', 'a', 'a', 'b', 'b', 'c'];
let result = Object.entries(
array.reduce((r, c) => (r[c] = (r[c] || 0) + 1, r) , {})
).map(([k, v]) => v == 1 ? k : v + k);
console.log(result);
You can also use Map if you need items in specific order:
let array = ['a', 'a', 'a', 'b', 'b', 'c'];
let result = (((arr, map) => {
arr.forEach(s => map.set(s, (map.get(s) || 0) + 1));
return [...map.entries()].map(([k, v]) => v == 1 ? k : v + k);
})(array, new Map()));
console.log(result);
I would recommend using a dictionary to track the duplicate values in the array.
var dictionary = {};
for(var i = 0;i < array.length;i++){
var value = array[i];
if(dictionary[value] === undefined){
dictionary[value] = 0;
}
dictionary[value] = dictionary[value] + 1;
}
console.log(dictionary)
function count() {
array_elements = ["a", "b", "c", "d", "e", "a", "b", "c", "f", "g", "h", "h", "h", "e", "a"];
array_elements.sort();
var current_elements = null;
var count= 0;
for (var i = 0; i < array_elements.length; i++) {
if (array_elements[i] != current_elements) {
if (cnt > 0) {
console.log(current_elements+count);
}
current_elements= array_elements[i];
count= 1;
} else {
count++;
}
}
if (cnt > 0) {
console.log(current_elements+count);
}
}
A single loop approach for simple run-length encoding (RLE) function.
let array = ['a', 'a', 'a', 'b', 'b', 'c'],
result = array.reduce(
(r, c, i, a) => r.concat(c === a[i - 1]
? ((+r.pop().slice(0, -1) || 1) + 1) + c
: c),
[]
);
console.log(result);
You can use Array.reduce() to produce an object with key-value (array value and count) pairs, then Array.map() to produce an array using Object.keys().
let array = ['a', 'a', 'a', 'b', 'b', 'c'];
let countObj = array.reduce((acc, val) => (acc[val] = acc[val] ? acc[val] + 1 : 1, acc), {});
console.log(countObj);
let countArr = Object.keys(countObj).map(key => '' + countObj[key] + key);
console.log(countArr);
For "something similar" why not just have an object that maps letter to occurrence. You can then simply use dot notation to get the property value assigned to each key.
Here I've used reduce.
const arr = ['a', 'a', 'a', 'b', 'b', 'c'];
// `reduce` over the arr
const obj = arr.reduce((acc, c) => {
// If the object (acc) we passed in doesn't have
// a key assigned to the letter in the current
// iteration (c) add it, set it to 0, then
// add one, otherwise, if there is a key, add one
acc[c] = (acc[c] || 0) + 1;
// Return the object for the next iteration
return acc;
// Pass in an initial object
}, {});
console.log(obj);
// Grab the value of `a` property
console.log(obj.a);

How to insert a new element at any position of a JS array?

I have an array [a, b, c]. I want to be able to insert a value between each elements of this array like that: [0, a, 0, b, 0, c, 0].
I guess it would be something like this, but I can't make it works.
for (let i = 0; i < array.length; i++) {
newArray = [
...array.splice(0, i),
0,
...array.splice(i, array.length),
];
}
Thank you for helping me!
For getting a new array, you could concat the part an add a zero element for each element.
var array = ['a', 'b', 'c'],
result = array.reduce((r, a) => r.concat(a, 0), [0]);
console.log(result);
Using the same array
var array = ['a', 'b', 'c'],
i = 0;
while (i <= array.length) {
array.splice(i, 0, 0);
i += 2;
}
console.log(array);
A bit shorter with iterating from the end.
var array = ['a', 'b', 'c'],
i = array.length;
do {
array.splice(i, 0, 0);
} while (i--)
console.log(array);
Another way if you want to exclude the start and end of array is :
var arr = ['a', 'b', 'c']
var newArr = [...arr].map((e, i) => i < arr.length - 1 ? [e, 0] : [e]).reduce((a, b) => a.concat(b))
console.log(newArr)
You can use map() with ES6 spread syntax and concat()
var arr = ['a', 'b', 'c']
var newArr = [0].concat(...arr.map(e => [e, 0]))
console.log(newArr)
Another ES6+ version using flatmap (if creation of a new array instead is ok):
['a', 'b', 'c', 'd']
.flatMap((e, index) => index ? [e, 0] : [0, e, 0])
Another way:
var a = ['a', 'b', 'c'],
b;
b = a.reduce((arr, b) => [...arr, b, 0], []);
console.log(b);
You could use .reduce():
function intersperse(arr, val) {
return arr.reduce((acc, next) => {
acc.push(next);
acc.push(val);
return acc;
}, [val]);
}
console.log(intersperse(['a', 'b', 'c'], 0));
Or to accomplish this by modifying the original array:
function intersperse(arr, val) {
for (let i = 0; i <= arr.length; i += 2) {
arr.splice(i, 0, val);
}
return arr;
}
console.log(intersperse(['a', 'b', 'c'], 0));
You can try with the below code. It will add 0 in middle of each two element of the array
console.log(['a', 'b', 'c'].reduce((r, a) => r.concat(a,0), [0]).slice(1, -1))
You just need to loop over the array elements and add the new element in each iteration, and if you reach the last iteration add the new element after the last item.
This is how should be your code:
var arr = ['a', 'b', 'c'];
var results = [];
arr.forEach(function(el, index) {
results.push(addition);
results.push(el);
if (index === arr.length - 1)
results.push(addition);
});
Demo:
This is a Demo snippet:
var arr = ['a', 'b', 'c'];
var results = [];
var addition = 0;
arr.forEach(function(el, index) {
results.push(addition);
results.push(el);
if(index === arr.length -1)
results.push(addition);
});
console.log(results);
If you want to insert elements only after existing ones:
console.log(["a", "b", "c"].map(i => [i, 0]).flat())
You could do
let arr = ['a', 'b', 'c'];
arr = arr.reduce((a, b) => {
a.push(0);
a.push(b);
return a;
}, []);
arr.push(0);
console.log(arr);
function insert(arr, elm) {
var newArr = [];
for(var i = 0; i < arr.length; i++) { // for each element in the array arr
newArr.push(elm); // add the new element to newArr
newArr.push(arr[i]); // add the current element from arr
}
newArr.push(elm); // finally add the new element to the end of newArr
return newArr;
}
console.log(insert(["a", "b", "c"], 0));
It could be done with strings by splitting and joining.
var arr = ['a', 'b', 'c'];
var newArray = ("0," + arr.toString().split(",").join(",0,")).split(",");
console.log(newArray);
This looks like the intersperse algorithm but does some addition to the head and tail as well. So i call it extrasperse.
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
extrasperse = (x,a) => a.reduce((p,c,i) => (p[2*i+1] = c, p), Array(2*a.length+1).fill(x));
console.log(JSON.stringify(extrasperse("X",arr)));
let arr = ['a', 'b', 'c'];
function insert(items, separator) {
const result = items.reduce(
(res, el) => [...res, el, separator], [separator]);
return result;
}
console.log(insert(arr, '0'));
all of the above methods in very long strings made my android computer run on React Native go out of memory.
I got it to work with this
let arr = ['a', 'b', 'c'];
let tmpArr = [];
for (const item in arr) {
tmpArr.push(item);
tmpArr.push(0);
}
console.log(tmpArr);
Another way is to use some functional methods like zip and flat. Check out lodash.
const array = ['a', 'b', 'c']
const zeros = Array(array.length + 1).fill(0)
const result = _.zip(zeros, array).flat().filter(x => x !== undefined)
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
Straight forward way of inserting only between:
const arr = ['a', 'b', 'c'];
arr.map((v, i) => !i || i === arr.length - 1 ? [v] : [0, v]).flat()
I think this is correct, ie, just adds the element between the elements of the array, and should be pretty efficient:
const intersperse = ([first, ...tail]: any[], element: any) => (
(first === undefined) ? [] : [first].concat(...tail.map((e) => [element, e]))
);
console.log(intersperse([], 0));
console.log(intersperse([1], 0));
console.log(intersperse([1, 2, 3], 0));
Thanks for your question and thanks to all contributors, for their answers.
This would be my approach
const arr = ["a", "b", "c"];
let toAdd = 0;
for (let i = 0; i <= arr.length; i += 2) {
arr.splice(i, 0, toAdd);
}
console.log(arr);
or
const arr = ["a", "b", "c"];
let toAdd = 0;
const newArr = [];
newArr.unshift(toAdd);
for (let i = 0; i < arr.length; i++) {
newArr.push(arr[i]);
newArr.push(toAdd);
}
console.log(newArr);
Cheers
Nav

How to compare 2 arrays and get count of each matching item

Say an AJAX call returns 2 arrays on success. The arrays returned are different everytime, but there could be some elements in common. For example,
array1 = [a, b, c, d]
array2 = [a, b, b, b, a, c, b, c, c]
Now I want to get the number of times each element of array 1 appeared in array 2, in this case, the results would be:
a: 2
b: 4
c: 3
d: 0
I have the following code that compares the two arrays, but I can't figure out how to keep a counter, do I have to construct a new 2d array for each element of array1? In which case, how do I construct the array without knowing the elements inside of it first?
success: function (array1, array2) {
for (var i = 0; i < array1.length; i++) {
for (var j = 0; j < array2.length; j++) {
if (array2[j] == array1[i]) {
console.log("found match: " + array2[j]);
// counting
// count should go up by 1
}
}
}
}
var array1 = ['a', 'b', 'c', 'd'];
var array2 = ['a', 'b', 'b', 'b', 'a', 'c', 'b', 'c', 'c'];
var result = {}
array1.forEach(function(item) {
result[item] = 0
})
array2.forEach(function(item) {
if(result.hasOwnProperty(item)) {
result[item]++
}
})
console.log(result);
To achieve this you just need to loop through array1 to get the unique values, then you can use filter() to find how many of them exist in the second array. Try this:
var array1 = ['a', 'b', 'c', 'd'];
var array2 = ['a', 'b', 'b', 'b', 'a', 'c', 'b', 'c', 'c'];
var result = {};
array1.forEach(function(item) {
result[item] = array2.filter(t => t == item).length;
})
console.log(result);
You may do as follows in O(n);
var arr1 = ["a", "b", "c", "d"],
arr2 = ["a", "b", "b", "b", "a", "c", "b", "c", "c"],
result = arr2.reduce((h,p) => (h.hasOwnProperty(p) && h[p]++, h) ,arr1.reduce((m,k) => (m[k] = 0,m),{}));
console.log(result);

How to merge two arrays in JavaScript and keep their order

I had an whiteboard task that stumped me in the interview, however I have written a solution and wondered if anyone has improvements on it as I'm iterating which the interviewer said not to. The two arrays must be merged with the order being array1[0], array2[0], array1[1], array2[1]... (see expectedResult) etc
const options = [[1, 12, 5], ["a", "b", "c", "d", "e"]]
const expectedResult = [1, "a", 12, "b", 5, "c", "d", "e"]
function mergeArrays(first, second) {
let returnArray = []
first.forEach((value, key) => {
returnArray.push(value)
if (second[key]) returnArray.push(second[key])
if (!first[key + 1] && second[key + 1]) {
returnArray.push(
...second.slice(key + 1, second.length)
)
}
})
return returnArray
}
const result = mergeArrays(options[0], options[1])
console.log(result.toString() === expectedResult.toString(), result)
With reduce (as an alternative to the classical for/while loop control structures)
const options = [[1, 12, 5], ["a", "b", "c", "d", "e"]];
const expectedResult = [1, "a", 12, "b", 5, "c", "d", "e"]
// a is the accumulator
// cV, cI are resp. current value and current index
result = options[0].reduce(function (a, cV, cI) {
return a.concat([cV,options[1][cI]]);
},[]);
result = result.concat(options[1].splice(options[0].length));
console.log(result.toString() === expectedResult.toString(), result)
At each step two elements are added to the accumulator array a using concat.
I go the classic way, with a while loop, because it minimize the checks inside of the loop and appends without another check just the rest of one of the arrays.
function mergeArrays(first, second) {
var min = Math.min(first.length, second.length),
i = 0,
result = [];
while (i < min) {
result.push(first[i], second[i]);
++i;
}
return result.concat(first.slice(min), second.slice(min));
}
const options = [[1, 12, 5], ["a", "b", "c", "d", "e"]];
console.log(mergeArrays(...options));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Instead of using value in if conditions , check for length of array.
Problems I see in code are at conditions
if (second[key]) returnArray.push(second[key])
// will not run if second[key] is 0,null,undefined.
if (!first[key + 1] && second[key + 1])
// will produce unwanted result if value reference is 0,null,undefined.
so instead, check for length would produce better result
So the condition
if (second[key]) returnArray.push(second[key])
can be changed into
if( second.length > key) returnArray.push(second[key])
You can use a recursive zipping function, using spread to feed an array of two into it as its parameters:
var z = (a, b) => a.length ? [a[0], ...z(b, a.slice(1))] : b;
var options =
[
[1, 12, 5],
["a", "b", "c", "d", "e"]
];
var expectedResult = z(...options);
console.log(JSON.stringify(expectedResult));
or for any number of array inputs:
var z = (a = [], ...b) =>
b.length ? a.length ? [a[0], ...z(...b, a.slice(1))] : z(...b) : a;
var options =
[
[1, 2],
'♦♡♣♤♥♢',
['A', 'B', 'C'],
['😊', '😔', '😠'],
[null, NaN, undefined]
];
var expectedResult = z(...options);
var stringify = (o) => JSON.stringify(o, (k, v) => v === undefined ? '__undefined__' : v !== v ? '__NaN__' : v).replace(/"__undefined__"/g, 'undefined').replace(/"__NaN__"/g, 'NaN');
console.log(stringify(expectedResult));

Categories

Resources