Related
I have an array that looks like this: [[3, Apple], [4, Banana], [7, Orange], [9, Pear]]
Now I'd like to add all missing numbers from 1 to 10 with empty entries where I have the fruit in the example, so that as result I'd have:
[
[1, ],
[2, ],
[3, Apple],
[4, Banana],
[5, ],
[6, ],
[7, Orange],
[8, ],
[9, Pear]
[10, ]
]
I'd share what I've tried so far, but I really am stuck at the beginning. Has anybody an idea how to accomplish that?
let result = []
let fruits = [[3, 'Apple'], [4, 'Banana'], [7, 'Orange'], [9, 'Pear']]
let fruitsObject = Object.fromEntries(fruits)
for (let i = 1; i<=10; i++){
result.push(fruitsObject[i] ? [i, fruitsObject[i]] : [i])
}
console.log(result)
You can start by creating an array with indexes only, and then iterate over your data to fill in the missing values from your input.
const data = [[3, "Apple"], [4, "Banana"], [7, "Orange"], [9, "Pear"]]
const result = data.reduce((result, [id, val]) => {
result[id - 1].push(val);
return result;
}, Array.from({length: 10}, (_, i)=> [i + 1]))
console.log(result);
Here 2nd argument of the reduce function is an array of length 10, filled with 1 element arrays, where element is an index + 1.
The first argument is a function called on every element of your input data, that modifies the 2nd argument.
A kind of over-engineering way. Don't be so hard on me.
const sourceArr = [[3, 'Apple'], [4, 'Banana'], [7, 'Orange'], [9, 'Pear']];
const sourceObj = Object.fromEntries(sourceArr);
const nullArr = [...Array(10).keys()].map(i => [i+1]);
const nullObj = Object.fromEntries(nullArr);
const unionObj = { ...nullObj, ...sourceObj };
const pairs = Object.entries(unionObj)
const result = pairs.map(pair => pair.filter(e => e));
console.log(result);
.as-console-wrapper{min-height: 100%!important; top: 0}
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);
I'm trying to solve this problem. Essentially, I have a array of keys, and an array of values within objects, and I want those values to have keys.
Below is my best attempt so far - usually use python so this is a bit confusing for me.
var numbers = [3, 4, 5,6]
var selection = [[1, 2, 3, 4], [6, 5, 4, 3], [2, 9, 4]]
var result = [];
for (arr in selection) {
numbers.forEach(function (k, i) {
result[k] = arr[i]
})
};
console.log(result);
The output I'm looking for is like this,
results = [{3:1,4:2,5:3,6:4}, {..},..]
Love some pointers to getting the right output.
Note. This is for google appscript! So can't use certain javascript functions (MAP I think doesn't work, unsure of reduce).
Cheers!
Use map on selection and Object.assign
var numbers = [3, 4, 5, 6];
var selection = [
[1, 2, 3, 4],
[6, 5, 4, 3],
[2, 9, 4]
];
var result = selection.map(arr =>
Object.assign({}, ...arr.map((x, i) => ({ [numbers[i]]: x })))
);
console.log(result);
Create a separate function which take keys and values as arguments and convert it into object using reduce(). Then apply map() on selections and make an object for each subarray using that function
var numbers = [3, 4, 5,6]
var selection = [[1, 2, 3, 4], [6, 5, 4, 3], [2, 9, 4]]
function makeObject(keys, values){
return keys.reduce((obj, key, i) => ({...obj, [key]: values[i]}),{});
}
const res = selection.map(x => makeObject(numbers, x));
console.log(res)
Create a new object from scratch for each number array:
const selection = [
[1, 2, 3, 4],
[6, 5, 4, 3],
[2, 9, 4],
];
function objMaker(numarr) {
const numbers = [3, 4, 5, 6];
numarr.forEach((num, i) => (this[numbers[i]] = num));
}
console.info(selection.map(numarr => new objMaker(numarr)));
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))
I have nested array data and I would like to extract all nested arrays to be siblings of their parent. I am pretty close, but I am getting an extra empty array in the results and I cannot figure out where it is coming from or how to get rid of it.
Note: I would really like to understand why this is happening and how to get rid of it in my function, and not just a .filter(arr => arr.length) on my results list.
This is my attempt so far:
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
];
// Desired Output
// [
// [1, 2, 5],
// [3, 4],
// [6],
// [7, 8, 9],
// [10, 11],
// [12, 13],
// [14, 15],
// [16, 17],
// [4, 4]
// [1]
// [1, 1]
// ]
function extractArrays (arr) {
return arr.reduce((res, curr) => {
if (Array.isArray(curr)) {
res = res.concat(extractArrays(curr));
}
else {
res[0].push(curr);
}
return res;
}, [[]]);
}
console.log(extractArrays(arrs));
// Results:
// [
// [], <-- Where is this coming from?
// [ 1, 2, 5 ],
// [ 3, 4 ],
// [ 6 ],
// [ 7, 8, 9 ],
// [ 10, 11 ],
// [ 12, 13 ],
// [], <-- Also here
// [ 14, 15 ],
// [ 16, 17 ],
// [ 4, 4 ],
// [ 1 ],
// [ 1, 1 ]
// ]
.as-console-wrapper {
max-height: 100% !important;
}
Element like [[14, 15], [16, 17]] will introduce a [] after recursion. This should be handled by checking length.
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
];
function extractArrays (arr, acc=[]) {
if (arr.length == 0 ) return acc;
let pure = arr.filter(elm => !Array.isArray(elm));
if (pure.length > 0) {
acc.push(pure);
}
acc.concat(arr.filter(elm => Array.isArray(elm)).map(elm => extractArrays(elm, acc)));
return acc;
}
console.log(extractArrays(arrs));
You can try the following code
var arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[
[14, 15],
[16, 17]
], // <-- added additional test case
[
[1], 4, [1, 1], 4
]
];
function extractArrays(arr) {
return arr.reduce((res, curr, i) => {
if (Array.isArray(curr)) {
res = res.concat(extractArrays(curr));
} else {
let index = 0;
for (let j = 0; j <= i; j++) {
if (!Array.isArray(arr[j])) {
res[index] ? res[index].push(curr) : res.push([curr]);
break;
} else {
index++;
}
}
}
return res;
}, []); // <-- no initial empty array inside here
}
console.log(extractArrays(arrs));
I just wanted to share my approach to this problem, I enjoyed trying to solve it, in my case I also passed an array to the extractArrays method, in order to make easier to capture and filter every array inside the arrs param.
let result = [];
extractArrays(arrs, result);
console.log(result);
function extractArrays(arr, result) {
let newResult = arr.reduce((acc, curr) => {
if (Array.isArray(curr)) {
extractArrays(curr, result);
} else {
acc.push(curr);
}
return acc;
}, []);
newResult.length && result.push(newResult);
}
You can check it when you return from function. stackblitz
function extractArray(arr) {
const res = arr.reduce((res, curr) => {
if(!Array.isArray(curr)){
return [[...res[0], curr], ...res.slice(1)]
}
return [...res, ...extractArray(curr)]
}, [[]]);
return res[0].length ? res : res.slice(1);
}
EDIT: More performant function (check stackblitz link)
function extractFaster(arr) {
let res = [0];
function recExtract(arr) {
let hasNonArrayElm = false;
let index = res.length -1;
arr.forEach(curr => {
if (!Array.isArray(curr)) {
hasNonArrayElm ? res[index].push(curr) : res.splice(index, 0, [curr]);
hasNonArrayElm = true;
return;
}
recExtract(curr);
});
}
recExtract(arr);
res.splice(-1, 1)
return res;
}
EDIT: The answer below the line is a great way to flatten arrays, but I suggested it because I misunderstood this question. I will leave it in case it benefits someone to know, but in order to keep an accurate record, I'll also update my answer to address the problem posed in the question.
The accepted answer seems sufficient enough, but I'll try my hand at it. I would use Array.reduce to cover everything with one swoop, and inside use Array.filter to separate the normal items from the array items, then use the spread ... operator on the nested arrays so everything gets pushed to the same level, after recursively calling the same extract function on all nested arrays. Honestly, the explanation might be harder to understand than the code, have a look:
const data = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
]
const extractChildArrays = arrs => arrs.reduce((acc, cur) => {
const nestedArrs = cur.filter(a => Array.isArray(a))
const normalItems = cur.filter(a => !Array.isArray(a))
acc.push(normalItems, ...extractChildArrays(nestedArrs))
return acc
}, [])
console.log(extractChildArrays(data))
UPDATE: Array.flat() is now accepted as part of the spec and it's supported by all modern browsers except Edge.
In ES6 there is actually an experimental array method called flat(). As of the writing of this answer, it's only compatible with Chrome, but a polyfill might be worth looking into, because it's SO EASY!
The first parameter of flat() is depth, so with the help of another answer, you can easily figure that out dynamically.
const data = arrs = [
[1, 2, [3, 4], 5],
[6, [7, 8, 9, [10, 11]]],
[12, 13],
[[14, 15], [16, 17]],
[[1], 4, [1, 1], 4]
]
const flattenArray = arr => {
const getDepth = a => Array.isArray(a) ?
1 + Math.max(...a.map(getDepth)) : 0
return arr.flat(getDepth(arr))
}
console.log(flattenArray(data))