Split an array-object without replication - javascript

I'm learning how to handle js arrays and I was wondering if it's possible to create new array-objects by splitting an attribute of an existing object.
I tried to do that with .map and .flatMap, but the output gives me combinations of objects that replicate the other values, while I'm looking for unique objects
I think that the code can be more clear:
const array=[
{ names:['something1', 'something2'],
state:false,
features:['feature1','feature2']
},
{ names:['something3', 'something4'],
state:true,
features:['feature3','feature4']
},
]
array.flatMap(({names,state,features}) => {
names.flatMap(name => {
features.flatMap(feature => {
console.log(({name,state,feature}));
})
})
})
So, with this code the output is:
{ name: 'something1', state: false, feature: 'feature1' }
{ name: 'something1', state: false, feature: 'feature2' }
{ name: 'something2', state: false, feature: 'feature1' }
{ name: 'something2', state: false, feature: 'feature2' }
{ name: 'something3', state: true, feature: 'feature3' }
{ name: 'something3', state: true, feature: 'feature4' }
{ name: 'something4', state: true, feature: 'feature3' }
{ name: 'something4', state: true, feature: 'feature4' }
But I want the output to be:
{ name: 'something1', state: false, feature: 'feature1' },
{ name: 'something2', state: false, feature: 'feature2' },
{ name: 'something3', state: true, feature: 'feature3' },
{ name: 'something4', state: true, feature: 'feature4' }
I'm a newbie in coding, sorry if my words are not properly correct in describing this problem.
Thanks for your patience

You can use .flatMap() with an inner .map() function (instead of a .flatMap() like you are doing) to map each element in the names array to its own respective object with it's associated feature.
See example below:
const array = [{
names: ['something1', 'something2'],
state: false,
features: ['feature1', 'feature2']
},
{
names: ['something3', 'something4'],
state: true,
features: ['feature3', 'feature4']
},
];
const res = array.flatMap(
({names, state, features}) => names.map((name, i) => ({name, state, feature: features[i]}))
);
console.log(res);

Here you go, slight edit on last one. Just map names.
const arr = [{
names: ["test1", "test2"],
values: ["t1", "t2"]
},
{
names: ["test3", "test4"],
values: ["t3", "t4"]
}];
const flat = arr.reduce((a, {names, values}) => {
names.map((name, i) => {
a.push({ name, value: values[i]});
});
return a;
}, []).flat();
console.log(`Flat: ${JSON.stringify(flat)}`);

If you want to learn programming, the magics might not be the best pick. map(), reduce() and the like are algorithms themselves. The basics would be completing these tasks with simple loops (for, while) and sometimes recursion (like the general solution for the origin of this question).
What you have at the moment is an array, which you can iterate over in an outer loop, and inside there are objects with parallel arrays, which you could iterate over also, in an inner loop:
const array=[
{ names:['something1', 'something2'],
state:false,
features:['feature1','feature2']
},
{ names:['something3', 'something4'],
state:true,
features:['feature3','feature4']
},
];
for(let outer=0;outer<array.length;outer++){
let obj=array[outer];
for(let inner=0;inner<obj.names.length;inner++)
console.log({
name:obj.names[inner],
state:obj.state,
feature:obj.features[inner]
});
}
Then yes, the outer loop does not need the index at all, so it could directly iterate over the elements (for(let obj of array) or array.forEach()), but for the inner loop you need the index, so it is not that trivial to throw it away (see the strange inbalance with the suggested map() variants: they have name, and features[i] - where of course name is actually names[i] extracted already, but it hides a bit of the fact that the two arrays are traversed in parallel).

Related

searching list of objects by key and returning count

I am using MongoDB with a Node API and one route needs to return a summary count of each type in a collection.
I am not using the MongoDB Aggregate pipelines because the data I need has already been sent to the API for other summary statistics in the same route.
Note: I have put the _id's below in single quotes for ease of use but they are mongoose.Schema.Types.ObjectId's.
So, given that I have an array of mongo objects like this:
const allObs = [
{
_id: '60d5f37fd93fb82ebe84d920',
type: '60d5f1d4cdc8942dc5b6b12e',
otherFields: 'about 10 - removed for clarity'
},
{
_id: '60d5f389d93fb82ebe84d926',
type: '60d5f1d4cdc8942dc5b6b12e',
otherFields: 'ditto'
},
{
_id: '60d5f39bd93fb82ebe84d92c',
type: '60d5f1e3cdc8942dc5b6b138',
otherFields: 'foobarbarfoo'
}
]
and I have a lookup table like this...
const lookupTable = [
{ _id: '60d5f1d4cdc8942dc5b6b12e', type: 'duck' },
{ _id: '60d5f1decdc8942dc5b6b133', type: 'goose' },
{ _id: '60d5f1e3cdc8942dc5b6b138', type: 'crane' },
{ _id: '60d5f1e9cdc8942dc5b6b13d', type: 'heron' }
]
How can I go about creating a summary table like this?
[
{ name: 'duck', data: [2] },
{ name: 'crane', data: [1] }
]
The resulting table structure is a bit odd (data with single value arrays) but we need this structure for Apex Charts.
Any help would be great, thank you.
There are multiple ways to do this, but the basic logic is doing a groupBy and match with lookup table. It would be easier to do with lodash or a helper library. But also without using JS it can be done pretty easily.
For a quick solution u can use this:
//Group by type and then storing the count
const grouped = allObs.reduce((p, c) => {
p[c.type] = p[c.type] || 0;
p[c.type] += 1;
return p;
}, {});
// putting that into a result array.
const result = lookupTable
.filter(entry=>grouped[entry._id]) //filtering whatever is not there
.map(entry => {
return { name: entry.type, data: [grouped[entry._id]] }
});
You can do it in single pass using a good old for loop.
Output:
[ { name: 'duck', data: [ 2 ] }, { name: 'crane', data: [ 1 ] } ]

Javascript - transform an object of array list to another format?

So I've been stumped on this for hours and I can't really figure out an elegant solution to solve this problem. Let's say I have this:
let Fields = {
GAME: [
{ code: '{{GTAV}}', title: { en: "grnti"} },
{ code: '{{GTA5}}', title: { en: "Grand theph " } },
]
};
How can I turn this into a new format that looks like this ?
let Fields = {
tags: [
{ name: 'GAME', tags:[
{ name: 'grnti', value: "{{GTAV}}" },
{ name: 'Grand theph', value: "{{GTA5N}}" }
]},
]};
I tried to create a function to do the job , but for some reason my brain cannot seem to grasp the solution. Any help please !
A simple version of this might look like the following:
const transform = (fields) => ({
mergeTags: Object .entries (fields) .map (([name, innerFields]) => ({
name,
mergeTags: innerFields .map (({code, title: {en}}) => ({name: en, value: code}))
}))
})
const fields = {RECIPIENT: [{code: '{{RECIPIENT.LN}}', title: {en: "name"}}, {code: '{{RECIPIENT.FN}}', title: {en: "first name" }}]}
console .log (transform (fields))
But from your nested mergeTags properties, I'm guessing that there is something recursive going on. If so, we need more information about the input and output structures.
i just threw a nested reduce function together.
const transformed = Object.entries(Fields).reduce((tags, [key, value]) => {
const mergedTags = value.reduce((codes, code) => {
codes.mergeTags.push({name: code.title.en, value: code.code});
return codes;
}, {name: key, mergeTags: []})
tags.mergeTags.push(mergedTags)
return tags;
}, {mergeTags: []})
Does that work for you?
It is hard to tell exactly from your question what you are hoping to accomplish as well as the shape of your data. Based on your question though, you would probably want to use the Object.keys and map functions
let Fields = {
RECIPIENT: [
{ code: '{{RECIPIENT.LN}}', title: { en: "name" } },
{ code: '{{RECIPIENT.FN}}', title: { en: "first name" } },
]
};
// gets the keys of the 'Fields' object(in this case only 'RECIPIENT'
let newFields = Object.keys(Fields)
// each key should create a new object with the 'key' from the original object as the 'name' of the new object
.map(key => ({
name: key,
// 'Fields[key]' gets the array from the 'RECIPIENT' property and then creates a new object from each object in the original array, mapping the 'title.en' property in the original object to 'name' in the new object and 'code' in the original object to 'value' in the new object
mergeTags: Fields[key].map(property => ({
name: property.title.en,
value: property.code
}))
}));
console.log(newFields);
Here's a clean way that may seem a bit like magic, but I'll walk you through what's going on.
let Fields = {
RECIPIENT: [
{ code: '{{RECIPIENT.LN}}', title: { en: "name"} },
{ code: '{{RECIPIENT.FN}}', title: { en: "first name" } },
]
};
const { pipe, fork, map, get } = rubico
const Transformed = pipe([
Object.entries, // { RECIPIENT: [...] } => [['RECIPIENT', [...]]
fork({
mergeTags: map(fork({ // iterate through each entry ['RECIPIENT', [...]]
name: get(0), // name is the item at index 0 of each entry
mergeTags: pipe([
get(1), // mergeTags starts with index 1 of each entry, the array of code+title objects
map(fork({ // iterate through the array of code + title objects and create new objects
name: get('title.en'), // name is title.en of each object
value: get('code'), // value is title.code of each object
})),
]),
})),
}),
])(Fields)
console.log(JSON.stringify(Transformed, null, 2))
<script src="https://unpkg.com/rubico"></script>
Disclaimer: I am the author of rubico
You can examine these methods in depth at the documentation

How to properly enumerate object elements in a state array?

I've begun learning React and have been following along with a tutorial while also experimenting along the way.
Something I was wondering was, if I have a state array containing objects, each of which have a property for their ID #, what is the best/most correct way to enumerate the objects? Below is what I have, which does work:
class Counters extends Component {
i = 0;
state = {
counters: [
/* array of counter objects */
{ id: ++this.i, value: 0 },
{ id: ++this.i, value: 0 },
{ id: ++this.i, value: 0 },
{ id: ++this.i, value: 0 }
]
};
I was wondering if this is considered "proper," and if not, what the correct way to do this is.
Thanks!
One way you can do is
state = {
counters: Array(numberOfCounters).fill().map((x, i) => ({id: i, value: 0}))
}
console.log(Array(10).fill().map((x, i) => ({id: i, value: 0})))

Checking if this is a Stack/DFS

I'm doing some work that involves grabbing some specific nodes in a tree-like structure. My colleague claims that my implementation, which we are trying to use a stack and a DFS algorithm, is not that.
Here is my implementation of using a stack to create a basic DFS algorithm:
const findMatchingElements = (node, name, result) => {
for(const child of node.children) {
findMatchingElements(child, name, result)
}
if(node.name === name) result.push(node)
return result
}
const getElements = (tree, name) => {
return findMatchingElements(tree, name, [])
}
getElements(obj, 'foo')
And a sample input:
const obj = {
id: 1,
name: 'foo',
children: [
{
id: 45,
name: 'bar',
children: [
{
id: 859,
name: 'bar',
children: []
}
]
},
{
id: 67,
name: 'foo',
children: [
{
id: 456,
name: 'bar',
children: []
},
{
id: 653,
name: 'foo',
children: []
}
]
}
]
}
I am getting my expected output:
[ { id: 653, name: 'foo', children: [] },
{ id: 67, name: 'foo', children: [ [Object], [Object] ] },
{ id: 1, name: 'foo', children: [ [Object], [Object] ] } ]
In the order I expect as well, but my colleague for some reason does not think this is a proper stack implementation. Am I missing something? Is it because of the way the final answer is printed out? To me, this feels like it is a stack.
I'm a bit confused about what you're disagreeing about here but that output looks like a stack to me if you agree that it's LIFO once the client starts using it.
Right now it's just a JavaScript array, but if you start pushing and popping on it, and only doing that, then it's a JavaScript implementation of a stack.
Because of the order of the lines in your recursive function, you are using a post-order traversal in your DFS. In your implementation, there's no such thing as in-order. Your co-worker might have been expecting pre-order DFS. To convert your algorithm to pre-order, just check the node before visiting the children nodes.
const findMatchingElements = (node, name, result) => {
if(node.name === name) result.push(node)
for(const child of node.children) {
findMatchingElements(child, name, result)
}
return result
}
Reference
You are using stack implicitly via recursion. I guess your colleague means your implementation does not use stack explicitly without recursion.

How to generate new array of objects from existing array of objects using lodash

Can someone help me generate a new array of objects from an existing one using lodash? I've been trying a combination of _.zipObject and map but to no avail... basically, I have an array of objects like:
const names = [
{
first_name: 'nedd',
given_name: 'cersei'
},
{
first_name: 'tyrion',
given_name: 'tywin'
}
]
However, I want it to look like:
[
{
name: 'nedd'
},
{
name: 'cersei'
},
{
name: 'tyrion'
},
{
name: 'tywin'
},
]
I have tried various iterations of:
const newArray = _.zipObject( names, _.fill( Array(names.length), {name: ['first_name' || 'given_name']} ) );
But without any luck... can someone help?
Thanks in advance!
This might work:
_.flatMap(names, (n)=> [{name: n.first_name}, {name: n.given_name}]);
Use _.flatMap combined with _.map:
_.flatMap(names, (nameObj) => _.map(nameObj, (objVal) => { return { name: objVal }; }));

Categories