I have multiple arrays in a main/parent array like this:
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
here are the array's for simpler reading:
[1, 17]
[1, 17]
[1, 17]
[2, 12]
[5, 9]
[2, 12]
[6, 2]
[2, 12]
[2, 12]
I want to select the arrays that are repeated 3 or more times (> 3) and assign it to a variable. So in this example, var repeatedArrays would be [1, 17] and [2, 12].
So this should be the final result:
console.log(repeatedArrays);
>>> [[1, 17], [2, 12]]
I found something similar here but it uses underscore.js and lodash.
How could I it with javascript or even jquery (if need be)?
Try this
array.filter(( r={}, a=>!(2-(r[a]=++r[a]|0)) ))
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
var r= array.filter(( r={}, a=>!(2-(r[a]=++r[a]|0)) ))
console.log(JSON.stringify(r));
Time complexity O(n) (one array pass by filter function). Inspired by Nitish answer.
Explanation
The (r={}, a=>...) will return last expression after comma (which is a=>...) (e.g. (5,6)==6). In r={} we set once temporary object where we will store unique keys. In filter function a=>... in a we have current array element . In r[a] JS implicity cast a to string (e.g 1,17). Then in !(2-(r[a]=++r[a]|0)) we increase counter of occurrence element a and return true (as filter function value) if element a occurred 3 times. If r[a] is undefined the ++r[a] returns NaN, and further NaN|0=0 (also number|0=number). The r[a]= initialise first counter value, if we omit it the ++ will only set NaN to r[a] which is non-incrementable (so we need to put zero at init). If we remove 2- as result we get input array without duplicates - or alternatively we can also get this by a=>!(r[a]=a in r). If we change 2- to 1- we get array with duplicates only.
UPDATE
Even shorter version based on #ken comment can be written (it should always work with arrays of numbers). The original longer version of #ken code is in snippet and shows how #ken uses in clever way second argument of .filter to avoid usage global variable r.
array.filter(a=>!(2-(this[a]=++this[a]|0)))
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
var r= array.filter(a=>!(2-(this[a]=++this[a]|0)), {})
console.log(JSON.stringify(r));
You could take a Map with stringified arrays and count, then filter by count and restore the arrays.
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = Array
.from(array.reduce(
(map, array) =>
(json => map.set(json, (map.get(json) || 0) + 1))
(JSON.stringify(array)),
new Map
))
.filter(([, count]) => count > 2)
.map(([json]) => JSON.parse(json));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Filter with a map at wanted count.
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = array.filter(
(map => a =>
(json =>
(count => map.set(json, count) && !(2 - count))
(1 + map.get(json) || 1)
)
(JSON.stringify(a))
)
(new Map)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Unique!
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]],
result = array.filter(
(s => a => (j => !s.has(j) && s.add(j))(JSON.stringify(a)))
(new Set)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Object.reduce, Object.entries for this like below
var array = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
let res = Object.entries(
array.reduce((o, d) => {
let key = d.join('-')
o[key] = (o[key] || 0) + 1
return o
}, {}))
.flatMap(([k, v]) => v > 2 ? [k.split('-').map(Number)] : [])
console.log(res)
OR may be just with Array.filters
var array = [[1, 17], [1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
let temp = {}
let res = array.filter(d => {
let key = d.join('-')
temp[key] = (temp[key] || 0) + 1
return temp[key] == 3
})
console.log(res)
For a different take, you can first sort your list, then loop through once and pull out the elements that meet your requirement. This will probably be faster than stringifying keys from the array even with the sort:
var arr = [[1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]]
arr.sort((a, b) => a[0] - b[0] || a[1] - b[1])
// define equal for array
const equal = (arr1, arr2) => arr1.every((n, j) => n === arr2[j])
let GROUP_SIZE = 3
first = 0, last = 1, res = []
while(last < arr.length){
if (equal(arr[first], arr[last])) last++
else {
if (last - first >= GROUP_SIZE) res.push(arr[first])
first = last
}
}
if (last - first >= GROUP_SIZE) res.push(arr[first])
console.log(res)
You could also do this with a single Array.reduce where you would only push to a result property if the length is equal to 3:
var array = [[1, 17], [1, 17], [1, 17], [1, 17], [2, 12], [5, 9], [2, 12], [6, 2], [2, 12]];
console.log(array.reduce((r,c) => {
let key = c.join('-')
r[key] = (r[key] || 0) + 1
r[key] == 3 ? r.result.push(c) : 0 // if we have a hit push to result
return r
}, { result: []}).result) // print the result property
ES6:
const repeatMap = {}
array.forEach(arr => {
const key = JSON.stringify(arr)
if (repeatMap[key]) {
repeatMap[key]++
} else {
repeatMap[key] = 1
}
})
const repeatedArrays = Object.keys(repeatMap)
.filter(key => repeatMap[key] >= 3)
.map(key => JSON.parse(key))
Related
I have two arrays:
const array = [
[1, 7, 'AAA'],
[2, 5, 'BBB'],
[3, 2, 'CCC'],
[4, 4, 'DDD'],
[4, 9, 'EEE'],
[4, 2, 'FFF'],
[5, 8, 'GGG'],
[6, 2, 'HHH']];
const names = [
[1, 'Joe'],
[2, 'Dave'],
[3, 'Mike'],
[4, 'Sandra'],
[5, 'Sue'],
[6, 'Mary']];
Based on the value in the first column, I want to sum the values in the array[1] and list the three-character letters. The result I'm trying to get is:
const names = [
[1, 'Joe',7,'AAA'],
[2, 'Dave',5,'BBB'],
[3, 'Mike',2,'CCC'],
[4, 'Sandra',15,'DDD, EEE, FFF'],
[5, 'Sue',8,'GGG'],
[6, 'Mary',2,'HHH']]
I'm not sure of the best approach, I'm fairly new to Javascript. What I've managed to do is get the right result when a value in array[0] isn't repeated, but I can't get a sum or list to work.
const counter = (array,value) => array.filter((v) => (v === value)).length;
const arrayCol = (array,value) => array.map(v => v[value]);
const sum = (prevVal, curVal) => prevVal + curVal;
names.forEach ((p,e) => {
array.forEach ((v,x) => (counter(arrayCol(array,0),v[0])===1) ?
(v[0]===p[0]) && names[e].push(v[1],v[2]) :
(v[0]===p[0]) && names[e].push(array.reduce(sum,0)) );
});
console.log(names);
I'm sure the answer has to do with map or filter but not sure how... any pointers appreciated. Thank you
EDIT: All three answers below (from Michael Haddad, Nina Scholz, and testing_22) work and are interesting.
You can use a combination of map and reduce, as in:
const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'],[4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'],[2, 'Dave'],[3, 'Mike'],[4, 'Sandra'],[5, 'Sue'],[6, 'Mary']];
const result = names.map(([id, name]) => {
let vals = [];
let sum = array.reduce((acc, [idx, number, XXX]) =>
(idx === id ? (vals.push(XXX), number) : 0) + acc, 0);
return [
id,
name,
sum,
vals.join(", ")
]
})
console.log(result)
You could collect all data for each group and then map the result in order of the names array.
const
array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']],
names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']],
groups = array.reduce((r, [id, value, code]) => {
r[id] ??= [0, ''];
r[id][0] += value;
r[id][1] += (r[id][1] && ', ') + code;
return r;
}, {}),
result = names.map(a => [...a, ...groups[a[0]]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A basic approach could be:
const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']];
let result = [];
for (let name of names) {
let newValue = [...name, 0];
let matchingItems = array.filter(i => i[0] === name[0]);
let strings = []; // for lack of a better name...
for (let item of matchingItems) {
newValue[2] += item[1];
strings.push(item[2]);
}
newValue.push(strings.join(", "));
result.push(newValue);
}
console.log(result);
You could also implement the joining logic yourself (I actually prefer this version for readability reasons):
const array = [[1, 7, 'AAA'], [2, 5, 'BBB'], [3, 2, 'CCC'], [4, 4, 'DDD'], [4, 9, 'EEE'], [4, 2, 'FFF'], [5, 8, 'GGG'], [6, 2, 'HHH']];
const names = [[1, 'Joe'], [2, 'Dave'], [3, 'Mike'], [4, 'Sandra'], [5, 'Sue'], [6, 'Mary']];
let result = [];
for (let name of names) {
let newValue = [...name, 0, ""];
let matchingItems = array.filter(i => i[0] === name[0]);
for (let item of matchingItems) {
newValue[2] += item[1];
newValue[3] += newValue[3] === "" ? item[2] : `, ${item[2]}`;
}
result.push(newValue);
}
console.log(result);
in my project I have to turn a bunch of coordinates to some meaningful two-dimensional array but I really don't know how to do it. Can somebody help?
To explain what I exactly want, let me give an example:
Let's suppose that I have these 2 arrays(the reason that I started from one is because 0 and the last element of my rows are borders):
[[1, 1], [1, 2], [1, 4], [1, 5], [1, 8], [1, 9], [1, 10],[2, 1], [2, 2], [2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [2, 10]]
Let the value inside these coordinates be like [row,col]. And let's say I wan't to match them to generate some sort of two-dimensional array and each of the elements should contain the value '#'. However, for example;
[1, 2], [1, 4]
[2, 2], [2, 4]
If there's a coordinate missing between two of these elements, they should be separated, meaning that there should be two different two-dimensional arrays, being split from that coordinate. In this case, the result should be;
// First two-dimensional array
const firstArray = [
['#','#'],
['#','#']
]
const secondArray = [
['#','#','','','#','#','#'],
['#','#','#','#','#','','#'],
]
In the second array, there are some '' values, but that is because the there are some coordinates missing(for [1, 5] and [1, 8], [1,6] and [1,7] are missing). So that should be considered too.
If you didn't understand please comment under the question me so that I can explain it to you.
How can I come up with the functionality that I'm looking for?
You can accomplish both steps with a single Array#reduce() call by using the coordinates themselves to place each [row, col] in its relevant place in the matrix.
Here using an OR short circuit for assigning new sub-arrays, with a commented out replacement using the logical nullish assignment operator (??=), and the comma operator for shorthand return in the arrow function.
const coords = [[1, 1], [1, 2], [1, 4], [1, 5], [1, 8], [1, 9], [1, 10], [2, 1], [2, 2], [2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [2, 10]];
const matrix = coords.reduce((acc, [row, col]) => (
// using OR short circuit for compatibility
(acc[row - 1] || (acc[row - 1] = []))[col - 1] = [row, col], acc
// using logical nullish assignment operator (??=)
//(acc[row - 1] ??= [])[col - 1] = [row, col], _matrix
), [])
// logging
for (const row of matrix) {
console.log(`[[${row.join('], [')}]]`)
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
const input = [[1, 1], [1, 2], [1, 4], [1, 5], [1, 8], [1, 9], [1, 10],[2, 1], [2, 2], [2, 4], [2, 5], [2, 6], [2, 7], [2, 8], [2, 10]]
const result = input.reduce((acc, [x, y]) => {
acc[x - 1] ??= []
const previousY = acc[x - 1][acc[x-1].length - 1];
if (previousY) {
const delta = y - previousY;
if (delta > 1) acc[x-1].push(...Array.from({length: delta - 1}));
}
acc[x-1].push(y);
return acc
}, [])
console.log('1234567890')
console.log(
result.map(row =>
row.map(coor => coor ? '#' : ' ').join('')
).join('\n'))
I'm currently building a tic tac toe in vanilla javascript. However the game is 'sort of' done but I'm trying to add levels of difficulty. So basically the thing I want to do is , on every player move , to get the the closest possible winning combination based on his moves and place computer's mark into the missing winning's combinations place.
Let's say I have multidimensional array with the winning combinations
winningCombinations: [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 4, 8],
[0, 3, 6],
[1, 4, 7],
[2, 4, 6],
[2, 5, 8]
]
And the player X starts his moves. So his first move is 0, so saving player's current combination in array. So in first move the current comb is
currentPlayerCombintation: [0]
so I want to return [0,1,2], [0,4,8] and [0,3,6] from the winning combination's array.
However the player makes his second move , so he target's 4 so
currentPlayerCombination: [0,4]
and now I want to return the closest possible winning combination which is [0,4,8].
I've tried a lot of things including every() , some() , filter() but could not achieve the thing I want.
I've tried sort of
for(let i = 0; i < this.currentPlayerCombination.length ; i++) {
this.winningCombinations.some((arr) => {
if(arr.includes(this.currentPlayerCombination[i])) {
console.log(arr);
}
});
}
But this didnt work as expected :(
You could take a Set and map the count of the matching items, get the max count and filter the array.
function getWinningPositions(pos) {
var posS = new Set(pos),
temp = winningCombinations.map(a => [a, a.reduce((c, v) => c + posS.has(v), 0)]),
max = Math.max(...temp.map(({ 1: c }) => c))
return temp
.filter(({ 1: c }) => c === max)
.map(([a]) => a);
}
var winningCombinations = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 4, 8], [0, 3, 6], [1, 4, 7], [2, 4, 6], [2, 5, 8]];
console.log(getWinningPositions([0]).map(a => a.join(' ')));
console.log(getWinningPositions([0, 4]).map(a => a.join(' ')));
console.log(getWinningPositions([0, 4, 5]).map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First map the winningCombinations to an array of arrays whose numbers are only the numbers that have not been picked yet. Then, find the lowest length of those arrays, and you can identify the original winningCombinations which are closest to the currentPlayerCombination:
const winningCombinations = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 4, 8],
[0, 3, 6],
[1, 4, 7],
[2, 4, 6],
[2, 5, 8]
];
const currentPlayerCombination = [0, 4];
// eg: get [[1, 2], [3, 5,], [6, 7, 8], [8], ...]
const winningCombsWithoutCurrent = winningCombinations.map(arr => (
arr.filter(num => !currentPlayerCombination.includes(num))
));
// eg: here, lowestLength should be 1, because [8] has a length of 1
const lowestLength = winningCombsWithoutCurrent.reduce((a, { length }) => Math.min(a, length), 3);
const combosWithLowestLength = winningCombsWithoutCurrent
.reduce((a, { length }, i) => {
if (length === lowestLength) {
a.push(winningCombinations[i]);
}
return a;
}, []);
console.log(combosWithLowestLength);
I am trying to replicate an array of arrays and then modify the same element of each sub-array.
The following code is used to replicate the initial array of arrays:
const array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const n = 2; // replicate twice
let replicated_arrays = [];
for (let i = 0; i < n; i++) {
replicated_arrays.push(array);
}
replicated_arrays = [].concat.apply([], replicated_arrays); // flatten to make one array of arrays
The following code is then used to modify the second element of each array:
const init = 10;
replicated_arrays.forEach(function(element, index, entireArray) {
entireArray[index][1] = init + index;
});
The desired output is:
[[1, 10, 3], [4, 11, 6], [7, 12, 9], [1, 13, 3], [4, 14, 6], [7, 15, 9]]
However, the above code produces the following:
[[1, 13, 3], [4, 14, 6], [7, 15, 9], [1, 13, 3], [4, 14, 6], [7, 15, 9]]
The forEach updates properly if the replicated array is created manually:
let replicated_arrays = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9]];
I therefore suspect it has something to do with the push method creating a reference to both instances of the initial array such that the final set of values (13, 14, and 15) are applied to both instances.
As an alternative to the push method, I tried the map method (e.g., in accordance with Duplicate an array an arbitrary number of times (javascript)), but it produced the same result.
Any insight or suggestions as to what is going on or how to make it work properly would be appreciated.
You need to take copies of the inner arrays, because you need to lose the same object reference.
For pushing, you could spread the array and omit flattening later.
const array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const n = 2; // replicate twice
let replicated_arrays = [];
for (let i = 0; i < n; i++) {
replicated_arrays.push(...array.map(a => a.slice())); // spread array
}
// no need for this! replicated_arrays = [].concat.apply([], replicated_arrays);
const init = 10;
replicated_arrays.forEach(function(element, index) {
element[1] = init + index; // access element directly without taking the outer array
});
console.log(replicated_arrays);
Instead of concat use reduce method will keep same reference.
const array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
const n = 2; // replicate twice
let replicated_arrays = [];
for (let i = 0; i < n; i++) {
replicated_arrays.push(array);
}
replicated_arrays = replicated_arrays.reduce(function(a, b){
return a.concat(b);
}, []);
replicated_arrays.forEach((_ae,i) => {
_ae[1] = 10 + i;
})
console.log(replicated_arrays);
output: [[1, 10, 3], [4, 11, 6], [7, 12, 9], [1, 13, 3], [4, 14, 6], [7, 15, 9]]
I have two arrays of array in Javascript like
var array1 = [[10, 2], [11, 4], [12, 30], [13, 17], [14, 28]];
var array2 = [[8, 13], [9, 19], [10, 6], [11, 7], [12, 1]];
I want to get the set of arrays from array1 which match the first element of each array of the array2
in my example case both array1 and array2 have array with first element as 10 11 and 12, so it should return
[[10, 2], [11, 4], [12, 30]];
is there any easy and efficient way using pure javscript or lodash, underscor framework or something like that. Without iterate over and match one by one of this two array ?
In ES6, you could use Set.
var array1 = [[10, 2], [11, 4], [12, 30], [13, 17], [14, 28]],
array2 = [[8, 13], [9, 19], [10, 6], [11, 7], [12, 1]],
set = new Set(array2.map(a => a[0])),
result = array1.filter(a => set.has(a[0]));
console.log(result);
Version with an object as hash table
var array1 = [[10, 2], [11, 4], [12, 30], [13, 17], [14, 28]],
array2 = [[8, 13], [9, 19], [10, 6], [11, 7], [12, 1]],
result = array1.filter(function (a) {
return this[a[0]];
}, array2.reduce(function (r, a) {
r[a[0]] = true;
return r;
}, Object.create(null)));
console.log(result);
You can use lodash _.intersectWith function in order to solve this problem in an inline.
_.intersectionWith(array1, array2, function(a, b) {
return a[0] === b[0];
});
I don't know about performance cos I haven't had the chance to have a look at the source code of this function. Anyway, I like it for its simplicity. Here's the fiddle in case you want to check it out.
If you can make use of Set, then you can compute the a set of numbers to look for first and use .filter to only get the arrays whose first element is in that set:
var haystack = new Set(array2.map(x => x[0]));
var newArray = array1.filter(x => haystack.has(x[0]));
Of course you can also use the lodash or underscore versions of .map and .filter.
Alternatives to using Set would be:
Create an array of numbers instead and use indexOf to test existence. That will scale linearly with the number of elements:
var haystack = array2.map(x => x[0]);
var newArray = array1.filter(x => haystack.indexOf(x[0]) > -1);
Create an object with number -> true entries to test existence with in, hasOwnProperty or just object access:
var haystack = array2.reduce((obj, x) => (obj[x[0]] = true, obj), {});
var newArray = array1.filter(x => haystack[x[0]]);
Which one performs better depends on the number of elements you have and the environment the code is running in.
You can do this with filter() and find()
var array1 = [
[10, 2],
[11, 4],
[12, 30],
[13, 17],
[14, 28]
];
var array2 = [
[8, 13],
[9, 19],
[10, 6],
[11, 7],
[12, 1]
];
var result = array1.filter(function(ar) {
return array2.find(function(e) {
return e[0] == ar[0]
})
})
console.log(result)
I would do this with a Map anf filter combo in ES6 as follows;
var array1 = [[10, 2], [11, 4], [12, 30], [13, 17], [14, 28]],
array2 = [[8, 13], [9, 19], [10, 6], [11, 7], [12, 1]],
m = new Map(array2),
array3 = array1.filter(a => m.has(a[0]));
console.log(array3);
If you need backwards compatibility other answers are pretty good.