I'm looping over the following array of arrays and for the first item in the array, I need to insert an object after every item, except the first and maybe last item. The data looks like this...
const data = [
['Path', 'Item1', 'Item2', 'Item3', 'Item4', 'Item5', 'Score'],
['/path-one', 1, 3, 2, 2, 4, 3],
['/path-two', 4, 5, 5, 5, 6, 3],
['/path-three', 5, 5, 3, 5, 3, 3],
['/path-four', 2, 3, 4, 2, 2, 3],
]
For the first row, except the first item and last item, after every other item I want to insert an object {role: 'annotation'}. For the rest of the rows, for every index in the first item array that has the role object, I want to duplicate the previous value such that if the first array after modification is:
['Path', 'Item1', {role: 'annotation'}, 'Item2', {role: 'annotation'}, 'Score'], then the other arrays will follow the pattern ['/path-one', 'value1', 'value1', 'value2', 'value2', 'value3']
My solution so far has been inadequate. Here's what I came up with...
let indexes = []
let scoreIndex
const result = data.map((item, index) => {
let clone = [...item]
item.map((i, ix) => {
if (index === 0) {
if (ix !== 0 && typeof clone[ix + 1] === 'string' && typeof clone[ix] !== 'object') {
if (clone[ix + 1] === 'Score') {
scoreIndex = ix
}
indexes.push(ix)
clone.splice((ix + 1), 0, {role: 'annotation'})
}
return i
} else {
if (indexes.includes(ix) && ix !== scoreIndex) {
item.splice((ix + 1), 0, i)
return i
}
return i
}
})
return clone
})
Your help would be greatly appreciated.
Perhaps I misunderstood something. I assumed the following:
:: The lengths of each array in the data array are the same.
:: You need to inject after each position, which will mess up a for loop that increase it's i(teration) variable.
:: There is always a Path and a Score in the first array.
:: You want to add a new value after each value, apart from the two above.
Solution
Get the length of each array: data[0].length
Loop from the end.
Ignore last position.
Ignore the first position
Splice different value based on if it's the first item in the data array.
I loop over each value once, but I do it in the order of: 'Item5', 4, 6, 3, 3, 'Item4', 2, 5, 5, 2, 'Item3', ...
const data = [
['Path', 'Item1', 'Item2', 'Item3', 'Item4', 'Item5', 'Score'],
['/path-one', 1, 3, 2, 2, 4, 3],
['/path-two', 4, 5, 5, 5, 6, 3],
['/path-three', 5, 5, 3, 5, 3, 3],
['/path-four', 2, 3, 4, 2, 2, 3],
]
function mapArray(data) {
let secondToLastItem = data[0].length - 2; // 1 & 3
let FIRST_ITEM = 0; // 4
for (let index = secondToLastItem; index > FIRST_ITEM; index--) { // 2
for (let item = 0; item < data.length; item++) {
let injectedValue = (item == FIRST_ITEM) // 5
? {'role': 'annotation'}
: data[item][index];
data[item].splice(index + 1, 0, injectedValue);
}
}
return data;
}
console.log( mapArray(data) );
This answer is not perfect, but should give an idea on how this can solved.
const data = [
['Path', 'Item1', 'Item2', 'Item3', 'Item4', 'Item5', 'Score'],
['/path-one', 1, 3, 2, 2, 4, 3],
['/path-two', 4, 5, 5, 5, 6, 3],
['/path-three', 5, 5, 3, 5, 3, 3],
['/path-four', 2, 3, 4, 2, 2, 3],
]
let indexes = []
let scoreIndex
// This method takes in an error and perform splice with the value passed, if no value passed, it will use the current index value.
const splicer = (arr, index, value) => {
var length = (arr.length - 1) * 2;
for(let i = 1; i < length; i = i+2) {
if(value) {
arr.splice(i, 0, value);
} else {
arr.splice(i, 0, arr[i]);
}
}
return arr;
}
// This method goes through data (array of arrays) and call splicer
const output = data.map((item, index) => {
if(index == 0) {
splicer(item, 0, {role : "annotation"})
} else {
splicer(item, 0);
}
});
Output
["Path",{"role":"annotation"},"Item1",{"role":"annotation"},"Item2",{"role":"annotation"},"Item3",{"role":"annotation"},"Item4",{"role":"annotation"},"Item5",{"role":"annotation"},"Score"]
["/path-one",1,1,3,3,2,2,2,2,4,4,3,3]
["/path-two",4,4,5,5,5,5,5,5,6,6,3,3]
["/path-three",5,5,5,5,3,3,5,5,3,3,3,3]
["/path-four",2,2,3,3,4,4,2,2,2,2,3,3]
With map, it is a little tough to skip values (it should be possible), so have used a plan for loop.
Related
I need to check whether one array contains all of the elements of another array, including the same duplicates. The second array can have extra elements. I'm using every...includes, but it's not catching that the second array doesn't have the right duplicates.
For example:
const arr1 = [1, 2, 2, 3, 5, 5, 6, 6]
const arr2 = [1, 2, 3, 5, 6, 7]
if(arr1.every(elem => arr2.includes(elem))){
return true // should return false because arr2 does not have the same duplicates
}
Thanks!
Edit: arr1 is one of many arrays that I am looping through which are coming out of a graph traversal algorithm, so I'd like to avoid restructuring them into an object to create a dictionary data structure if possible.
Try creating this function:
function containsAll (target, toTest) {
const dictionary = {}
target.forEach(element => {
if (dictionary[element] === undefined) {
dictionary[element] = 1;
return;
}
dictionary[element]++;
});
toTest.forEach(element => {
if (dictionary[element] !== undefined)
dictionary[element]--;
})
for (let key in dictionary) {
if (dictionary[key] > 0) return false;
}
return true;
}
Then invoke it like this:
const arr1 = [1, 2, 2, 3, 5, 5, 6, 6]
const arr2 = [1, 2, 3, 5, 6, 7]
console.log(containsAll(arr1, arr2)) // returns false
const arr1 = [1, 2, 2, 3, 5, 5, 6, 6];
//const arr2 = [1, 2, 3, 5, 6, 7];
const arr2 = [1, 2, 2, 3, 5, 5];
let includesAll1 = true;
let includesAll2 = true;
const checkObj1 = {
};
const checkObj2 = {
};
arr1.forEach((el)=> {
if(checkObj1[el] === undefined) {
checkObj1[el] = 1;
} else {
checkObj1[el]++;
}
});
arr2.forEach((el)=> {
if(checkObj2[el] === undefined) {
checkObj2[el] = 1;
} else {
checkObj2[el]++;
}
});
const check1Keys = Object.keys(checkObj1);
const check2Keys = Object.keys(checkObj2);
if(check1Keys.length > check2Keys.length) {
includesAll2 = false;
check2Keys.forEach((key)=> {
const value1 = checkObj1[key];
const value2 = checkObj2[key];
if(!arr1.includes(parseInt(key)) || value1 != value2) {
includesAll1 = false;
}
});
} else {
includesAll1 = false;
check1Keys.forEach((key)=> {
const value1 = checkObj1[key];
const value2 = checkObj2[key];
console.log(value1, value2, key);
if(!arr2.includes(parseInt(key)) || value1 != value2) {
includesAll2 = false;
}
});
}
console.log(includesAll1);
console.log(includesAll2);
Does this solve your problem?
const arr = [1, 2, 3, 5, 6, 7, 2, 10, 2, 3, 2];
const subArr = [1, 2, 2, 3, 2]
const contains = subArr.every(num => subArr.filter(n => n == num).length <= arr.filter(n => n== num).length);
console.log(contains);
You indicate order does not matter in your comments. That makes this very simple.
Sort both arrays
Check if corresponding elements are equal
consider errors associated with sparse or short arrays
Use .reduce() to boil it down to a single result
So this really comes down to a single statement once the arrays are sorted:
matcher.reduce((acc, value , idx)=>matcher[idx] === test[idx], false);
You also mentioned testing this against many arrays. So the full example below does that for demo purposes.
let toMatch = [1, 2, 2, 3, 5, 5, 6, 6]
let arrayOfArrays = [[1,2],[1, 2, 3, 5, 6, 7, 3, 9, 8, 2, 7],[1, 2, 3, 3, 6, 7],[1, 3, 3, 5, 6, 7],[1, 2, 3, 5, 6, 6], [3,5,2,1,6,2,5,6]];
let toMatchSorted = toMatch.slice().sort();
arrayOfArrays.forEach(arr=>{
let toTestSorted = arr.slice().sort();
let out = toMatchSorted.reduce((acc, value , idx)=>toMatchSorted[idx] === toTestSorted[idx], false);
console.log(`Input: ${arr}, Result: ${out}`);
});
How can I sum vertically all data from an array of arrays?
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array1.data.map(function(value, index) {
return value + array2.data[index];
}, 0);
});
console.log(result)
The output should be the vertical sum of arrays.
[3,6,9,12,15,18,21,24]
The problem is that array1 return always as undefined.
You code is almost correct but with 1 issues.
You are looping on accumulator. This will be an array of number in second iteration. Instead loop over array2 or current item.
Idea of .reduce is to have same signature for all iteration. If you do not pass default value for accumulator, first iteration will be of type Array<{ label: string, data: Array<number>}> and second iteration will be just Array<number>. So you can skip behavior for first iteration by passing default value as []. Now the calculation will break as array[n] will be undefined. For this, you can use a default value of 0.
So your calculation will look like:
value + (array1[index] || 0)
Following is a sample:
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array2.data.map(function(value, index) {
return value + (array1[index] || 0);
}, 0);
}, []);
console.log(result)
Use the index/key of map and add to the previous value.
const arrayOfArrays = [{label:'First Value', data:[1,2,3,4,5,6,7,8]},{label:'Second Value', data:[1,2,3,4,5,6,7,8]},{label:'Third Value', data:[1,2,3,4,5,6,7,8]}];
const res = arrayOfArrays.reduce((acc, cur) => (cur.data.map((i, k) => {acc[k] = acc[k] ? acc[k] += i : i}), acc), [])
console.log(res)
you're using reduce in a wrong way, but heres a for loop that does the same job:
arrayOfArrays = [{
label:'First Value', data:[1,2,3,4,5,6,7,8]},{
label:'Second Value', data:[1,2,3,4,5,6,7,8]},{
label:'Third Value', data:[1,2,3,4,5,6,7,8]
}];
const newArr = [];
for(let x = 0; x < arrayOfArrays[0].length; x++){
newArr.push(arrayOfArrays[0].data[x]+arrayOfArrays[1].data[x]+arrayOfArrays[2].data[x])
}
console.log(newArr); // new array
You can flatten the array by looping the array of objects and pushing the data property to a new array, then use reduce/map on the flattened data:
arrayOfArrays = [
{label:'First Value', data:[1,2,3,4,5,6,7,8]},
{label:'Second Value', data:[1,2,3,4,5,6,7,8]},
{label:'Third Value', data:[1,2,3,4,5,6,7,8]}
];
var data = [];
arrayOfArrays.forEach((element)=> {
data.push(element.data)
})
var sum = (r, a) => r.map((b, i) => a[i] + b);
var result = data.reduce(sum);
console.log(result);
Which outputs:
[3, 6, 9, 12, 15, 18, 21, 24]
Working fiddle
If you know that the length of each array is same. you can do as follows
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
let out = arrayOfArrays.reduce((acc, {data}) => acc.map((e, i) => e+data[i]), new Array(8).fill(0));
console.log(out)
You are passing the wrong accumulator which should be an array also in wrong place, it must be with reduce not with map
var result = arrayOfArrays.reduce(function (array1, array2) {
return array1.map(function (value, index) {
return value + array2.data[index];
});
}, Array(8).fill(0));
I would do it like this:
Introduce a helper transport function:
const transport = (arr) => arr[0].map((col, i) => arr.map(row => row[i]));
Get a proper matrix:
const matrix = arrayOfArrays.map(el => el.data)
Then the task becomes trivial:
const res = transport(matrix).map(arr => arr.reduce((x, y) => x + y))
// > (8) [3, 6, 9, 12, 15, 18, 21, 24]
You could take advantage of function generators in case you need to later transform or alterate the values, or just iterate them without needing the entire result set.
In this solution, a function generator is used and the logic applied is:
Get the array with the longest length (assuming length might change)
Get all the elements at index i from 0 to longest length and yield their sum.
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
/**
Sums elements of arrays inside the array vertically.
*/
function* sumVertically(arr) {
// Get the longest array.
const longestArrayLength = arr.sort(({length: l1}, {length: l2}) => l1 - l2)[0].length;
// Acquire all elements at index [i] of each array and sum them. Yield the sum.
for (let i = 0; i < longestArrayLength; i++) yield arr.map(e => e[i]).reduce((a,b) => a + b, 0);
}
const result = [...sumVertically(arrayOfArrays.map(i => i.data))];
console.log(result);
Let's say I have an array such as: [1, 1, 2, 2, 3, 3, 4, 5]
And I want to remove this array of elements [1, 2, 3, 4, 5]
So in the end I want to be left with [1, 2, 3]
I have tried using the method below but it removes all copies of the elements from the main array.
myArray = myArray.filter( function( el ) {
return !toRemove.includes( el );
} );
Here is a way to do it using filter, indexOf and splice.
const input = [1, 1, 2, 2, 3, 3, 4, 5];
function removeSubset(arr, subset) {
const exclude = [...subset];
return arr.filter(x => {
const idx = exclude.indexOf(x);
if (idx >= 0) {
exclude.splice(idx, 1);
return false;
}
return true;
});
}
console.log(removeSubset(input, [1, 2, 3, 4, 5]));
You could get a Map and count the values and filter by checking the count and decrement the count if found.
var array = [1, 1, 2, 2, 3, 3, 4, 5],
remove = [1, 2, 3, 4, 5],
map = remove.reduce((m, v) => m.set(v, (m.get(v) || 0) + 1), new Map),
result = array.filter(v => !map.get(v) || !map.set(v, map.get(v) - 1));
console.log(result);
One solution is looping on the array of elements to remove and for each one remove the first element found on the input array:
const input = [1, 1, 2, 2, 3, 3, 4, 5];
const removeItems = (input, items) =>
{
// Make a copy, to not mutate the input.
let clonedInput = input.slice();
// Search and remove items.
items.forEach(x =>
{
let i = clonedInput.findIndex(y => y === x);
if (i >= 0) clonedInput.splice(i, 1);
});
return clonedInput;
}
console.log(removeItems(input, [1,2,3,4,5]));
console.log(removeItems(input, [1,2]));
console.log(removeItems(input, [1,99,44,5]));
If you still want to use filter, you can use the items to remove as the this argument of the filter, something like this:
const input = [1, 1, 2, 2, 3, 3, 4, 5];
const removeItems = (input, items) =>
{
return input.filter(function(x)
{
let i = this.findIndex(y => y === x);
return i >= 0 ? (this.splice(i, 1), false) : true;
}, items.slice());
}
console.log(removeItems(input, [1,2,3,4,5]));
console.log(removeItems(input, [1,2]));
console.log(removeItems(input, [1,99,44,5]));
You can use Filter and Shitf and Sort
let arr = [1, 1, 2, 2, 3, 3, 4, 5]
let remove = [1, 3, 2, 4, 5].sort((a,b)=>a-b)
let op = arr.sort((a,b)=>a-b).filter(e => ( remove.includes(e) ? (remove.shift(), false) : true ))
console.log(op)
What I'm trying to do is find how many times an array elements repeats itself in array, push the element along with the number of repeats it has in an object and after that delete the element and all its duplicates.
At the moment I have this function :
function getDuplicates(arr) {
let lastIndex = null;
let obj = {};
for ( let i = 0; i < arr.length; i++ ) {
lastIndex = arr.lastIndexOf(arr[i]);
obj[arr[i]] = lastIndex + 1;
arr.splice(0, lastIndex + 1 );
}
console.log(obj);
}
getDuplicates([ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6 ]);
which logs : { '1': 4, '2': 2, '3': 4, '5': 5 }
It works great for the first 3 numbers ( 1,2 and 3 ) but 4 doesnt show up, 5 is messed up and 6 doesnt show due to lastIndex +1. Am I missing something or is there a better way to do this ?
Thank you.
You can simplify a lot the logic. Just an object to count and an if statement to increment values or define as 1 if it wasn't defined.
function countDuplicates(arr) {
// Contains a pair of values an instances.
var counting = {};
// Iterate array: check if already counted. If yes, increment, if not define as 1.
for (el of arr) (counting[el]) ? counting[el]++ : counting[el] = 1;
console.log(counting);
return counting;
}
countDuplicates([ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6 ]);
Adding, if you also want to get the unique elements, you can just use E6 set:
var set = new Set([ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6 ]);
You can count and print as you would want like this:
function getDuplicates(arr) {
var counts = {};
arr.forEach(function(x) { counts[x] = (counts[x] || 0)+1; });
console.log(counts);
}
function getDuplicates(arr) {
let lastNum = null;
let obj = {};
for ( let i = 0; i < arr.length; i++ ) {
if (arr[i] != lastNum){
lastNum = arr[i];
obj[arr[i]] = 1;
}else{
obj[arr[i]]++;
}
}
console.log(obj);
}
You can simply use Array#reduce() to count the occurrences and Array#filter() to remove the duplicates
getDuplicates([1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6]);
function getDuplicates(arr) {
var obj = arr.reduce((map, item) => (map[item] = ++map[item] || 1, map),{} );
var withoutDup = arr.filter((item, pos) => arr.indexOf(item) == pos);
console.log(JSON.stringify(obj));
console.log(JSON.stringify(withoutDup));
}
Here's one method how to solve it.
Firstly I've removed all duplicated elements from the given array, using new Set() and then iterated over it using Array#forEach and checked with Array#filter how many times given element appears in the passed array.
function getDuplicates(arr){
var filtered = [...new Set(arr)],
result = {};
filtered.forEach(function(v){
result[v] = arr.filter(c => c == v).length;
})
console.log(result);
}
getDuplicates([ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6 ]);
Array#reduce solution.
function getDuplicates(arr) {
var res = arr.reduce(function(s, a) {
s[a] = arr.filter(c => c == a).length;
return s;
}, {});
console.log(res);
}
getDuplicates([1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6]);
It looks as if you want to COUNT duplicates, but if all you want to do is remove duplicates (As headline states), as per #ChantryCargill s suggestion:
function removeDuplicates (arr) {
var results = [];
for(var i = 0; i < arr.length; i++) {
var item = arr[i];
if(results.indexOf(item) === -1) {
results.push(item);
}
}
return results;
}
console.log(removeDuplicates([ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6 ]));
//[1, 2, 3, 4, 5, 6]
If you want to COUNT duplicates:
function getDuplicates(arr) {
var results = {};
for(var item of arr) {
if(!results[item]) {
results[item] = 0;
}
results[item]++;
}
return results;
}
console.log(getDuplicates([ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6 ]));
//{"1":4,"2":2,"3":4,"4":2,"5":3,"6":1}
Try this:
function getDuplicates(){
var numbers=Array.prototype.slice.call(arguments);
var duplicates={};
for(var index in numbers){
if(numbers.indexOf(numbers[index])==index)
continue;
duplicates[numbers[index]]= (duplicates[numbers[index]] || 0) + 1;
}
return duplicates;
}
console.log(getDuplicates(1,2,3,1,1,3,4,5,6,7,8,6));
/*
prints {
1: 2,
3: 1,
6: 1
}
*/
This question already has answers here:
How to remove repeated entries from an array while preserving non-consecutive duplicates?
(5 answers)
Closed 6 years ago.
I am trying to remove duplicates in an array in JavaScript. The given array being
array = [1,1,1,1,1,1,1,1,1,,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,1,1,1,1,1,2,2,2,2,2,2,2,2]
resultant_array = [1,2,3,1,2]
Here the second 1 is not considered as a duplicate
OR
array = [1,1,1,1,1,1,1,1,1,1,1,1]
resultant_array = [1]
any ideas how i can do this
You can use reduce like this:
var array = [1,1,1,1,1,1,1,1,1,,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,1,1,1,1,1,2,2,2,2,2,2,22];
var result = array.reduce(function(r, e) {
if(r[r.length - 1] != e) // if the last element in the result is not equal to this item, then push it (note: if r is empty then r[-1] will be undefined so the item will be pushed as any number is != undefined)
r.push(e);
return r;
}, []);
console.log(result);
var arr = [1,1,2,2,3,3];
var obj = {};
for(var i in arr) {
obj[arr[i]] = true;
}
var result = [];
for(var i in obj) {
result.push(i);
}
I set the keys of the object as the value of the array and there can't be multiple keys with the same value. Then I took all the keys and put it in the result.
You could check the predecessor with Array#reduce
var array = [1, 1, 1, 1, 1, 1, 1, 1, 1, , 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2],
result = array.reduce(function (r, a) {
return r[r.length - 1] === a ? r : r.concat(a);
}, []);
console.log(result);
Or use Array#filter and an object for the last value.
var array = [1, 1, 1, 1, 1, 1, 1, 1, 1, , 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2],
result = array.filter(function (a, i, aa) {
return this.last !== a && (this.last = a, true);
}, { last: undefined });
console.log(result);