I have the following data array:
const data = [
{
value: [
'a',
'b',
'a',
'a'
]
},
{
value: [
'c',
'c',
'd',
'c'
]
}
];
So there's is 4 combination here based on index:
combination 1 : a - c (index 0 in each value arrays)
combination 2 : b - c (index 1 in each value arrays)
combination 3 : a - d (index 2 in each value arrays)
combination 4 : a - c (index 3 in each value arrays)
As you can see the first and the last combinations are the same, so i want to remove the second occurrence from each array, the result should be:
[
{
value: [
'a',
'b',
'a'
]
},
{
value: [
'c',
'c',
'd'
]
}
]
You can zip the values arrays from both objects to form an array which looks like:
["a-c", "b-c", ...]
As these are now strings, you can turn this array into a Set using new Set(), which will remove all duplicate occurrences. You can then turn this set back into an array which you can then use .reduce() on to build you array of objects from. For each value you can obtain the list of values by using .split() on the '-', and from that, populate your reduced array.
See example below:
const data = [{ value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c' ] } ];
const unq = [...new Set(
data[0].value.map((_,c)=> data.map(({value})=>value[c]).join('-'))
)];
const res = unq.reduce((acc, str) => {
const values = str.split('-');
values.forEach((value, i) => acc[i].value.push(value));
return acc;
}, Array.from({length: data.length}, _ => ({value: []})))
console.log(res);
Limitations of the above method assume that you won't have a - character as your string value. If this is an issue, you can consider using a different delimiter, or find unique values within your array using .filter() instead of a Set.
You could save a lookup object for unique pairs of value based with index
Given your input is, below solution could help you
const data = [
{
value: ["a", "b", "a", "a"],
},
{
value: ["c", "c", "d", "c"],
},
]
const lookup = {}
data[0].value.forEach((_, index) => {
lookup[`${data[0].value[index]}-${data[1].value[index]}`] = true
})
const res = Object.keys(lookup).reduce(
(acc, key) => {
const [val1, val2] = key.split("-")
acc[0].value.push(val1)
acc[1].value.push(val2)
return acc
},
[{ value: [] }, { value: [] }]
)
console.log(res)
Below is a two step solution with a generator function and a single pass.
const data = [ { value: [ 'a', 'b', 'a', 'a' ] }, { value: [ 'c', 'c', 'd', 'c', ] } ];
const zipDataValues = function* (data) {
const iterators = data.map(item => item.value[Symbol.iterator]())
let iterations = iterators.map(iter => iter.next())
while (iterations.some(iteration => !iteration.done)) {
yield iterations.map(iteration => iteration.value)
iterations = iterators.map(iter => iter.next())
}
}
const filterOutDuplicateCombos = function (values) {
const combosSet = new Set(),
resultData = [{ value: [] }, { value: [] }]
for (const [valueA, valueB] of values) {
const setKey = [valueA, valueB].join('')
if (combosSet.has(setKey)) {
continue
}
combosSet.add(setKey)
resultData[0].value.push(valueA)
resultData[1].value.push(valueB)
}
return resultData
}
console.log(
filterOutDuplicateCombos(zipDataValues(data))
) // [ { value: [ 'a', 'b', 'a' ] }, { value: [ 'c', 'c', 'd' ] } ]
Here is a reference on generators and iterators
Filter combinations + sorting by the first occurrence:
const data = [{
value: ['a', 'b', 'a', 'a']
},{
value: ['c', 'c', 'd', 'c']
}];
var res = {}, i, t;
for (i = 0; i < data[0].value.length; ++i) {
res[data[0].value[i]] = res[data[0].value[i]] || {};
res[data[0].value[i]][data[1].value[i]] = true;
}
data[0].value = [];
data[1].value = [];
for (i in res) {
for (t in res[i]) {
data[0].value[data[0].value.length] = i;
data[1].value[data[1].value.length] = t;
}
}
console.log(data);
Related
I'm trying to do a group by over an array of objects. The array that I'm trying to group by is subject to change and I need a solution that's dynamic.
This is how the array that i'm trying to work on looks like.
const arr = [
{
first: {
label: 'a',
key: 'a'
},
second: {
label: 'b',
key: 'b',
}
},
{
first: {
label: 'aa',
key: 'aa'
},
second: {
label: 'bb',
key: 'bb',
}
}
]
I've tried this so far:
const result = arr.reduce((acc, curr) => {
acc['first'] = acc['first'] || [];
acc['second'] = acc['second'] || [];
acc['first'].push(curr.first);
acc['second'].push(curr.second);
return acc;
}, {});
This solves my problem, but it's not a dynamic solution.
This is the expected result:
const obj = {
first: [
{
label: 'a',
key: 'a'
},
{
label: 'aa',
key: 'aa'
}
],
second: [
{
label: 'b',
key: 'b'
},
{
label: 'bb',
key: 'bb'
}
]
}
To make this more general, you simply need to loop over the keys, rather than hardcoding your first/second code. That should look something like this:
const result = arr.reduce((acc, curr) => {
let keys = Object.keys(curr);
keys.forEach((key) => {
acc[key] = acc[key] || [];
acc[key].push(curr[key]);
});
return acc;
}, {});
You can use reduce and Object.entries
const arr = [{first: {label: 'a',key: 'a'},second: {label: 'b',key: 'b',}},{first: {label: 'aa',key: 'aa'},second: {label: 'bb',key: 'bb',}}]
let final = arr.reduce((op, inp) => {
Object.entries(inp).forEach(([key, value]) => {
op[key] = op[key] || []
op[key].push(value)
})
return op
},{})
console.log(final)
This question already has answers here:
How to sort in specific order - array prototype(not by value or by name)
(4 answers)
Closed 3 years ago.
const order = ['b', 'c', 'a'];
const objects = [
{ name: 'a' },
{ name: 'b' },
{ name: 'c' },
];
Trying to figure out the most efficient way to sort the objects array by name using the manual order array.
Here is a quick use of sort plus indexOf.
const order = ['b', 'c', 'a'];
const objects = [
{ name: 'a' },
{ name: 'b' },
{ name: 'c' },
];
const sortedObjects = objects.sort((o1, o2) => order.indexOf(o1.name) - order.indexOf(o2.name));
console.log(sortedObjects);
With cached indices:
const order = ['b', 'c', 'a'].reduce((acc, elt, index) => (acc[elt] = index, acc), {});
const objects = [
{ name: 'a' },
{ name: 'b' },
{ name: 'c' },
];
const sortedObjects = objects.sort((o1, o2) => order[o1.name] - order[o2.name]);
console.log(sortedObjects);
You can cache the indices using Object.entries() and Object.fromEntries() to re-arrange the order object into a lookup table:
const order = ['b', 'c', 'a'];
const objects = [
{ name: 'a' },
{ name: 'b' },
{ name: 'c' },
];
const lut = Object.fromEntries(
Object.entries(order).map(entry => entry.reverse())
);
objects.sort((a, b) => lut[a.name] - lut[b.name]);
console.log(objects);
I'm trying to duplicate each element in an array, but using functional style.
I have this currently:
["a", "b", "c"]
And I'm getting this:
["a","a","b","b","c","c"]
So far I have tried the following, mapping each element to an array, then using flat() to get a 1d array. Is there a cleaner way because it feels like I'm abusing map and flat.
["a", "b", "c"].map(item => [item, item]).flat();
Is there a better way to do this?
I was trying to provide a example as simple as possible but left some details out. The real input is not sorted because elements are not comparable.
It's something like:
[
{
a:"a"
b:"b"
},
{
c: 1
d: 2
},
{
apple: {},
sellers: ["me", "her"]
}
]
The duplicated result should be something like this, where duplicated elements are next to each other:
[
{
a:"a"
b:"b"
},
{
a:"a"
b:"b"
},
{
c: 1
d: 2
},
{
c: 1
d: 2
},
{
apple: {},
sellers: ["me", "her"]
},
{
apple: {},
sellers: ["me", "her"]
}
]
Array.reduce is semantically the appropriate method here: take an object (in this case an array) and return an object of a different type, or with a different length or shape (note: edited to use Array.push for faster performance per #slider suggestion):
EDIT: I've edited my answer to reflect OP's updated input data. Note also, that this solution is cross-browser and NodeJS compatible without requiring transpilation.
let data = [
{
a:"a",
b:"b",
},
{
c: 1,
d: 2
},
{
apple: {},
sellers: ["me", "her"]
}
];
let result = data
.reduce((acc, el) => {
acc.push(el, el);
return acc;
}, []);
console.log(JSON.stringify(result, null, 2));
Otherwise you could map each element, duplicating it, then combine them:
let data = [
{
a:"a",
b:"b",
},
{
c: 1,
d: 2
},
{
apple: {},
sellers: ["me", "her"]
}
];
let result = data.map(item => [item, item]).reduce((acc, arr) => acc.concat(arr));
console.log(JSON.stringify(result, null, 2));
As mentioned in other answers here, either of these approaches have the advantage of not requiring the original array to have been sorted.
You can use the function reduce and concatenate the same object on each iteration.
let array = ["a", "b", "c"],
result = array.reduce((a, c) => a.concat(c, c), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I would recommend Array.prototype.flatMap -
const twice = x =>
[ x, x ]
console .log
( [ 'a', 'b', 'c' ] .flatMap (twice) // [ 'a', 'a', 'b', 'b', 'c', 'c' ]
, [ 1, 2, 3, 4, 5 ] .flatMap (twice) // [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 ]
)
flatMap is useful for all kinds of things -
const tree =
[ 0, [ 1 ], [ 2, [ 3 ], [ 4, [ 5 ] ] ] ]
const all = ([ value, ...children ]) =>
[ value ] .concat (children .flatMap (all))
console .log (all (tree))
// [ 0, 1, 2, 3, 4, 5 ]
really cool things -
const ranks =
[ 'J', 'Q', 'K', 'A' ]
const suits =
[ '♡', '♢', '♤', '♧' ]
console .log
( ranks .flatMap (r =>
suits .flatMap (s =>
[ [ r, s ] ]
)
)
)
// [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧']
// , ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧']
// , ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧']
// , ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧']
// ]
flatMap is just a specialised Array.prototype.reduce and is easy to implement in environments where Array.prototype.flatMap is not already supported -
const identity = x =>
x
const flatMap = (xs = [], f = identity) =>
xs .reduce ((r, x) => r . concat (f (x)), [])
const ranks =
[ 'J', 'Q', 'K', 'A' ]
const suits =
[ '♡', '♢', '♤', '♧' ]
console.log
( flatMap (ranks, r =>
flatMap (suits, s =>
[ [ r, s ] ]
)
)
)
// [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧']
// , ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧']
// , ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧']
// , ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧']
// ]
You could just do this:
var arr = ["a", "b", "c"];
arr = arr.concat(arr).sort();
This is one of the simplest methods to do what you are asking to do.
The simplest solution is to use flatMap():
const source = ["a", "b", "c"];
const result = source.flatMap(item => [item, item]);
[ 'a', 'a', 'b', 'b', 'c', 'c' ]
A little bit of classic:
let source = ["a", "b", "c"];
const originalLength = source.length;
for(let i = 0; i <= originalLength + (originalLength - 2); i++) {
source.splice(i, 0, source[i++]);
}
[ 'a', 'a', 'b', 'b', 'c', 'c' ]
I want to create an object with the given structure:
const questions = [
{
id: '1',
instruction: 'Question1',
options: {
'a': 'SomeText1',
'b': 'SomeText2',
'c': 'SomeText3'
},
correct: ['c']
},
{
id: '2',
instruction: 'Question2',
options: {
'a': 'SomeText1',
'b': 'SomeText2',
'c': 'SomeText3',
'd': 'SomeText4'
},
correct: ['a']
},
{
id: '3',
instruction: 'Question3,
options: {
'a': 'SomeText1',
'b': 'SomeText2'
},
correct: ['b']
}
]
I have an arrays containing necessary information to fill this object and create it using .map() .
const questions =
[ 'Question1',
'Question2',
'Question3'
]
const answers =
[
'Answer1',
'Answer2',
'Answer3'
]
const options = [
[ 'Option1', 'Option2', 'Option3' ],
[ 'Option4', 'Option5', 'Option6' ],
[ 'Option7', 'Option8', 'Option9' ]
]
function toJson()
const alphabet = ['a', 'b', 'c', 'd', 'e'];
const json = questions.map((question, index) => (
{
id: index + 1,
instruction: question,
options: Object.assign({}, options[index]),
correct: answers[index]
}
))
}
I have only problem with options key. As You see I want to have a letters as keys, depending on how many answers question has.
This function gives me numbers as keys when I use Object.assign(), and I don't know how to replace them with letters from alphabet array.
EDIT:
So the solution for the options key in desired object is:
options: Object.assign(
{},
...options[index].map((a, i) => ({ [alphabet[i]]: a }))
),
Now I'm able to create an object with consecutive alphabet letters with assigned answer.
options[index] returns an array. It contains values by index. By passing it to Object.assign, you add all values by their array index as a string: "0", "1", etc.
If we map the array in to a list of { "a": option } first, and spread the result in to the Object.assign call, we can change those indexes to the letters you want:
const questions =
[ 'Question1',
'Question2',
'Question3'
]
const answers =
[
'Answer1',
'Answer2',
'Answer3'
]
const options = [
[ 'Option1', 'Option2', 'Option3' ],
[ 'Option4', 'Option5', 'Option6' ],
[ 'Option7', 'Option8', 'Option9' ]
]
function toJson() {
const alphabet = ['a', 'b', 'c', 'd', 'e'];
const json = questions.map((question, index) => (
{
id: index + 1,
instruction: question,
options: Object.assign(
{},
...options[index].map((a, i) => ({ [alphabet[i]]: a }))
),
correct: answers[index]
}
));
return json;
}
console.log(toJson());
I suggest using something like zip and objectFromPairs (both snippets from 30secondsofcode, a project/website I am a maintainer of). From the website:
zip
Creates an array of elements, grouped based on the position in the original arrays.
Use Math.max.apply() to get the longest array in the arguments. Creates an array with that length as return value and use Array.from() with a map-function to create an array of grouped elements. If lengths of the argument-arrays vary, undefined is used where no value could be found.
objectFromPairs
Creates an object from the given key-value pairs.
Use Array.reduce() to create and combine key-value pairs.
The only extra step I took was to trim each zipped array to the length of options[index].
const questions = ['Question1',
'Question2',
'Question3'
]
const answers = [
'Answer1',
'Answer2',
'Answer3'
]
const options = [
['Option1', 'Option2', 'Option3'],
['Option4', 'Option5', 'Option6'],
['Option7', 'Option8', 'Option9']
]
const zip = (...arrays) => {
const maxLength = Math.max(...arrays.map(x => x.length));
return Array.from({
length: maxLength
}).map((_, i) => {
return Array.from({
length: arrays.length
}, (_, k) => arrays[k][i]);
});
};
const objectFromPairs = arr => arr.reduce((a, [key, val]) => ((a[key] = val), a), {});
function toJson() {
const alphabet = ['a', 'b', 'c', 'd', 'e'];
const json = questions.map((question, index) => ({
id: index + 1,
instruction: question,
options: objectFromPairs(zip(alphabet, options[index]).slice(0, options[index].length)),
correct: answers[index]
}))
console.log(json);
}
toJson();
the below should work (i believe)
options: alphabet.reduce((acc, letter, i) => {
let option = options[index][i] || 'DefaultText' + i;
acc[letter] = option;
return acc;
}, {})
Edit: Corrected typos
EDIT:
So the solution for the options key in desired object is:
options: Object.assign(
{},
...options[index].map((a, i) => ({ [alphabet[i]]: a }))
),
I need to order an array of objects, composed by a name and a dependencies list (made out of names).
An example of this array could be:
[
{ name: 'a', requires: ['b', 'c'] },
{ name: 'b', requires: ['c'] },
{ name: 'c', requires: [] },
]
I'd like this array to be sorted so that the items which require a specific set of dependencies will be positioned after its required dependencies.
The array could actually contain more items, I'm okay if the sorting function throws an error in case of circular dependencies.
Example output:
[
{ name: 'c', requires: [] }, // first, no dependencies, and required by both the others
{ name: 'b', requires: ['c'] }, // second, because it needs `c` first
{ name: 'a', requires: ['b', 'c'] }, // last, because requires both the others
]
What's the most concise way to do it?
You can try following (changed test case to support more possible combinations)
var arr = [
{ name: 'd', requires: ['a', 'c'] },
{ name: 'a', requires: ['b', 'c'] },
{ name: 'b', requires: ['c'] },
{ name: 'e', requires: ['d'] },
{ name: 'c', requires: [] },
];
var map = {}; // Creates key value pair of name and object
var result = []; // the result array
var visited = {}; // takes a note of the traversed dependency
arr.forEach(function(obj){ // build the map
map[obj.name] = obj;
});
arr.forEach(function(obj){ // Traverse array
if(!visited[obj.name]) { // check for visited object
sort_util(obj);
}
});
// On visiting object, check for its dependencies and visit them recursively
function sort_util(obj){
visited[obj.name] = true;
obj.requires.forEach(function(dep){
if(!visited[dep]) {
sort_util(map[dep]);
}
});
result.push(obj);
}
console.log(result);
Update: thanks to Nina Scholz, I updated the code so that sort should work
This might do the job.
The idea behind is, to user the sort and check if element a is in the requires of element b. If so, we can assume, that ashould be before b.
But I´m not 100% sure, I just checked against your example and the example of #nikhilagw. I might have forgotten something. Please let me know if it worked!
For every element, I additionally inherit all dependencies.
const list = [
{ name: 'b', requires: ['c'] },
{ name: 'e', requires: ['d'] },
{ name: 'd', requires: ['a', 'c'] },
{ name: 'c', requires: [] },
{ name: 'a', requires: ['b', 'c'] },
];
// indexed by name
const mapped = list.reduce((mem, i) => {
mem[i.name] = i;
return mem;
}, {});
// inherit all dependencies for a given name
const inherited = i => {
return mapped[i].requires.reduce((mem, i) => {
return [ ...mem, i, ...inherited(i) ];
}, []);
}
// order ...
const ordered = list.sort((a, b) => {
return !!~inherited(b.name).indexOf(a.name) ? -1 : 1;
})
console.log(ordered);
This proposal looks for previous elements and checks if the actual element has the wanted requirements sorted before.
If all requirements are found the object is spliced to the index.
function order(array) {
var i = 0,
j,
temp;
while (i < array.length) {
temp = array.slice(0, i);
for (j = i; j < array.length; j++) {
if (array[j].requires.every(n => temp.some(({ name }) => n === name))) {
array.splice(i++, 0, array.splice(j, 1)[0]);
break;
}
}
}
return array;
}
var array = [{ name: 'd', requires: ['a', 'c'] }, { name: 'a', requires: ['b', 'c'] }, { name: 'b', requires: ['c'] }, { name: 'e', requires: ['d'] }, { name: 'c', requires: [] }];
console.log(order(array));
.as-console-wrapper { max-height: 100% !important; top: 0; }
After several years I found this super short solution to the problem, a friend of mine shared it with me, I don't take credits.
elements.sort((a, b) =>
a.requires.includes(b.name) ? 1 : b.requires.includes(a.name) ? -1 : 0
);
Your data can be represented via graph. So you could use topological sort for this problem.