Going through an nested array and removing duplicates, while adding other elements - javascript

So here is what I'm trying to do.
I have an array that contains more arrays of dates (which there are multiples of) and individual scores. It looks like this:
var example = [
["11/7/2015", 4],
["11/7/2015", 7],
["11/7/2015", 2],
["11/8/2015", 2],
["11/8/2015", 7],
["11/9/2015", 0],
["11/10/2015", 1]
];
My goal is to iterate through this entire array (it has around 900 cells), that can add/combine the scores of the dates that are similar, and overall removes all duplicate dates with the scores added together.
So the end result of the first array should look like this:
var example = [
["11/7/2015", 13],
["11/8/2015", 9],
["11/9/2015", 0],
["11/10/2015", 1]
];
As you can see, the duplicate dates were removed and the scores of each duplicate cell were added under one cell.
I tried doing this by using a for loop like this (using a duplicate of the array so I can use it as comparison to the original):
for(var i = 1; i < example.length; i--){
if(example[i][0] === dummyArray[i-1][0]){
example[i-1][1] += dummyArray[i][1];
example.splice(i,1);
} else{
}
}
But I can't use i-1 syntax inside the loop and not sure where to go from here. My goal is to do this in pure javascript and not use any libraries.

Here's one way of doing that:
var dateScoreAggregateMap = {};
example.forEach(function(pair){
if(dateScoreAggregateMap[pair[0]]){
dateScoreAggregateMap[pair[0]] += pair[1];
} else {
dateScoreAggregateMap[pair[0]] = pair[1];
}
});
example = Object.keys(dateScoreAggregateMap).map(function(date){
return [date, dateScoreAggregateMap[key]];
});

If you don't mind the result in object form:
var example = [
["11/7/2015", 4],
["11/7/2015", 7],
["11/7/2015", 2],
["11/8/2015", 2],
["11/8/2015", 7],
["11/9/2015", 0],
["11/10/2015", 1]
];
var out = example.reduce(function (p, c) {
if (!p[c[0]]) p[c[0]] = 0; // key doesn't exist in object, add it and set it to zero
p[c[0]] += c[1]; // add the score to the existing key
return p;
}, {});
alert(JSON.stringify(out));

First generate results through an object:
var obj = {};
example.map(function(el) {
el[0] in obj ? obj[el[0]] += el[1] : obj[el[0]] = el[1];
return obj;
});
el is the array pair. You ask if the date el[0] is in the object. If so, add the corresponding value. If not, set the key with the corresponding value.
Translate the object to an array:
var arr = Object.keys(obj).map(function(el) {
return [el, obj[el]];
});
or (what is equal):
var arr = [];
for(k in obj) {
var pair = [k, obj[k]];
arr.push(pair);
}
JSFiddle

Related

Multidimensional array replace with close range Javascript

Let's say we have a List of ranges expressed in arrays with two elements [from, to].
When we add a new array range like [5,8], it should check in List if there is a closest range and then replace it with the new range value. An example is provided below:
Example 1
var List = [[1,2], [3,4], [6,7], [9,10]]
var newData = [5,8]
Expected Output:
[[1,2], [3,4], [5,8], [9,10]]
The [6,7] range is already included in [5,8]
Example 2
var List = [[1,3], [4,6], [8,10]]
var newData = [5,9]
Expected Output:
[[1,3], [4,10]]
Assuming the initial list is well-formed, with its pairs sorted and non-overlapping, you could use binary search to find the end points of a new pair in the array and so determine any overlap. If overlap, splice the array accordingly:
function addSegments(segments, ...pairs) {
for (let pair of pairs) {
let [start, end] = pair.map(function (x, i) { // Binary search
let low = 0,
high = segments.length;
side = 1 - i;
while (low < high) {
let mid = (low + high) >> 1;
if (x < segments[mid][side]) high = mid;
else low = mid + 1;
}
return low - (side && segments[low-1]?.[side] === x);
});
if (start < end) {
pair = [
Math.min(segments[start][0], pair[0]),
Math.max(segments[end-1][1], pair[1])
];
}
segments.splice(start, end - start, pair);
}
}
// Demo
let list = [[1, 2], [3, 4], [6, 7], [9, 10]];
addSegments(list, [5, 8]);
console.log(JSON.stringify(list));
list = [[1, 3], [4, 6], [8, 10]];
addSegments(list, [5, 9]);
console.log(JSON.stringify(list));
If we have two arrays containing all the integers included in the ranges, then we can intersect and see if they overlap. If they do, we create a new range from the union of the two ranges, and use that in the output instead.
So to that end, we define three helper functions, range(), intersect() and union(), and then we use those to generate the output array. If the intersection exists, we overwrite any overlapped range with the new union of the two. I assumed that if two ranges just touched they weren't meant to be combined, so the overwrite is only triggered if the intersection contains more than one element. Also, I added an initial sort.
function add(list, data) {
let buffer = [], idx;
const range=(a,b)=>Array.from({length:b-a+1}, (_, i)=>(a+i));
const intersect=(a,b)=>a.filter(x=>b.includes(x));
const union=(a,b)=>[...new Set([...a, ...b])].sort((a,b)=>a-b);
list.sort((a,b)=>a[0]-b[0]);
list.forEach(el=>{
let x = range(el[0], el[1]);
let y = range(data[0], data[1]);
let i = intersect(x, y);
if(i.length>1) {
let d = union(x,y);
data = [d[0], d[d.length-1]];
if(idx) { buffer[idx] = data; }
else { idx = buffer.push(data)-1; }
}
else { buffer.push(el); };
});
return buffer;
}
// DEMO
let List = [[1,2], [3,4], [6,7], [9,10]];
let newData = [5,8];
console.log(JSON.stringify(List));
console.log(JSON.stringify(newData));
console.log(JSON.stringify(add(List, newData)));
console.log('');
List = [[1,3], [4,6], [8,10]];
newData = [5,9];
console.log(JSON.stringify(List));
console.log(JSON.stringify(newData));
console.log(JSON.stringify(add(List, newData)));
console.log('');
// DEMO WITH UNORDERED ELEMENTS
List = [[3,4], [9,10], [6,7], [1,2]];
newData = [5,8];
console.log(JSON.stringify(List));
console.log(JSON.stringify(newData));
console.log(JSON.stringify(add(List, newData)));
console.log('');

How filter simple multidimensional array with only numbers

I have a multidimensional array like this:
let arr = [ [1,2,3], [3,4,6], [4,5,7], [8,9,3]];
and I need to use only a filter function for filtering this array. I must filter the array and create a new array from the current inner arrays which contain the number 3:
let myArr = [ [1,2,3], [3,4,6], [4,5,7], [8,9,3]];
function getVal(value, arr){
for(let i in arr){
for(let j in arr[i]){
if(arr[i][j] == value){
return true;
}
}
}
}
let newArr = myArr.filter(getVal(3, myArr));
My code is not working. Actually, it's working, but I don`t see any result.
The expected result is like this:
newArr = [[1,2,3], [3,4,6], [8,9,3]];
All I have found are filter methods with objects.
You need a different style of the callback.
const contains = v => a => a.includes(v);
var array = [[1, 2, 3], [3, 4, 6], [4, 5, 7], [8, 9, 3]],
result = array.filter(contains(3));
console.log(result);
Assuming that it is 2 dimensional array. Use indexOf to check inside arrays:
myArr.filter((a)=> { return a.indexOf(3)>=0})
cont result = arr.filter(a => a.includes(3));

How to get distinct values from an array of arrays in JavaScript using the filter() method? [duplicate]

This question already has answers here:
How to remove duplicates from a two-dimensional array? [closed]
(3 answers)
Closed 3 years ago.
I have an array like this:
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
What should I do to retrieve an array without the duplicates?
[[1, 2], [3, 4], [2, 1]];
I would like to use the filter method. I tried this but it doesn't work:
x.filter((value,index,self) => (self.indexOf(value) === index))
EDIT: as I specified to use the filter method, I don't think this question is a duplicate. Also, I got several interesting answers.
Try converting the inner arrays to a string, then filter the dupes and parse the string again.
let x = [[1, 2], [3, 4], [1, 2]];
var unique = x.map(ar=>JSON.stringify(ar))
.filter((itm, idx, arr) => arr.indexOf(itm) === idx)
.map(str=>JSON.parse(str));
console.log(unique);
Filter just causes things to get into O(n^2).
The currently accepted answer uses .filter((itm, idx, arr) => arr.indexOf(itm) === idx) which will cause the array to be iterated each time during each iteration... n^2.
Why even go there? Not only that, you need to parse in the end. It is a lot of excess.
There is no real good way to use filter without hitting O(n^2) here, so if performance is the goal is should probably be avoided.
Instead, just use reduce. It is very straightforward and fast easily accomplishing O(n).
"Bin reduce the set to unique values."
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
let y = Object.values(x.reduce((p,c) => (p[JSON.stringify(c)] = c,p),{}));
console.log(y);
In case it isn't as clear, here is a more readable version of the bin reduction.
// Sample Data
let dataset = [[1, 2], [3, 4], [1, 2], [2, 1]];
// Create a set of bins by iterating the dataset, which
// is an array of arrays, and structure the bins as
// key: stringified version of the array
// value: actual array
let bins = {};
// Iteration
for(let index = 0; index < dataset.length; index++){
// The current array, from the array of arrays
let currentArray = dataset[index];
// The JSON stringified version of the current array
let stringified = JSON.stringify(currentArray);
// Use the stringified version of the array as the key in the bin,
// and set that key's value as the current array
bins[stringified] = currentArray;
}
// Since the bin keys will be unique, so will their associated values.
// Discard the stringified keys, and only take the set of arrays to
// get the resulting unique set.
let results = Object.values(bins);
console.log(results);
If you were to have to go the route of filter, then n^2 must be used. You can iterate each item looking for existence using every.
"Keep every element which does not have a previous duplicate."
let x = [
[1, 2],
[3, 4],
[1, 2],
[2, 1]
];
let y = x.filter((lx, li) =>
x.every((rx, ri) =>
rx == lx ||
(JSON.stringify(lx) != JSON.stringify(rx) || li < ri))
);
console.log(y);
Okay, the string hash idea is brilliant. Props to I wrestled a bear once. I think the code itself could be a bit better though, so here's how I tend to do this type of thing:
let x = [[1, 2], [3, 4], [1, 2]];
const map = new Map();
x.forEach((item) => map.set(item.join(), item));
console.log(Array.from(map.values()));
And if you want an ugly one liner:
let x = [[1, 2], [3, 4], [1, 2]];
const noRepeats = Array.from((new Map(x.map((item) => [item.join(), item]))).values());
console.log(noRepeats);
This is a solution with time complexity of O(n) where n is the number of elements in your array.
Using the filter method as the OP wants it:
const x = [[1, 2], [3, 4], [1, 2], [2, 1]];
const s = new Set();
const res = x.filter(el => {
if(!s.has(el.join(""))) {
s.add(el.join(""));
return true;
}
return false
})
console.log(res)
My personal preference here is to use ForEach as it looks more readable.
const x = [[1, 2], [3, 4], [1, 2], [2, 1]];
const s = new Set();
const res = [];
x.forEach(el => {
if(!s.has(el.join(""))) {
s.add(el.join(""));
res.push(el)
}
})
console.log(res);
We are using a Set and a simple combination of the elements of the array to make sure they are unique. Otherwise this would become O(n^2).
The equivalent to
x.filter((value,index,self) => (self.indexOf(value) === index))
would be
x.filter((v,i,self) => {
for1:
for (let j = 0; j < self.length; j++) {
if (i == j) {
return true;
}
if (self[j].length != v.length) {
continue;
}
for (let k = 0; k < v.length; k++) {
if (self[j][k] != v[k]) {
continue for1;
}
}
return false;
}
return true;
})
Unlike some of the other answers, this does not require a conversion to string and can thus work with more complex values.
Use === instead of == if you want.
The time complexity is not great, of course.
indexOf does not work on identical instances of arrays/objects type elements within an array, as such arrays just hold references.
In filter function instance you get via parameter v (in below code) is not the same instance as stored in array, making indexOf unable to return the index of it.
In below code, by converting objects to strings we can use indexOf to find duplicates.
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
console.log(x.
map(function(v){
return JSON.stringify(v)
})
.filter(function(v, i, o) {
return o.length == i ? true : o.slice(i + 1).indexOf(v) == -1;
})
.map(function(v) {
return JSON.parse(v)
})
);

Remove duplicates from a multidimensional array

I have found questions that kind of touch on the issue I'm having, but I haven't found a solution that works for me yet. I have this array: [[1, red], [2, green], [3, red], [3, blue], [5, green]] and I need it to return [[1, red], [2, green], [3, blue]. What I need the code to do is go through the array and find ONLY colors that match, not numbers, and get rid of that entire index.
I have tried something like this
var uniqueArray = colors.filter(function(item, pos) {
return colors.indexOf(item) == pos;
});
I'm thinking that this code is searching for a complete match, and I only require a partial match. So basically, how would I modify .filter() to get rid of partial duplicates (only matching the colors)?
Please let me know if I need to provide any more information.
// Parameter marr: multidimensional array
function removeSameColors(marr){
var carr = [];
var rarr = [];
var j = -1;
for(var i = 0, l = marr.length; i < l; i++){
if(carr[marr[i][1]] !== true){
carr[marr[i][1]] = true;
rarr[++j] = marr[i];
}
}
return rarr;
}
That should solve your problem with very low execution time.
You could use a hash table with the color and use Array#filter for the wanted items.
var data = [[1, 'red'], [2, 'green'], [3, 'red'], [3, 'blue'], [5, 'green']],
result = data.filter(function (a) {
if (!this[a[1]]) {
return this[a[1]] = true;
}
}, Object.create(null));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I would use a for loop to populate a new, unique array:
var old_array = [[1, red], [2, green], [3, red], [3, blue], [5, green]],
old_count = old_array.length,
unique_array = [], check_array = [], i = 0;
for(; i < old_count; i++) {
if (check_array.indexOf(old_array[i][1]) === -1) {
check_array.push(old_array[i][1]);// this array is filled with new colors, and used for checking
unique_array.push(old_array[i]);
}
}
I would just keep track of the unique colors in an object passed into the filter, because as a hash it's guaranteed to be unique. If the object doesn't have a property by that color name, it returns it from the filter. Otherwise if it does it ignores the item.
var colors = [[1, "red"], [2, "green"], [3, "red"], [3, "blue"], [5, "green"]];
var uniqueArray = colors.filter(function(item, pos) {
if (!this.hasOwnProperty(item[1])) {
return this[item[1]] = true;
}
return false;
}, {});
This gives you uniqueArray = [[1,"red"],[2,"green"],[3,"blue"]], as expected.

Merging of two arrays, store unique elements, and sorting in jQuery

var Arr1 = [1,3,4,5,6];
var Arr2 = [4,5,6,8,9,10];
I am trying to do merge these two arrays and output coming is [1,3,4,5,6,4,5,6]
I have used $.merge(Arr1, Arr2); this piece to merge them. Using alert I can see the merged array like above.
Now my question is how can I get the following output:
[1,3,4,5,6,8,9,10]
i.e. the elements should be unique as well as sorted in the same manner I have mentioned.
Please help.
You can use Array.prototype.sort() to do a real numeric sort and use Array.prototype.filter() to only return the unique elements.
You can wrap it into a helper similar to this:
var concatArraysUniqueWithSort = function (thisArray, otherArray) {
var newArray = thisArray.concat(otherArray).sort(function (a, b) {
return a > b ? 1 : a < b ? -1 : 0;
});
return newArray.filter(function (item, index) {
return newArray.indexOf(item) === index;
});
};
Note that the custom sort function works with numeric elements only, so if you want to use it for strings or mix strings with numbers you have to update it off course to take those scenarios into account, though the rest should not change much.
Use it like this:
var arr1 = [1, 3, 4, 5, 6];
var arr2 = [4, 5, 6, 8, 9, 10];
var arrAll = concatArraysUniqueWithSort(arr1, arr2);
arrAll will now be [1, 3, 4, 5, 6, 8, 9, 10]
DEMO - concatenate 2 arrays, sort and remove duplicates
There is many ways of doing this I'm sure. This was just the most concise I could think off.
merge two or more arrays + remove duplicities + sort()
jQuery.unique([].concat.apply([],[[1,2,3,4],[1,2,3,4,5,6],[3,4,5,6,7,8]])).sort();
One line solution using just javascript.
var Arr1 = [1,3,4,5,6];
var Arr2 = [4,5,6,8,9,10];
const sortedUnion = [... new Set([...Arr1,... Arr2].sort((a,b)=> a-b))]
console.log(sortedUnion)
This looks like a job for Array.prototype.indexOf
var arr3 = arr1.slice(), // clone arr1 so no side-effects
i; // var i so it 's not global
for (i = 0; i < arr2.length; ++i) // loop over arr2
if (arr1.indexOf(arr2[i]) === -1) // see if item from arr2 is in arr1 or not
arr3.push(arr2[i]); // it's not, add it to arr3
arr3.sort(function (a, b) {return a - b;});
arr3; // [1, 3, 4, 5, 6, 8, 9, 10]
a = [1, 2, 3]
b = [2, 3, 4]
$.unique($.merge(a, b)).sort(function(a,b){return a-b}); -> [1, 2, 3, 4]
Update:
This is a bad idea, since the 'unique' function is not meant for use on numbers or strings.
However, if you must then the sort function needs to be told to use a new comparator since by default it sorts lexicographically.
Using underscore.js:
_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]).sort(function(a,b){return a-b});
=> [1, 2, 3, 10, 101]
This example is taken directly from underscore.js, a popular JS library which complements jQuery
I did that as follows, where t1 and t2 are my two tables.
The first command put the values of the table t2 to the t1. The second command removes the duplicate values from the table.
$.merge(t1, t2);
$.unique(t1);
function sortUnique(matrix) {
if(matrix.length < 1 || matrix[0].length < 1) return [];
const result = [];
let temp, ele;
while(matrix.length > 0) {
temp = 0;
for(let j=0; j<matrix.length; j++) {
if(matrix[j][0] < matrix[temp][0]) temp = j;
}
if(result.length === 0 || matrix[temp][0] > result[result.length-1]) {
result.push(matrix[temp].splice(0,1)[0]);
} else {
matrix[temp].splice(0,1);
}
if(matrix[temp].length===0) matrix.splice(temp, 1);
}
return result;
}
console.log(sortUnique([[1,4,8], [2,4,9], [1,2,7]]))
Using JavaScript ES6 makes it easier and cleaner. Try this:
return [...Arr1, ...Arr2].filter((v,i,s) => s.indexOf(v) === i).sort((a,b)=> a - b);
and there you have it. You could build it in a function like:
function mergeUniqueSort(Arr1, Arr2){
return [...Arr1, ...Arr2].filter((v,i,s) => s.indexOf(v) === i).sort((a,b)=> a - b);
}
and that settles it. You can also break it down using ES6. Use a Spread Operator to combine arrays:
let combinedArrays = [...Arr1, ...Arr2]
then get the unique elements using the filter function:
let uniqueValues = combinedArrays.filter((value, index, self ) => self.indexOf(value) === index)
Lastly you now sort the uniqueValue object:
let sortAscending = uniqueValues.sort((a-b) => a - b) // 1, 2, 3, ....10
let sortDescending = uniqueValues.sort((b-a) => b - a) // 10, 9, 8, ....1
So you could use any part, just in case.

Categories

Resources