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.
Related
Let's say I have 4 arrays:
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
And I want to return the child/sub arrays that start with both 1 and 2, what type of loop would I need?
Currently, this is what I have:
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
var selected = [1, 2]; // These are the values that need to match
var result = [];
for (var i = 0; i < selected.length; i++) {
for (var j = 0; j < arrays.length; j++) {
if (arrays[i][j] === selected[i]) {
result.push(arrays[i]);
}
}
}
When there's more than 1 value in the selected array, it seems to return all the ones that match 2 on the second index, so the result would be:
[
[1, 2, 1],
[1, 2, 3],
[0, 2, 2]
]
The loop needs to ensure that on the second iteration it's making sure the first value is still true, as my intended result would be:
[
[1, 2, 1],
[1, 2, 3]
]
Please someone help me, I've had my head trying hundreds of different loop and checks variations for 2-3 days.
Thanks so much!!
Jake
Your current code pushes to the result array whenever any given index matches between arrays and selected. Instead you will need to reverse your loops and iterate over selected for every sub array and check if every element matches, if not break the inner loop and don't push.
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2],
];
const selected = [1, 2]; // These are the values that need to match
const result = [];
for (let i = 0; i < arrays.length; i++) {
let match = true;
for (let j = 0; j < selected.length; j++) {
if (arrays[i][j] !== selected[j]) {
match = false;
break;
}
}
if (match) {
result.push(arrays[i]);
}
}
console.log(result);
A more modern solution would be to use filter() with a nested every() call on selected.
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2],
];
var selected = [1, 2];
const result = arrays.filter(arr => selected.every((n, i) => n === arr[i]));
console.log(result);
Here is another approach where you turn both arrays to string and check it those inner arrays start with selected array.
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
var selected = [1, 2];
const result = arrays.filter(e => e.toString().startsWith(selected.toString()))
console.log(result)
Let's try to put your condition into words. That way, an implementation may come to mind more easily.
A short wording may be: "Take all arrays that match (rather: start with) a certain sub-array." In code, it may look like this:
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
const selection = [1, 2];
const result = filterArrays(arrays, selection);
console.log(result);
function filterArrays(arrays, selection) {
const selectedArrays = [];
for (let i = 0; i < arrays.length; ++i) {
const array = arrays[i];
const subarray = array.slice(0, selection.length); // Get starting sub-array
if (compareArrays(subarray, selection)) {
selectedArrays.push(array);
}
}
return selectedArrays;
}
/*Ignore; helper function*/
function compareArrays(array1, array2) {
if (array1.length !== array2.length) return false;
const length = array1.length;
for (let i = 0; i < length; ++i) {
if (array1[i] !== array2[i]) return false;
}
return true;
}
.as-console-wrapper {max-height:100%!important}
Another, more specific wording may be: "Take all arrays that match a selection at an index." Note that we only reworded the "match a sub-array" part. I believe this is what you tried.
Refer to pilchard's answer for an implementation. Note that their implementation assumes the arrays in arrays to be at least the same length as selected.
I see you used var instead of the preferred modern let/const declarators. Here's a short outline of their differences:
let/const declarators:
Block-scoped.
Narrower scope means less name-space pollution.
More similar to declarators in other well-known languages:
Variables of these declarators cannot be used before their declaration (see TDZ).
var declarator:
Function-scoped.
Hoisted and with no TDZ, resulting in this (perhaps confusing) behaviour:
Variables declared with var can be used even before their declaration.
Duplicate declarations are allowed since they are effectively the same.
Also, JavaScript has different kinds of for-loops:
for-loop: The for-loops you used are this kind. It is the most versatile kind.
for...of-loop: A loop to iterate over an iterable object (see iterators). For example, arrays are iterable, so you can get its values with a for...of-loop:
const values = [1, 2, 3];
let sum = 0;
for (const value of array) {
sum += value;
}
console.log(sum); // -> 6
for...in-loop: A loop to iterate over enumerable properties of an object. It is easily confused with a for...of-loop, but MDN's example demonstrates the differences understandably.
In my code example above, the for-loop in filterArrays() can be replaced with a for...of-loop to better convey my intention: To iterate over all arrays in arrays, disregarding their index:
for (let i = 0; i < arrays.length; ++i) {
const array = arrays[i];
// ...
}
// Same as
for (const array of arrays) {
// ...
}
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)
})
);
I know there are a couple of answers on the matter, but as I was trying to make my own, I got very lost on how recursive functions work, here's what I tried :
function flatcoords(arr) {
for (i = 0; i < arr.length; i++) {
const value = arr[i];
if (value.isArray) {
return flatcoords(value);
} else {
return arr;
}
}
}
const arr_test = [
[
[1, 2],
[1, 2]
],
[1, 2],
[1, 2],
[
[
[1, 2],
[1, 2]
]
]
];
//wanted_result = [ [1,2],[1,2],[1,2],[1,2],[1,2],[1,2] ]
console.log(flatcoords(arr_test));
I want the result to be a 2D array, what am I missing in my logic?
First of all, you need to declare all variables before use. Then you could use Array.isArray for a check if an item is an array.
In this case, I suggest to check the first item of the checked element if it is an array as well, in this case flat the array.
As result, an array comes in handy, because not only one element should be returned, but all. And while arrays have more item, you need to append all items to the existing result array.
function flatcoords(array) {
var result = [],
value,
i;
for (i = 0; i < array.length; i++) {
value = array[i];
if (Array.isArray(value) && Array.isArray(value[0])) {
result = result.concat(flatcoords(value));
} else {
result.push(value);
}
}
return result;
}
const arr_test = [[[1, 2], [1, 2]], [1, 2], [1, 2], [[[1, 2], [1, 2]]]];
console.log(flatcoords(arr_test));
.as-console-wrapper { max-height: 100% !important; top: 0; }
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
I wanted to change the rows into columns of an array.
[
[1],
[1,2],
[1,2,3],
[4,2,3],
[4,5,3],
[4,5,6]
]
to
[
[1,1,1,4,4,4],
[2,2,2,5,5],
[3,3,3,6]
]
I tried
var res = [];
for(i in this.fields) {
for(j in this.fields[i].value) {
if(i === 0) res[j] = [];
res[j][i] = this.fields[i].value[j];
}
}
this gives me empty set.
Create this function:
function transpose(arr) {
return Object.keys(arr[0]).map(function (c) {
return arr.map(function (r) {
return r[c];
});
});
}
and then:
var transposedArray = transpose(originalArray);
What you're asking looks a little weird because you have different lengths and you're ignoring undefined values, but it is still achievable.
Don't use for..in loops for Array, use a normal for. Also, you'll need to know how many items you'll have in your new parent Array, which is the max of the lengths of the original child Arrays.
var arrR = [ // will refer to "down" and "across" as in this literal
[1],
[1, 2],
[1, 2, 3],
[4, 2, 3],
[4, 5, 3],
[4, 5, 6]
];
function r2c(arr) {
var arrC = [], // next get the longest sub-array length
x = Math.max.apply(Math, arr.map(function (e) {return e.length;})),
y = arr.length,
i, j;
for (i = 0; i < x; ++i) { // this is the loop "down"
arrC[i] = [];
for (j = 0; j < y; ++j) // and this is the loop "across"
if (i in arr[j])
arrC[i].push(arr[j][i]);
}
return arrC;
}
var arrC = r2c(arrR);
/* [
[1, 1, 1, 4, 4, 4],
[2, 2, 2, 5, 5],
[3, 3, 3, 6]
] */
You should still consider if you're happy with [[1], [1, 2], [1]] becoming [[1, 1, 1], [2]], which I would consider unexpected (the position of 2 is completely lost), but seems to be what you intend.
Similar to Pauls but doesn't need to get the max length first:
function transpose(arr) {
// Loop over arrays as long as one has values
// Arrays should be contiguous, may fail if sparse
for (var result = [], i=0, more; more; i++) {
more = false;
// Get the ith element of each array (if there is one)
for (var j=0, jLen=arr.length; j<jLen; j++) {
// Don't add missing members
if (arr[j].hasOwnProperty(i)) {
// Add array for result if not already there
result[i] = result[i] || [];
// Do transpose
result[i][j] = arr[j][i];
// Only keep going while there is data
more = true;
}
}
}
return result;
}
BTW, a fixed version of your original function is:
function transpose2(fields) {
// Make sure the result array is initialised
var res = [];
// Don't forget to keep counters local - declare them
// I've removed *this* as it's a plain function, use it if
// it's an instance method
for(var i in fields) {
// Values are read directly, there is no "value" accessor
for(var j in fields[i]) {
// Don't rely on order of enumeration - may not start at 0
if(!res[j]) res[j] = [];
// Do the transpose
res[j][i] = fields[i][j];
}
}
return res;
}
But as noted above, for..in is not liked for arrays, particularly as there are many libraries that extend built-ins like Array.prototype so you will traverse those properties too. But if you're cool with that, this is a good way to deal with sparse arrays. You can add a hasOwnProperty test to avoid inherited enumerables.
Note also that the order of enumeration isn't necessarily from '0' or in any particular order, hence changed way of initialising res[j].