How to properly enumerate object elements in a state array? - javascript

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})))

Related

Find deep nested array depth

I have a nested array. Like below:
I want to find the depth of this nested array, which means the child element has most deep nested children.
let arr = [
{
name: 'tiger',
children: [{
name: 'sinba',
children: [{
name: 'cute',
children: []
}]
}]
},
{
name: 'lion',
children: []
}
]
In this case, the depth is 3, the tiger has 3 level. So the depth is 3
How could i achieve this? I try to use recursive, but don't know how to find the element which
has most nested children.
Thanks in advance.
Assuming that there are no circular references, you could try something like this
let arr = [{
name: 'tiger',
children: [{
name: 'sinba',
children: [{
name: 'cute',
children: []
}]
}]
},
{
name: 'lion',
children: []
}
]
function count(children) {
return children.reduce((depth, child) => {
return Math.max(depth, 1 + count(child.children)); // increment depth of children by 1, and compare it with accumulated depth of other children within the same element
}, 0); //default value 0 that's returned if there are no children
}
console.log(count(arr))
Our function would not work if there were some circular references, so there might be a need to adjust it accordingly. Detecting circular references is a whole ordeal. If nothing is done about it, the function will throw a Maximum call stack size exceeded error.
In order to handle it without any additional functionality implementation you could use already existing native JSON.stringify to do so. The stringify option will throw an exception only if you try to serialize BigInt values which we can handle ourselves or when objects are cyclic, which is excatly what we wanted.
let arr = [{
name: 'tiger',
children: []
}]
function testCircular(arr){
try {
BigInt.prototype.toJSON = function() { return this.toString() } // Instead of throwing, JSON.stringify of BigInt now produces a string
JSON.stringify(arr);
return false;
}
catch (e) {
// will only enter here in case of circular references
return true;
}
}
function count(children) {
if (testCircular(children)) return Infinity;
return children.reduce((depth, child) => {
return Math.max(depth, 1 + count(child.children)); // increment depth of children by 1, and compare it with accumulated depth of other children within the same element
}, 0); //default value 0 that's returned if there are no children
}
console.log(count(arr)) // normally counting
arr[0].children = arr; // creates circular reference
console.log(count(arr)) // counting for circular

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

Problem updating object properties inside a promise-aware for loop in javascript

I am having a problem accessing updated object properties inside a for loop in Javascript.
let distribution = await ctx.db.query.distribution(
{
where: {
id,
},
},
`
{
id
clients {
id
wallet {
amount
}
}
}`,
);
let count = 0;
for (const client of distribution.clients) {
await ctx.db.mutation.updateWallet({
where: {
id: client.id,
},
data: {
amount: client.wallet.amount + someAmount,
},
});
distribution.clients[count].wallet.amount = client.wallet.amount + someAmount;
count++;
}
In the above code, I execute a graphQL query to fetch all information associated with a distribution. I then want to iterate through the associated clients array and update each client’s wallet. The same client may appear multiple times in this array and each time
their wallet must be updated.
A problem occurs when a wallet has already been updated in a previous iteration. When I try to update the amount a second time, the client.wallet.amount reflects the initial value of the wallet rather than value after the first update.
Clearly the property clients[count].wallet.amount attribute of the distribution object isn’t being updated after each iteration. I thought javascript objects were passed by reference, and therefore the object should be updated after each iteration.
Could someone explain to me why the distribution object property is not being updated and how I can update it correctly?
FYI: I cannot use other loops such as forEach as it is not promise-aware and does not suppose async/await
A problem occurs when a wallet has already been updated in a previous iteration.
Each wallet object is only modified once when the loop runs. Two or more wallet objects in the array may represent the same row in the database, but they are still distinct objects -- changing one will not change the other.
const wallets = [
{ id: 1, amount: 0 },
{ id: 2, amount: 0 },
{ id: 1, amount: 0 },
]
for (const wallet of wallets) {
wallet.amount = wallet.amount + 5
}
console.log(wallets[0]) // { id: 1, amount: 5 }
console.log(wallets[1]) // { id: 2, amount: 5 }
console.log(wallets[2]) // { id: 1, amount: 5 }
Note that we don't need to use a count variable at all. Because the const wallet is a reference to the original object in the array, if we modify it, we modify the original object.
If you want need to track the amounts by ID, you'd need to implement that logic. For example:
const wallets = [
{ id: 1, amount: 0 },
{ id: 2, amount: 0 },
{ id: 1, amount: 0 },
]
const amountsById = {}
for (const wallet of wallets) {
amountsById[wallet.id] = (amountsById[wallet.id] || wallet.amount) + 5
}
const updatedWallets = wallets.map(wallet => ({
...wallet,
amount: amountsById[wallet.id],
}))
console.log(updatedWallets[0]) // { id: 1, amount: 10 }
console.log(updatedWallets[1]) // { id: 2, amount: 5 }
console.log(updatedWallets[2]) // { id: 1, amount: 10 }

Split an array-object without replication

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).

Counting and storing distinct elements and their counts from an array of object

I have an array of object and I want to count the number of distinct elements and counts of those objects.
[ { name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
]
I want to count the number of distinct names and store them in an object. I have tried it by 1# pushing all the names in an array,
2# then sorting them,
3# then calculating the number of distinct names and
4# finally pushing them to the object.
This process is too long. Is there a shorter way to do this. I am using Nodejs
Thanks in advance
You will create a new object, where the key is the name and the value the count:
var youArr = [
{ name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
];
var count = {}
for(var i=0; i < youArr.length; i++){
count[youArr[i].name] = count[youArr[i].name] || 0;
count[youArr[i].name]++;
}
alert(count['Namus']); // 2
This is a great place to use the reduce function:
The reduce() method applies a function against an accumulator and each
value of the array (from left-to-right) has to reduce it to a single
value.
...
reduce executes the callback function once for each element present in
the array, excluding holes in the array, receiving four arguments: the
initial value (or value from the previous callback call), the value of
the current element, the current index, and the array over which
iteration is occurring.
It would look something like this:
var arr = [ { name: 'Suman',
game: '5A'
},
{ name: 'Suman',
game: '5A'
},
{ name: 'Namus',
game: '5A'
},
{ name: 'Namus',
game: '5A'
}
]
var counts = arr.reduce(function(counts, item) {
counts[item.name] = (counts[item.name] || 0) + 1;
return counts;
}, {});
counts is then:
{ Suman: 2, Namus: 2 }
Asked in the comments:
what if i want the count as well as name in an array of object like
[{name: 'Suman', count:'2'}, {name:'Namus', count:'2'}]
If you already have counts from the reduce call above, then you can map its keys to the format you want:
var countsArray = Object.keys(counts).map(function(name) {
return {name: name, count: counts[name]};
});
countsArray is then:
[ { name: 'Suman', count: 2 },
{ name: 'Namus', count: 2 } ]

Categories