Deduplicate array of arrays in javascript - javascript

I want to deduplicate an array of arrays. A duplicate array is one that matches a subset of element indices. In this case, say, index [1] and index [3].
const unDeduplicated = [
  [ 11, 12, 13, 14, 15, ],
  [ 21, 22, 23, 24, 25, ],
  [ 31, 88, 33, 99, 35, ], // duplicate in indices: 1, 3 with row index 4
  [ 41, 42, 43, 44, 45, ],
  [ 51, 88, 53, 99, 55, ], // duplicate in indices: 1, 3 // delete this row from result
];
const deduplicated = getDeduplicated( unDeduplicated, [ 1, 3, ], );
console.log( deduplicated );
// expected result:
// [
//   [ 11, 12, 13, 14, 15, ],
//   [ 21, 22, 23, 24, 25, ],
//   [ 31, 88, 33, 99, 35, ],
//   [ 41, 42, 43, 44, 45, ],
// // this row was omitted from result because it was duplicated at indices 1 and 3 with row index 2
// ]
What is a function getDeduplicated() that can give me such a result?
I have tried the below function but it's just a start. And it isn't close to giving me the desired result. But it gives an idea of what I'm trying to do.
/**
* Returns deduplicated array as a data grid ([][] -> 2D array)
* #param { [][] } unDedupedDataGrid The original data grid to be deduplicated to include only unque rows as defined by the indices2compare.
* #param { Number[] } indices2compare An array of indices to compare for each array element.
* If every element at each index for a given row is duplicated elsewhere in the array,
* then the array element is considered a duplicate
* #returns { [][] }
*/
const getDeduplicated = ( unDedupedDataGrid, indices2compare, ) => {
let deduped = [];
unDedupedDataGrid.forEach( row => {
const matchedArray = a.filter( row => row[1] === 88 && row[3] === 99 );
const matchedArrayLength = matchedArray.length;
if( matchedArrayLength ) return;
deduped.push( row, );
});
}
I've researched some lodash functions that might help like _.filter and _.some but so far, I can't seem to find a structure that produces the desired result.

You can create Set out of the values in columns as you iterate over rows. You could choose to create sets only for the designated columns, e.g. 1 and 3 in your case. Then when iterating over each row you check if any of the designated columns in that row has such a value that is already in the corresponding set, and if it does you discard that row.
(On phone, cannot type actual code. And I guess code is pretty straight forward too)

It's probably not the most efficient algorithm, but I'd do something like
function getDeduplicated(unDeduplicated, idxs) {
const result = [];
const used = new Set();
unDeduplicated.forEach(arr => {
const vals = idxs.map(i => arr[i]).join();
if (!used.has(vals)) {
result.push(arr);
used.add(vals);
}
});
return result;
}

Idk if i understand good what you want to do but here is what i've done
list = [
[ 11, 12, 13, 14, 15, ],
[ 21, 22, 23, 24, 25, ],
[ 21, 58, 49, 57, 28, ],
[ 31, 88, 33, 88, 35, ],
[ 41, 42, 43, 44, 45, ],
[ 51, 88, 53, 88, 55, ],
[ 41, 77, 16, 29, 37, ],
];
el_list = [] // Auxiliar to save all unique numbers
res_list = list.reduce(
(_list, row) => {
// console.log(_list)
this_rows_el = [] // Auxiliar to save this row's elements
_list.push(row.reduce(
(keep_row, el) => {
// console.log(keep_row, this_rows_el, el)
if(keep_row && el_list.indexOf(el)==-1 ){
el_list.push(el)
this_rows_el.push(el)
return true
}else if(this_rows_el.indexOf(el)!=-1) return true // Bypass repeated elements in this row
else return false
}, true) ? row : null) // To get only duplicated rows (...) ? null : row )
return _list
}, []
)
console.log(res_list)

This is fairly concise. It uses nested filters. It will also work for any number of duplicates, keeping only the first one.
init = [
[ 11, 12, 13, 14, 15],
[ 21, 22, 23, 24, 25],
[ 31, 88, 33, 99, 35],
[ 41, 42, 43, 44, 45],
[ 51, 88, 53, 99, 55],
];
var deDuplicate = function(array, indices){
var res = array.filter(
(elem) => !array.some(
(el) =>
array.indexOf(el) < array.indexOf(elem) && //check that we don't discard the first dupe
el.filter((i) => indices.includes(el.indexOf(i))).every((l,index) => l === elem.filter((j) => indices.includes(elem.indexOf(j)))[index])
//check if the requested indexes are the same.
// Made a bit nasty by the fact that you can't compare arrays with ===
)
);
return(res);
}
console.log(deDuplicate(init,[1,3]));

Not the most efficient but this will remove dups of more than one duplicate array
const unDeduplicated = [ [ 11, 12, 13, 14, 15, ], [ 21, 22, 23, 24, 25, ], [ 31, 88, 33, 99, 35, ], [ 41, 33, 43, 44, 45, ], [ 51, 88, 53, 99, 55, ]]
const unDeduplicated1 = [
[ 11, 12, 13, 14, 15, ],
[ 21, 22, 23, 24, 25, ],// duplicate in indices: 1, 3 with row index 3
[ 31, 88, 33, 99, 35, ], // duplicate in indices: 1, 3 with row index 4
[ 21, 22, 43, 24, 45, ],// duplicate in indices: 1, 3 // delete this
[ 51, 88, 53, 99, 55, ], // duplicate in indices: 1, 3 // delete this row from result
];
function getDeduplicated(arr, arind) {
for (let i = 0; i < arr.length; i++) {
for (let j = 1 + i; j < arr.length; j++) {
if (arr[j].includes(arr[i][arind[0]]) && arr[j].includes(arr[i][arind[1]])) {
arr.splice(j, 1)
i--
} else continue
}
}
return arr
}
const deduplicated = getDeduplicated(unDeduplicated, [1, 3]);
const deduplicated2 = getDeduplicated(unDeduplicated1, [1, 3]);
console.log(deduplicated)
console.log("#####################")
console.log(deduplicated2)

Related

How to determine sequential numbers in an array

When an array is prepared as follows.
let array = [7, 8, 9, 14, 15, 16, 33, 34, 35, 36, 90, 91, 92, 93, 94];
I would like to determine the sequential number of each interval of the array and find the value that is the median of each interval, as shown below. (If the length of the interval is even, the previous value closer to the center is substituted.) I need this method to create a graph, but I can't think of a solution, so I am asking for help. Thank you in advance for your help.
[8, 15, 34, 92]
// {7, 8, 9}, {14, 15, 16}, {33, 34, 35, 36}, {90, 91, 92, 93, 94}
The grouping of sequential numbers could be done as follows.
var array = [7, 8, 9, 14, 15, 16, 33, 34, 35, 36, 90, 91, 92, 93, 94];
var result = [], temp = [], difference;
for (var i = 0; i < array.length; i += 1) {
if (difference !== (array[i] - i)) {
if (difference !== undefined) {
result.push(temp);
temp = [];
}
difference = array[i] - i;
}
temp.push(array[i]);
}
if (temp.length) {
result.push(temp);
}
console.log(result);
// [ [ 7, 8, 9 ], [ 14, 15, 16 ], [ 33, 34, 35, 36 ], [ 90, 91, 92, 93, 94 ] ]
The length of the array, the length of the intervals, and the number of intervals are not defined, and dynamic behavior is required.
Update
You may want to use this line:
result.map(e => Math.floor(e.reduce((a,b) => a+b, 0) / e.length))
to get something like this:
const array = [7, 8, 9, 14, 15, 16, 33, 34, 35, 36, 90, 91, 92, 93, 94];
let result = [], temp = [], difference;
for (let i = 0; i < array.length; i += 1) {
if (difference !== (array[i] - i)) {
if (difference !== undefined) {
result.push(temp);
temp = [];
}
difference = array[i] - i;
}
temp.push(array[i]);
}
temp.length && result.push(temp);
console.log(
result.map(e => e[Math.floor(e.length/2) - ( e.length % 2 === 0 ? 1 : 0)])
)
result.map(e => ...) // loop over multidimensional array including all values
e[Math.floor(e.length/2) - (e.length % 2 === 0 ? 1 : 0)] // determine center

How can I make a new array from a event emitter ( gives bytes array), where new arrays first element will be '10' and last '13'?

I have incoming bytes of arrays once a time. It kind of looks like this
[10,12,34,58,62]
[83,33,23,44,13]
[10,12,34,58,62]
[34, 77,54,23,87]
[83,33,23,44,13]
[10,12,34,58,62]
[83,33,23,44,66]
[13]
…continues.
So, it renders each array every time. I needed them to be rendered like
[10,12,34,58,62,83,33,23,44,13]
[10,12,34,58,62,34,77,54,23,87,83,33,23,44,13]
[10,12,34,58,62,83,33,23,44,66,13].
The first element will be 10 and the last 13.
Can anyone help me with the code? Thanks in advance.
// #param {accumulator} is array of array of numbers
// #param {chunk} is the array of numbers
// #return {accumulator}
const process_bytes = (accumulator, chunk) => {
if (accumulator && accumulator.length) {
if (chunk && chunk.length) {
if (chunk[0] === 10) {
return [...accumulator, chunk];
} else {
return [...accumulator.slice(0, accumulator.length - 1), [...accumulator[accumulator.length - 1], ...chunk]];
}
}
}
return chunk && chunk.length && [chunk] || [];
};
Assumption
The following code follows the provided input data and the expected output in the OP
RUN THE CODE
const process_bytes = (accumulator, chunk) => {
if (accumulator && accumulator.length) {
if (chunk && chunk.length) {
if (chunk[0] === 10) {
return [...accumulator, chunk];
} else {
return [...accumulator.slice(0, accumulator.length - 1), [...accumulator[accumulator.length - 1], ...chunk]];
}
}
}
return chunk && chunk.length && [chunk] || [];
};
const testData = [
[10, 12, 34, 58, 62],
[83, 33, 23, 44, 13],
[10, 12, 34, 58, 62],
[34, 77, 54, 23, 87],
[83, 33, 23, 44, 13],
[10, 12, 34, 58, 62],
[83, 33, 23, 44, 66]
];
let processed_bytes = [];
testData.forEach(x => {
processed_bytes = process_bytes(processed_bytes, x);
});
console.log(processed_bytes);
WYSIWYG => WHAT YOU SHOW IS WHAT YOU GET
If your incoming bytes is 2d array then try this.
let arr = [
[10, 12, 34, 58, 62],
[83, 33, 23, 44, 13],
[10, 12, 34, 58, 62],
[34, 77, 54, 23, 87],
[83, 33, 23, 44, 13],
[10, 12, 34, 58, 62],
[83, 33, 23, 44, 66],
[13]
]
let openedArr = []
for (let value of arr) {
openedArr = [...openedArr, ...value]
}
let resultArr = []
let child = []
for (let value of openedArr) {
child.push(value)
if (value == 13) {
resultArr.push(child)
child = []
}
}
console.log(resultArr)
Explaination
Make an new array by spreading all incoming array.
Create two arrays resultArr and Child Array
Push every array elements to Child array
If there is 13, then push child array to resultArr, and empty child array.

Why is my state array variable being treated as "pass-by-reference" and mutated when I use it as a parameter to another function?

I have a simple component here where I set a state variable called input to the value of an array of numbers. Then, within useEffect I call a function that randomizes the initial state array, and puts the results in a new state variable called output.
I need my input array to stay in the same order. However, it is being mutated when I call the shuffleArray function.
I thought there was no way to alter the value held by a variable passed as a parameter, which would be possible if JavaScript supported passing by reference.
const App = () => {
const [input, setInput] = React.useState([90, 32, 28, 8, 21, 24, 64, 92, 45, 98, 22, 21, 6, 3, 27, 18, 11, 56, 16, 42, 36, 2, 60, 38, 24, 8, 16, 76, 62, 14, 84, 32, 24, 18, 8, 5, 25, 68, 65, 26, 22, 2, 52, 84, 30, 8, 2, 90, 5, 34, 56, 16, 42, 36]);
const [output, setOutput] = React.useState([]);
const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
React.useEffect(() => {
setOutput(shuffleArray(input));
}, [])
return (
<div>
[
{
input.length > 0 ?
input.map((n, i) => (
<span key={i}>
{ (i? ", " : "") + n }
</span>
))
:
"No array..."
}
]
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The input is changed because that's the way pass-by-value works. Primitives cannot be mutated without reassignment. However, when it comes to objects (and arrays, as in this case), properties of the object can be mutated without reassignment.
If you want to keep input unchanged, you can use Array.from instead and manipulate a copy of the array.
const App = () => {
const [input, setInput] = React.useState([90, 32, 28, 8, 21, 24, 64, 92, 45, 98, 22, 21, 6, 3, 27, 18, 11, 56, 16, 42, 36, 2, 60, 38, 24, 8, 16, 76, 62, 14, 84, 32, 24, 18, 8, 5, 25, 68, 65, 26, 22, 2, 52, 84, 30, 8, 2, 90, 5, 34, 56, 16, 42, 36]);
const [output, setOutput] = React.useState([]);
const shuffleArray = (array) => {
const shuffled = Array.from(array);
for (let i = shuffled.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = shuffled[i];
shuffled[i] = shuffled[j];
shuffled[j] = temp;
}
return shuffled;
}
React.useEffect(() => {
setOutput(shuffleArray(input));
}, [])
return (
<div>
[
{
input.length > 0 ?
input.map((n, i) => (
<span key={i}>
{ (i? ", " : "") + n }
</span>
))
:
"No array..."
}
]
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Javascript actually passes Objects and Arrays by "Copy of a Reference" as explained in this SO answer: https://stackoverflow.com/a/13104500/17704187
So your shuffleArray function actually mutates the contents of the input array.

What to use instead of push?

In the code below, I use "push" to fill and empty array. I need help to write the above code so that it comes out the same way, without using "push". I have been challenged to this by the book, "Head First JavaScript Programming". I have tried, but I am stumped.
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69,
64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44
]
var highScore = 0
var output
for (var i = 0; i < scores.length; i++) {
output = `Bubbles solution # ${i} score: ${scores[i]}<br>`
document.write(output)
if (scores[i] > highScore) {
highScore = scores[i]
}
}
let bestSolutions = []
for (var i = 0; i < scores.length; i++) {
if (scores[i] == highScore) {
bestSolutions.push([i])
}
}
document.write(`Bubbles Tests: ${scores.length}<br>`)
document.write(`Highest Bubble Score: ${highScore}<br>`)
document.write(`Solutions with highest score: #${bestSolutions[0]} and #${bestSolutions[1]}`)
If I understand this correctly you're looking to find all max values in an array of numbers. If that is the case, you can use the reduce and filter methods as an alternative to using push.
function findMaxes(arr) {
const max = Math.max.apply(null, arr);
return arr.filter(n => n == max);
}
For example:
findMaxes([3, 5, 1, 1, 4, 5]) == [5, 5]
findMaxes([-1, -1, -1]) == [-1, -1, -1]
If you want to find the positions of all maxes:
function findAllMaxPositions(arr) {
const max = Math.max.apply(null, arr);
return arr.map((e, index) => [e, index])
.filter(pair => pair[0] == max)
.map(e => e[1]);
}
You can try this approach:
Use Math.max.apply to find the highest score.
Use a loop to find all matching values and store their indexes.
let scores = [ 60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44 ]
var highScore = Math.max.apply(null, scores)
var bestSolutions = scores.reduce((acc, score, index) =>
score === highScore ? [ ...acc, index] : acc, []
);
document.write(`Bubbles Tests: ${scores.length}<br>`)
document.write(`Highest Bubble Score: ${highScore}<br>`)
document.write(`Solutions with highest score: #${bestSolutions[0]} and #${bestSolutions[1]}`)
Algo based:
Create 2 variables, highScore and bestSolutions.
Loop over Array.
For every iteration, make 2 checks:
If current score is greater than highScore
If yes, initialize highScore with current score and initialize bestSolutions with current index.
If highScore is equal to current score
If yes, push index to current bestSolutions
let scores = [ 60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44 ];
let highScore = 0;
let bestSolutions = [];
for(let i = 0; i< scores.length; i++) {
if (scores[i] > highScore) {
highScore = scores[i];
bestSolutions = [ i ];
} else if (scores[i] === highScore) {
bestSolutions.push(i)
}
}
document.write(`Bubbles Tests: ${scores.length}<br>`)
document.write(`Highest Bubble Score: ${highScore}<br>`)
document.write(`Solutions with highest score: #${bestSolutions[0]} and #${bestSolutions[1]}`)
Reference:
Find the min/max element of an Array in JavaScript
Ok, one approach is the following : If you want your bestscores array to contain the indices of the elements in scores that are equal to highscore, you can define bestcores as the map of scores through the function that takes a tuple (value,index) and return index if value == highscore otherwise null.
Examples of maps
const a = [2, 3, 5, 0, 6];
const b = a.map(value => value * 2); // [4, 6, 10, 0, 12];
const c = a.map((value, index) => index); //[0, 1, 2, 3, 4];
const d = a.map((value, index) => value + index); //[2, 4, 7, 3, 10];
const e = a.map((value,index) => value >= 3 ? index : null);
// [null, 1, 2, null, 4]. We can deal with those nulls with a filter
const f = e.filter(value => value != null) // [1,2,4]
Altogether in your case
const highscores = scores.map((value,index) => value == highscore ? index : null)
.filter(value => value != null);
This gives you a high level approach but is a bit computationally inefficient as it actually does a double iteration over the scores array. A manual iteration over your array and a push is actually more efficient.
You can use ( ) rather than [ ] in bestSolutions.push(i)
var bestSolutions = [];
for (let i = 0; i < scores.length; i++) {
if (scores[i] == highScore) {
bestSolutions.push(i)
}
}
A very obvious alternative would be
bestSolutions[bestSolutions.length] = [i];
Explanation: At any time, in bestSolutions you have exactly bestSolutions.length number of elements. The first has the index of 0, the second has the index of 1, ..., the last is having the index of bestSolutions.length - 1. Hence, the index of the n+1th element will be bestSolutions.length. After assigning the new value to this index, bestSolutions.length will equal its former value + 1.
The edge-case in the above is when bestSolutions is empty, in which case it does not have first or last elements, but the suggested code will work for this case as well, because in this case bestSolutions.length will be 0, exactly the element you want to initialize.
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69,
64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44
]
var highScore = 0
var output
for (var i = 0; i < scores.length; i++) {
output = `Bubbles solution # ${i} score: ${scores[i]}<br>`
document.write(output)
if (scores[i] > highScore) {
highScore = scores[i]
}
}
let bestSolutions = []
for (var i = 0; i < scores.length; i++) {
if (scores[i] == highScore) {
bestSolutions[bestSolutions.length] = [i];
}
}
document.write(`Bubbles Tests: ${scores.length}<br>`)
document.write(`Highest Bubble Score: ${highScore}<br>`)
document.write(`Solutions with highest score: #${bestSolutions[0]} and #${bestSolutions[1]}`)

Combine 2 Arrays in 2D Array if one parameter is the same

The problem I have is, that I want to combine a 2D Array like this:
[
[ "Renault", 61, 16, … ],
[ "Ferrari", 58, 10, … ],
[ "Mercedes", 32, 12, … ],
[ "Mercedes", 24, 21, … ],
[ "Toro Rosso", 7, 8, … ]
]
So in this example I have the String "Mercedes" twice at index 0 in the arrays.
What I want the output to be is the following:
[
[ "Renault", 61, 16, … ],
[ "Ferrari", 58, 10, … ],
[ "Mercedes", 56, 33, … ],
[ "Toro Rosso", 7, 8, … ]
]
So if a String like "Mercedes" is appearing twice in that array I want the second one to be deleted. But the values from the second array must be taken over into the first "Mercedes" Array.
It seems that you actually want to aggregate similar values in the array. Therefore you could do a cycle in foreach and save yourself in a temporary array the string values already known and instead in another your merged classes. At that point, as you scroll through your array, you can check if you have already added an equal string in the first fake, and if so, you can take its index and aggregate them correctly in the other array.
let arr = [
["Renault", 61, 16],
["Ferrari", 58, 10],
["Mercedes", 32, 12],
["Mercedes", 24, 21],
["Toro Rosso", 7, 8]
]
var tmp1 = [], tmp2 = [];
arr.forEach(function(currentValue, index, arr) {
// currentValue contain your element
// index contain the index of your element
// arr contain you entire "values" array
var ind = tmp1.indexOf(currentValue[0]);
if(ind === -1) {
tmp1.push(currentValue[0]);
tmp2.push(currentValue);
} else {
tmp2[ind][1] += currentValue[1];
tmp2[ind][2] += currentValue[2];
}
});
console.log(tmp2);
You can use reduce() to build a object and then use Object.values()
const arr = [
[ "Renault", 61, 16 ],
[ "Ferrari", 58, 10],
[ "Mercedes", 32, 12],
[ "Mercedes", 24, 21],
[ "Toro Rosso", 7, 8]
]
let res = arr.reduce((ac, [k, ...rest]) => {
if(!ac[k]) ac[k] = [];
ac[k] = ac[k].concat(k,...rest);
return ac;
},[])
console.log(Object.values(res))
Sure, by creating an array for your names and then separate the unique ones from them, at last, match them with the index.
let arr = [
["Renault", 61, 16],
["Ferrari", 58, 10],
["Mercedes", 32, 12],
["Mercedes", 24, 21],
["Toro Rosso", 7, 8]
]
let c = [...new Set(arr.map(a => a[0]))] // get unique values of all arr[0]
let ans = arr.filter((a, post) => {
if (c[post]) { // check if index exists because we seperated one, the last is undefined here
if (a[0] == c[post]) {
return a;
}
} else {
return a; // else return as usual
}
})
console.log(ans)
You get the filtered array.
You can iterate over the array and keep a map object of the names you already processed with their index in the result array so you can access them quicker when you need to push more values.
i.e:
{
"Renault": 0,
"Ferrari": 1,
"Mercedes": 2,
"ToroRosso": 3
}
Here is a running example with the steps:
const arr = [
["Renault", 61, 16],
["Ferrari", 58, 10],
["Mercedes", 32, 12],
["Mercedes", 24, 21],
["Toro Rosso", 7, 8]
];
let map = {};
let globalIndex = 0;
let resultArray = [];
arr.forEach((currentArray) => {
const [name, ...rest] = currentArray;
if (map[name] === undefined) {
// if our map doesn't already store this value
// then store it with it's current index
map[name] = globalIndex;
// push it to the result array
resultArray[globalIndex] = currentArray;
// increment the global index
//so we can keep track of it in the next iteration
globalIndex++;
} else {
// we already have an array with this name
// grab the relevant index we previously stored
// and push the items without the name via this index
const relevantIdx = map[name];
resultArray[relevantIdx] = [
...resultArray[relevantIdx],
...rest
]
}
})
console.log(resultArray);

Categories