Related
Is there a way to filter an array so that only the unique (distinct) items are left? (not to be confused with removing duplicated items)
I'm looking for something like this:
arrayDistinct([1, 2, 1, 3, 2, 4])
//=> [3, 4]
Here's a program that doesn't iterate through the input multiple times or create intermediate state (like counts) to arrive at the solution -
// distinct1.js
function distinct (values = [])
{ const r = new Set(values) // result
const s = new Set // seen
for (const v of values)
if (s.has(v))
r.delete(v)
else
s.add(v)
return Array.from(r)
}
console.log(distinct([ 1, 2, 3, 4, 1, 2, 5 ]))
// [ 3, 4, 5 ]
Technically, the Set constructor used above does iterate through the input to create the initial set. We can further optimise by starting with empty sets -
// distinct2.js
function distinct (values = [])
{ const r = new Set // result
const s = new Set // seen
for (const v of values)
if (s.has(v))
continue
else if (r.has(v))
(r.delete(v), s.add(v))
else
r.add(v)
return Array.from(r)
}
console.log(distinct([ 1, 2, 3, 4, 1, 2, 5 ]))
// [ 3, 4, 5 ]
To see the big effect small changes like this can make, let's consider a significantly large input. We will generate one million 7-digit numbers and find the distinct values using various approaches -
const rand = n =>
Math.floor(Math.random() * n)
const inrange = (min, max) =>
rand(max - min + 1) + min
const r =
Array.from(Array(1e6), _ => inrange(1e6, 1e7-1))
console.log(r)
[
5114931, 9106145, 8460777, 4453008, 2388497, 6013688, 2552937,
1804171, 1264251, 4848368, 9659369, 8420530, 7150842, 3490019,
9003395, 7645576, 8968872, 3617948, 6780357, 5715769, 7911037,
3293079, 6173966, 8016471, 3462426, 8048448, 5061586, 8478988,
1733908, 1007848, 7027309, 7210176, 8598863, 8341131, 4586641,
5121335, 7157381, 8835726, 5395867, 6145745, 5058377, 5817408,
6153398, 6514711, 9297841, 7851503, 1678386, 2833373, 9093901,
6002570, 4512648, 1586990, 9984143, 8618601, 5609095, 8971964,
9845723, 7884387, 8635795, 3105128, 2764544, 2213559, 5788547,
8729079, 2176326, 1339145, 8278925, 5598964, 5712291, 2302033,
3744467, 4555008, 3301943, 5993299, 6499550, 3125444, 5763790,
6476676, 7920890, 9299943, 5129401, 7414350, 6469143, 2246004,
6659545, 9269620, 8333459, 2468048, 6420650, 3330098, 7722297,
6082093, 8883388, 6240800, 8976961, 9192095, 4827011, 4202172,
9476644, 3786121,
... 999900 more items
]
Using a cursory benchmark program -
function benchmark (f, ...args)
{ console.time(f.name)
const r = f(...args)
console.timeEnd(f.name)
return r
}
We will measure the two solutions in this answer and compare them against another answer in this thread -
benchmark(distinct1, r) // first solution in this answer
benchmark(distinct2, r) // second solution in this answer
benchmark(distinct3, r) // solution from #hev1
Results -
distinct1: 406.695ms
distinct2: 238.350ms
distinct3: 899.650ms
The revised program runs about twice as fast as the first program, because it uses half the amount of iteration. Programs like #hev1's which use multiple iterations and/or allocate additional memory for each element result in a longer runtime.
Expand the program below to verify the results in your own browser -
// #user633183-1
function distinct1 (values = [])
{ const r = new Set(values)
const s = new Set
for (const v of values)
if (s.has(v))
r.delete(v)
else
s.add(v)
return Array.from(r)
}
// #user633183-2
function distinct2 (values = [])
{ const r = new Set
const s = new Set
for (const v of values)
if (s.has(v))
continue
else if (r.has(v))
(r.delete(v), s.add(v))
else
r.add(v)
return Array.from(r)
}
// #hev1
const distinct3 = arr => {
const freq = arr.reduce((acc,curr)=>(acc[curr]=(acc[curr]||0)+1,acc), {});
return arr.filter(x => freq[x] === 1);
}
function benchmark (f, ...args)
{ console.time(f.name)
const r = f(...args)
console.timeEnd(f.name)
return r
}
const rand = n =>
Math.floor(Math.random() * n)
const inrange = (min, max) =>
rand(max - min + 1) + min
const r =
Array.from(Array(1e6), _ => inrange(1e6, 1e7-1))
benchmark(distinct1, r) // 406.695ms
benchmark(distinct2, r) // 238.350ms
benchmark(distinct3, r) // 899.650ms
For enhanced performance, you can first iterate over the array once to create an object representing the frequency of each element and then filter the array to get all the elements with a frequency of 1. This solution runs in O(n) time and space.
const arrayDistinct = arr => {
const freq = arr.reduce((acc,curr)=>(acc[curr]=(acc[curr]||0)+1,acc), {});
return arr.filter(x => freq[x] === 1);
}
console.log(arrayDistinct([1, 2, 1, 3, 2, 4]));
For arrays that contain elements of different types that have the same string representation, we can use a Map instead of an object to store frequency.
const arrayDistinct = arr => {
const freq = arr.reduce((map,curr)=>(map.set(curr, (map.get(curr)||0)+1),map), new Map);
return arr.filter(x => freq.get(x) === 1);
}
console.log(arrayDistinct([1, 2, 1, 3, 2, 4, '1']));
const og = [1, 2, 1, 3, 2, 4];
const ogfrequency = {};
/*compute frequency of each elem*/
for (const i of og) {
ogfrequency[i] = ogfrequency[i] ? ++ogfrequency[i] : 1;
}
/*pass or fail the test of filter based on frequencies collected previously*/
const answer = og.filter((e) => ogfrequency[e] === 1);
console.info("answer::", answer);
This can be implemented like so:
const arrayDistinct = array => array.filter(value => array.indexOf(value) === array.lastIndexOf(value))
arrayDistinct([1, 2, 1, 3, 2, 4])
//=> [3, 4]
Something like
const values = [1, 2, 1, 3, 2, 4];
const results = values.filter(value =>
values.filter(v => v === value).length === 1
);
console.log(results);
I have an arrays:
a = [1, 1, 1, 1]
Which should be merged with an array of arrays:
b = [[0],[0],[0],[0]]
To form a third:
c = [[0,1],[0,1],[0,1],[0,1]]
One way I have thought would be to run a .forEach on a and concatenate to each object of b. however, I'd like this so the list may become longer with, for example d=[2,2,2,2] creating e=[[0,1,2],[0,1,2],[0,1,2],[0,1,2]].
a = [1, 1, 1, 1];
b = [[0],[0],[0],[0]];
a.forEach((number,index) => {
b[index] = b[index].concat(number)
});
console.log(b);
Thanks!
The concat method does not append, it creates a new array that you need to something with.
You want map, not forEach - and you mixed up a and b in whose elements need to be wrapped in an array literal to be concatenated:
var a = [1, 1, 1, 1];
var b = [[0],[0],[0],[0]];
var c = a.map((number, index) => {
return b[index].concat([number]);
});
// or the other way round:
var c = b.map((arr, index) => {
return arr.concat([a[index]]);
});
You could use reduce() method with forEach() and create new array.
let a = [1, 1, 1, 1], b = [[0],[0],[0],[0]], d=[2,2,2,2,2]
const result = [b, a, d].reduce((r, a) => {
a.forEach((e, i) => r[i] = (r[i] || []).concat(e))
return r;
}, [])
console.log(result)
I have an function that takes an array as an input.
How can I modify it to work with variable arguments as well as arrays.
For example I want arrSum(1,2,3) === arrSum([1,2,3]) to return true i.e. both should return 6
const arrSum = arr => arr.reduce((a,b) => a+b,0)
console.log(arrSum([1,2,3]))
You can use spread syntax with concat. In first case you will get array with another array inside and in second case just an array of arguments but with [].concat(...arr) you will transform that to array of arguments for both cases.
const arrSum = (...arr) => [].concat(...arr).reduce((a, b) => a + b, 0)
console.log(arrSum([1, 2, 3]))
console.log(arrSum(1, 2, 3))
console.log(arrSum([1, 2, 3], [4], 5))
You can solve this problems in many way , one way if you're using arrow function would be like this using rest operator
const arrSum = (...arr) => {
// if we have just on param which is array
if (arr.length === 1) {
return arr[0].reduce((a, b) => a + b, 0);
}
return arr.reduce((a, b) => a + b, 0);
};
console.log(arrSum(1, 2, 3)); // 6
console.log(arrSum([1, 2, 3])); // 6
in the code above we use rest operator learn more rest_parameters MDN
also we can use normal functions using the variable arguments inside functions like this
function arrSum(args) {
if (arguments.length === 1) {
return arguments[0].reduce((a, b) => a + b, 0);
} else {
return [].reduce.call(arguments, (a, b) => a + b, 0);
}
}
console.log(arrSum(1, 2, 3));
console.log(arrSum([1, 2, 3]));
learn more about
arguments Object MDN
US/docs/Web/JavaScript/Reference/Functions/arguments
so the same problem can be solved many ways choose what works best for you .
A solution example:
function arrSum( nums ) {
var n = 0;
if ( typeof( nums ) != 'object' )
return arrSum( arguments );
for ( var i = 0; i < nums.length ; i++ )
n += nums[ i ];
return n;
}
arrSum( 1, 2, 3 );
arrSum( [ 1, 2, 3 ] );
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'];
I want to sort a multidimensional array using the first row,
I want that the second and the third row get the same sort order of the first row
Here is an example of array :
var arr =
[
[1,3,5,6,0,2],
[1,2,7,4,0,6],
[1,2,3,4,5,6]
]
the result that I want =
[
[0,1,2,3,5,6],
[0,1,6,2,7,4],
[5,1,6,2,3,4]
]
I tried the following function but it doesnt work as I want
arr = arr.sort(function(a,b) {
return a[0] > b[0];
});
What's wrong with my function ?
Thank you
I think I understand what you're looking for:
a = [
[1,3,5,6,0,2],
[1,2,7,4,0,6],
[1,2,3,4,5,6]
]
transpose = m => m[0].map((_, c) => m.map(r => r[c]));
z = transpose(transpose(a).sort((x, y) => x[0] - y[0]))
z.map(r =>
document.write('<pre>'+JSON.stringify(r) + '</pre>'));
In ES5
transpose = function(m) {
return m[0].map(function (_, c) {
return m.map(function (r) {
return r[c]
})
})
};
z = transpose(transpose(a).sort(function (x, y) { return x[0] - y[0] }));
UPD: the transpose trick is kinda "smart", but hardly efficient, if your matrices are big, here's a faster way:
a = [
[1,3,5,6,0,2],
[1,2,7,4,0,6],
[1,2,3,4,5,6]
]
t = a[0].map((e, i) => [e, i]).sort((x, y) => x[0] - y[0]);
z = a.map(r => t.map(x => r[x[1]]));
z.map(r =>
document.write('<pre>'+JSON.stringify(r) + '</pre>'));
Basically, sort the first row and remember indexes, then for each row, pick values by an index.
A bit more verbose than previous answer in ES5
var arr = [
[1, 3, 5, 6, 0, 2],
[1, 2, 7, 4, 0, 6],
[1, 2, 3, 4, 5, 6]
]
var sortIndices = arr[0].slice().sort(function(a, b) {
return a - b;
}).map(function(el) {
return arr[0].indexOf(el)
});
arr = arr.reduce(function(a, c) {
var sorted = sortIndices.map(function(i) {
return c[i];
});
a.push(sorted);
return a;
}, []);
document.getElementById('pre').innerHTML = JSON.stringify(arr)
<pre id="pre"></pre>