Related
I've been trying to write a function which takes in an array as the first argument, then one or more other arguments which are numbers. The purpose of the function is to check whether these numbers are present in the array and remove them if so.
I have tried the following but the results haven't been what I had expected.
The desired outcome is that 3 and 2 be removed from the array leaving me with [1,4]. Instead, only 2 is removed with the end result being [1,3,4]. I've been struggling with this for a while and would appreciate any feedback you might be able to provide. I'm knew to this and this is the first problem which has left me stumped so far!
function test(myArray, ...checkNums) {
for (let num in checkNums) {
for (let num2 in myArray) {
if (myArray[num] == checkNums[num2]) {
myArray.splice(num, 1);
}
}
}
return myArray;
}
const arr = test([1, 2, 3, 4], 3, 2);
console.log({arr})
The easiest way is to just filter the array to only keep values not in checkNums. Using a Set gives better performance (depending on the implementation, lookup is either O(1) or O(log n) or anything sublinear for a Set, compared to O(n) for an Array).
function test(myArray, ...checkNums) {
const checkNumsSet = new Set(checkNums);
return myArray.filter((num) => !checkNumsSet.has(num));
}
const arr = test([1, 2, 3, 4], 3, 2);
console.log({arr})
With myArray and checkNums as arrays, you can use a filter based on .includes:
const myArray = [1,2,3,4];
const checkNums = [3,4];
const filterNums = (nums, checkNums) => {
return nums.filter(num => !checkNums.includes(num));
}
console.log(filterNums(myArray, checkNums));
Your code is removing items so your index variable is stale after you remove an element. The simplest fix is to just iterate backwards.
Also, you should avoid using for in to iterate over an array
Lastly, your array was just modifying what was passed in but you never kept a reference to it, I'm returning the modified array.
function test(myArray, ...checkNums) {
for (let checkNumsIndex = checkNums.length - 1; checkNumsIndex >=0; checkNumsIndex--) {
for (let myArrayIndex = myArray.length - 1; myArrayIndex >=0; myArrayIndex--) {
if (myArray[myArrayIndex] == checkNums[checkNumsIndex]) {
myArray.splice(myArrayIndex, 1);
}
}
}
return myArray;
}
const arr = test([1, 2, 3, 4], 3, 2);
console.log({arr});
A more straight forward is using filter and includes. This doesn't have the problem that your example has where you're testing values outside of the bounds of the array.
function removeElements(myArray, ...checkNums) {
return myArray.filter((num) => !checkNums.includes(num));
}
const arr = removeElements([1, 2, 3, 4], 3, 2);
console.log({arr});
You can use for...of see for..in vs for...of in order to iterate through your arguments, check if the number exist in your array and if yes, splice at index number
function test(myArray, ...checkNums) {
//get the elements from ...checkNums
for (let num of checkNums) {
//check if your number exist in array
if (myArray.includes(num)) {
const indexOfNum = myArray.indexOf(num);
//if it exists splice at found index of your umber
myArray.splice(indexOfNum, 1)
}
}
return myArray;
}
const result = test([1, 2, 3, 4], 3, 2);
console.log(result)
I'm a newbie to all of this and trying to improve myself by solving problems and challenges.
I came across a problem whereby I have an unordered array which contains 8 integers.
eg [2,3,1,4,6,5,8,7]
I need to sort it [1,2,3,4,5,6,7,8] and reorder the array so that the array starts with the end value and then the first value and so on eg [8,1,7,2,6,3,5,4,]
I worked out I could use map() to iterate across the array and then use push() with pop() and shift() however it leaves the last 2 numbers behind in the original array and I'm not sure why. I got around this by using a concat and a reverse but I still don't understand why pop and shift don't bring across all the elements.
Code below that doesn't pull all the elements:
const reorder = (array) => {
let store = []
array.sort((a, b) => a - b).map((item, i) => {
if (array) {
store.push(array.pop())
store.push(array.shift())
}
})
return store
}
reorder([2, 3, 1, 4, 6, 5, 8, 7]) // returns [8,1,7,2,6,3]
Code that works but I have to add a concat and a reverse:
const reorder = (array) => {
let store = []
array.sort((a, b) => a - b).map((item, i) => {
if (array) {
store.push(array.pop())
store.push(array.shift())
}
})
return store.concat(array.reverse())
}
reorder([2, 3, 1, 4, 6, 5, 8, 7]) //returns [8,1,7,2,6,3,5,4]
Thanks for any help
I would just bisect the array, sort them in opposite orders and then add each element from each array to a new array
Given that you want to then take the sorted bisected arrays and produce another single array, I'd then use Array.prototype.reduce:
const alternatingSort = function (array) {
array = array.sort();
const midpoint = Math.round(array.length / 2)
let arr1 = array.slice(0, midpoint);
let arr2 = array.slice(midpoint);
arr2 = arr2.sort(function (a, b) { return b - a });
return arr1.reduce(function (retVal, item, index) {
arr2[index] && retVal.push(arr2[index]);
retVal.push(item);
return retVal;
}, []);
}
console.log(alternatingSort([2, 3, 1, 4, 6, 5, 8, 7]));
console.log(alternatingSort([2, 3, 1, 4, 6, 5, 8])); // with odd number
As I've seen nobody explained why the original OP solution doesn't work, Here is why:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/
Map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values (including undefined).
It is not called for missing elements of the array; that is:
1.Indexes that have never been set;
2.which have been deleted; or
3.which have never been assigned a value.
So what is happening in our code is that:
On the first iteration,
[(2), 3, 1, 4, 6, 5, 8, 7]
Map picks the first element(2) in the array, and delete the first and last characters in the array, so the array becomes
[3,(1), 4, 6, 5, 8]
Now, as map will not consider deleted elements, the second element(1) in the current array is called, also the first and last element in also removed:
[1, 4,(6), 5]
Now, map is trying to find the third element(6), and delete the first and last element:
[4,6]
Now, map is trying to find the fourth element, which is out of bound, so the map function will terminate.
So, you are strongly advised not to use Array.prototype.shift or Array.prototype.pop in Array.prototype.map.
You can do it following way:
const reorder = (array) => {
array.sort((a, b) => a - b);
const result = [];
const length = array.length;
for (let i = 0; i < length; i++) {
if (i % 2 === 0) {
result.push(array.pop());
} else {
result.push(array.shift());
}
}
return result;
}
const result = reorder([2, 3, 1, 4, 6, 5, 7]);
console.log(result);
Notice that I've intentionally made the array length to be an odd number. Some of the solutions here will break if the length is an odd number.
Personally I would sort, split in half and then just insert in. Not very fancy, but gets the job done.
function strangeWeave (arr) {
var sorted = arr.slice().sort()
var result = sorted.splice(0,Math.floor(sorted.length/2))
for (let i=0;sorted.length;i+=2) {
result.splice(i,0,sorted.pop())
}
return result
}
console.log(strangeWeave([1,2]))
console.log(strangeWeave([1,2,3]))
console.log(strangeWeave([1,2,3,4,5,6,7,8]))
console.log(strangeWeave([1,2,3,4,5,6,7,8,9]))
There is a much easier solution to sort two different arrays, one normal and one in reverse, then connect them together. Here is the code for that:
var myArray = [1, 3, 2, 4, 5, 7, 6, 8];
function getCopy(arr) {
var x = [];
for(var i = 0; i < arr.length; i++)
x.push(arr[i]);
return x;
}
function sortMyWay(arr) {
var sortedArr = [],
leftSide = getCopy(arr).sort()
.splice(0, Math.ceil(arr.length / 2)),
rightSide = getCopy(arr).sort().reverse()
.splice(0, Math.floor(arr.length / 2));
for(var i = 0; i < arr.length; i++)
i % 2
? sortedArr.push(leftSide[Math.floor(i / 2)])
: sortedArr.push(rightSide[Math.floor(i / 2)]);
console.log(sortedArr);
return sortedArr;
}
var sortedArr = sortMyWay(myArray);
Hope it helped!
Happy coding :)
I have array of elements as input.all I need is to double the value of array elements.Though it is simple to make use of map to get the solution.I am interested in using reduce.eg:
io: var a = [1,2,3,4,5];
op:[2,4,6,8,10]; .but this is where I ended.
var c = a.reduce( (acc,b) => {
acc =b*2;
console.log(acc);
return acc
},[]);
var arr = [1, 2, 3, 4, 5];
var doubled = arr.reduce(function (memo, val) {
memo.push(val * 2);
return memo;
}, []);
console.log(doubled);
var arr2 = [1, 2, 3, 4, 5];
arr2.reduce(function (memo, val, i) {
memo[i] *= 2;
return memo;
}, arr2);
console.log(arr2);
var arr3 = [1, 2, 3, 4, 5];
arr3.reduce(function (memo, val, i) {
arr3[i] *= 2;
}, null);
console.log(arr3);
In the first solution, reduce starts with an empty array (the second argument provided to it). Then, that array is passed down to next iterations as the first argument (memo) to the function we provided. The second argument is the current element of the iteration. After the doubled value is pushed in the new array, that array is returned so it can be accessed in future iterations as memo.
In the second solution, no new array is created and the initial one is used instead. It is passed to reduce as its second element and later accessed through memo.
The third solution is like the second one except the reduced array is just referenced as it is. Notice that null must be passed as second argument. If nothing is passed, reduce will start from the second element (since there's no initial value) and the first element won't get doubled.
Note
The first time the callback is called, accumulator and currentValue can be one of two values. If no initialValue is provided, then accumulator will be equal to the first value in the array, and currentValue will be equal to the second.
let arrayOfNumbers = [1, 2, 3, 4];
arrayOfNumbers.reduce((accumulator, currentValue, index, array) => {
//code
},initialvalue);
if you miss this initialvalue argument then your index begins from one(1) and the accumulator will have the first value of array.
Hence your array[index] = array[index]*2
For example in such case
arr=[1,2,3,4,5]
output
arr=[1,4,6,8,10]
Now this will be the approach
let arrayOfNumbers = [1, 2, 3, 4];
arrayOfNumbers.reduce((accumulator, currentValue, index, array) => {
console.log(accumulator,currentValue);
array[index] = array[index]*2;
},0);
arr=[1,2,3,4,5]
output
arr=[1,4,6,8,10]
take initialvalue as 0(or any value), so that you can get the index from 0.
if you do this accumulator will get 0 and the index begins from 0 only.
Note
if you don't initialize accumulator value that is initialvalue.
Default
accumulator(initialvalue) will be arr[0]
and index begins from 1
and
if you initialize accumulator value that is initialvalue(argument) then
accumulator(initialvalue) will take that initialvalue and index begins from 0.
As you provided in your last parameter to reduce, acc is an array, although you're treating it like an integer. You'll want something like below:
var c = a.reduce( (acc,b) => {
acc.push(b*2);
console.log(acc);
return acc
},[]);
Try this
var c = a.reduce( (acc,b) => acc.concat(b*2)
,[]);
I'll provide a couple other answers that aren't shown here
1. Using spread syntax
const data =
[1,2,3,4,5];
const double = xs =>
xs.reduce ((ys, x) => [...ys, x * 2], [])
console.log(double(data))
// [ 2, 4, 6, 8, 10 ]
2. Using a destructured parameter with recursion
const data =
[1,2,3,4,5]
// this time we don't need reduce
const double = ([x,...xs]) =>
x === undefined ? [] : [x * 2, ...double(xs)]
console.log(double(data))
// [ 2, 4, 6, 8, 10 ]
3. Tacit (Point-free style)
const data =
[1,2,3,4,5]
const mult = x => y =>
y * x
const reduce = f => acc => ([x,...xs]) =>
x === undefined
? acc
: reduce (f) (f (acc, x)) (xs)
const map = f =>
reduce ((acc, x) =>
[...acc, f (x)]) ([])
// point-free double
const double =
map (mult (2))
console.log (double (data))
// [ 2, 4, 6, 8, 10 ]
I'm using the following function to add specific numbers into an array that I later want to be assigned to a variable. For this I'm using two for loops, but I feel like there is a more succinct way to do it. I tried merging the two loops in one without getting an error, but the output is not the same.
Working Example:
function fill () {
var array = [];
for (var index = 0; index < arguments.length; index++) {
for (var number = arguments[index][0]; number <= arguments[index][1]; number++)
array.push(number);
}
return array;
};
/* Use */
var keys = fill([1, 10], [32, 34]);
/* Output */
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32, 33, 34]
Merged Example:
function fill () {
var array = [];
for (var index = 0, number = arguments[index][0];
index < arguments.length && number <= arguments[index][1];
index++ && number++) {
array.push(number);
}
return array;
};
/* Use */
var keys = fill([1, 10], [32, 34]);
/* Output */
[1, 1]
Is it possible to actually merge the two loops into one? If not, is there a way to write the foregoing function in less code?
Your code in the first example is fine. There is no real "clean" way to remove the nested loops.
You could iterate over them with forEach, but then you'd still have nested loops, even if one of them is disguised as a function call:
function fill () {
var array = [];
Array.prototype.slice.apply(arguments) // Make an array out of arguments.
.forEach(function(arg){
for (var number = arg[0]; number <= arg[1]; number++){
array.push(number);
}
});
return array;
};
console.log(fill([1, 10], [32, 34]));
And you'd have to use Array.prototype.slice.apply to convert arguments to an actual array. (which is ugly)
So, basically, nested loops aren't necessarily "evil". Your first example is as good as it gets.
JavaScript is a functional language. For the sake of modern coding purposes a functional approach is best for the coder's benefit.
var fillArray = (...args) => args.reduce((res,arg) => res.concat(Array(...Array(arg[1]-arg[0]+1)).map((e,i) => i + arg[0])),[]),
filled = fillArray([1, 10], [32, 34]);
console.log(filled);
OK what happens here.. It's very simple. We do the job by fillArray function. fillArray function takes indefinite number of arguments. So we collect them all in an array called args by utilizing the ES6 rest operator ....
var fillArray = (...args)
Now that we have our source arrays in the args array we will apply a reduce operation to this array with an initial value of an empty array (res). What we will do is.. as per each source (arg) array we will create a new array and then we will concatenate this to the res array. Ok we receive [1,10] as source which means we need an array of length arg[1]-arg[0]+1 right. So comes
Array(...Array(arg[1]-arg[0]+1))
we could also do like Array(arg[1]-arg[0]+1).fill() same thing. We now have an array filled with "undefinite" in the needed length. Then comes map. This is really very simple as we apply to this undefinites array like
.map((e,i) => i + arg[0]))
which means each item will be the current index + offset which is the arg[0]
Then we concatenate this array to our results array and pass to the next source array. So you see it is very straight forward and maintainable.
You might not be able to escape the two loops, but that shouldn't necessarily be a goal either. The loop themselves aren't really harmful – it's only if you're iterating over the same data multiple times that you might want to reconsider your code
Consider this entirely different approach
const range = (x , y) =>
x > y
? []
: [ x, ...range (x + 1, y) ]
const concat = (xs, ys) =>
xs .concat (ys);
const flatMap = (f, xs) =>
xs .reduce ((acc, x) => concat (acc, f (x)), [])
const apply = f => xs =>
f (...xs)
const fill = (...ranges) =>
flatMap (apply (range), ranges);
console.log
(fill ( [1,10]
, [32,34]
, [40,49]
, [100,100]
)
)
So yes, #Redu is on the right track with "JavaScript is a functional language", but I think his/her answer falls short of delivering a well-composed functional answer.
The answer above shows how functions with individualized concerns can be easy to read, easy to write, and easy to combine to achieve complex computations.
In ES6, you could use the rest operator and build a new array, based on the items.
function fill(...p) {
return p.reduce((r, a) => r.concat(Array.apply(null, { length: a[1] - a[0] + 1 }).map(() => a[0]++)), []);
};
var keys = fill([1, 10], [32, 34]);
console.log(keys);
Similar to another answer, but a little more complete:
const arrays = [[1,10],[32,34],[9,12]]
const range = (a,b) => a >= b ? [] :
[...Array(b-a).keys()].map(i => i+a)
const result = arrays.reduce( (a,c) =>
a.concat( range(c[0], c[1]+1) ), [] )
// => [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32, 33, 34, 9, 10, 11, 12 ]
If you prefer a more traditional range function, then:
const arrays = [[1,10],[32,34],[9,12]]
function range(a,b) {
var arr = []
for (let i = a; i < b; i++)
arr.push(i)
return arr
}
const result = arrays.reduce( function(a,c) {
return a.concat( range(c[0], c[1]+1) )
}, [] )
After almost 2 years and some great answers that were posted to this thread proposing interesting alternatives, I found a way to merge the two loops into one, but it ain't pretty!
Code:
function fill () {
var
ar = [],
imax = arguments.length,
/* The functions that calculate the bounds of j. */
jmin = i => arguments[i][0],
jmax = i => arguments[i][1] + 1;
for (
let i = 0, j = jmin(i);
i < imax && j < jmax(i);
/* If j reaches max increment i and if i is less than max set j to min. */
ar.push(j++), (j == jmax(i)) && (i++, (i < imax) && (j = jmin(i)))
);
return ar;
};
/* Use */
var keys = fill([1, 10], [32, 34], [76, 79]);
console.log.apply(console, keys);
Say I have an array var arr = [1, 2, 3], and I want to separate each element by an element eg. var sep = "&", so the output is [1, "&", 2, "&", 3].
Another way to think about it is I want to do Array.prototype.join (arr.join(sep)) without the result being a string (because the elements and separator I am trying to use are Objects, not strings).
Is there a functional/nice/elegant way to do this in either es6/7 or lodash without something that feels clunky like:
_.flatten(arr.map((el, i) => [el, i < arr.length-1 ? sep : null])) // too complex
or
_.flatten(arr.map(el => [el, sep]).slice(0,-1) // extra sep added, memory wasted
or even
arr.reduce((prev,curr) => { prev.push(curr, sep); return prev; }, []).slice(0,-1)
// probably the best out of the three, but I have to do a map already
// and I still have the same problem as the previous two - either
// inline ternary or slice
Edit: Haskell has this function, called intersperse
Using a generator:
function *intersperse(a, delim) {
let first = true;
for (const x of a) {
if (!first) yield delim;
first = false;
yield x;
}
}
console.log([...intersperse(array, '&')]);
Thanks to #Bergi for pointing out the useful generalization that the input could be any iterable.
If you don't like using generators, then
[].concat(...a.map(e => ['&', e])).slice(1)
A spread and explicit return in reducing function will make it more terse:
const intersperse = (arr, sep) => arr.reduce((a,v)=>[...a,v,sep],[]).slice(0,-1)
// intersperse([1,2,3], 'z')
// [1, "z", 2, "z", 3]
In ES6, you'd write a generator function that can produce an iterator which yields the input with the interspersed elements:
function* intersperse(iterable, separator) {
const iterator = iterable[Symbol.iterator]();
const first = iterator.next();
if (first.done) return;
else yield first.value;
for (const value of iterator) {
yield separator;
yield value;
}
}
console.log(Array.from(intersperse([1, 2, 3], "&")));
One straightforward approach could be like feeding the reduce function with an initial array in size one less than the double of our original array, filled with the character to be used for interspersing. Then mapping the elements of the original array at index i to 2*i in the initially fed target array would do the job perfectly..
In this approach i don't see (m)any redundant operations. Also since we are not modifying any of the array sizes after they are set, i wouldn't expect any background tasks to run for memory reallocation, optimization etc.
One other good part is using the standard array methods since they check all kinds of mismatch and whatnot.
This function returns a new array, in which the called upon array's items are interspersed with the provided argument.
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
Array.prototype.intersperse = function(s){
return this.reduce((p,c,i) => (p[2*i]=c,p), new Array(2*this.length-1).fill(s));
}
document.write("<pre>" + JSON.stringify(arr.intersperse("&")) + "</pre>");
Using reduce but without slice
var arr = ['a','b','c','d'];
var lastIndex = arr.length-1;
arr.reduce((res,x,index)=>{
res.push(x);
if(lastIndex !== index)
res.push('&');
return res;
},[]);
If you have Ramda in your dependencies or if willing to add it, there is intersperse method there.
From the docs:
Creates a new list with the separator interposed between elements.
Dispatches to the intersperse method of the second argument, if present.
R.intersperse('n', ['ba', 'a', 'a']); //=> ['ba', 'n', 'a', 'n', 'a']
Or you can check out the source for one of the ways to do it in your codebase. https://github.com/ramda/ramda/blob/v0.24.1/src/intersperse.js
You could use Array.from to create an array with the final size, and then use the callback argument to actually populate it:
const intersperse = (arr, sep) => Array.from(
{ length: Math.max(0, arr.length * 2 - 1) },
(_, i) => i % 2 ? sep : arr[i >> 1]
);
// Demo:
let res = intersperse([1, 2, 3], "&");
console.log(res);
ONE-LINER and FAST
const intersperse = (ar,s)=>[...Array(2*ar.length-1)].map((_,i)=>i%2?s:ar[i/2]);
console.log(intersperse([1, 2, 3], '&'));
javascript has a method join() and split()
var arr = ['a','b','c','d'];
arr = arr.join('&');
document.writeln(arr);
Output should be: a&b&c&d
now split again:
arr = arr.split("");
arr is now:
arr = ['a','&','b','&','c','&','d'];
if (!Array.prototype.intersperse) {
Object.defineProperty(Array.prototype, 'intersperse', {
value: function(something) {
if (this === null) {
throw new TypeError( 'Array.prototype.intersperse ' +
'called on null or undefined' );
}
var isFunc = (typeof something == 'function')
return this.concat.apply([],
this.map(function(e,i) {
return i ? [isFunc ? something(this[i-1]) : something, e] : [e] }.bind(this)))
}
});
}
you can also use the following:
var arr =['a', 'b', 'c', 'd'];
arr.forEach(function(element, index, array){
array.splice(2*index+1, 0, '&');
});
arr.pop();
My take:
const _ = require('lodash');
_.mixin({
intersperse(array, sep) {
return _(array)
.flatMap(x => [x, sep])
.take(2 * array.length - 1)
.value();
},
});
// _.intersperse(["a", "b", "c"], "-")
// > ["a", "-", "b", "-", "c"]
const arr = [1, 2, 3];
function intersperse(items, separator) {
const result = items.reduce(
(res, el) => [...res, el, separator], []);
result.pop();
return result;
}
console.log(intersperse(arr, '&'));
A few years later, here's a recursive generator solution. Enjoy!
const intersperse = function *([first, ...rest], delim){
yield first;
if(!rest.length){
return;
}
yield delim;
yield * intersperse(rest, delim);
};
console.log([...intersperse(array, '&')]);
export const intersperse = (array, insertSeparator) => {
if (!isArray(array)) {
throw new Error(`Wrong argument in intersperse function, expected array, got ${typeof array}`);
}
if (!isFunction(insertSeparator)) {
throw new Error(`Wrong argument in intersperse function, expected function, got ${typeof insertSeparator}`);
}
return flatMap(
array,
(item, index) => index > 0 ? [insertSeparator(item, index), item] : [item] // eslint-disable-line no-confusing-arrow
);
};
Here is also a version with reduce only:
const intersperse = (xs, s) => xs.reduce((acc, x) => acc ? [...acc, s, x] : [x], null)
const a = [1, 2, 3, 4, 5]
console.log(intersperse(a, 0))
// [1, 0, 2, 0, 3, 0, 4, 0, 5]
Updated for objects not using join method:
for (var i=0;i<arr.length;i++;) {
newarr.push(arr[i]);
if(i>0) {
newarr.push('&');
}
}
newarr should be:
newarr = ['a','&','b','&','c','&','d'];