I get an input like this:
input 1:
{
"name": "Ben",
"description": "Ben",
"attributes": [
{
"type": "Background",
"value": "Default"
},
{
"type": "Hair-color",
"value": "Brown"
}
]
}
input 2
{
"name": "Ice",
"description": "Ice",
"attributes": [
{
"type": "Background",
"value": "Green"
},
{
"type": "Hair-color",
"value": "White"
}
]
}
input 3
{
"name": "Itay",
"description": "Itay",
"attributes": [
{
"type": "Background",
"value": "Default"
},
{
"type": "Hair-color",
"value": "Brown"
}
]
}
What I want to do is count the amount of each type of background and each type of hair-color appearing.
(These are sample examples and in reality there are more types and different values)
Let's say in these examples we have 2 objects that have a background as default then I want to have a count of that like so:
export interface TraitCount {
value: string,
count: number
}
export interface CountOfEachAttribute {
trait_type: string,
trait_count: traitCount[] | null,
total_variations: number
}
I want the most effective code because there are other aspects to the code, in addition it will run on 5-10k queries not just three, so needs
to run in good times too :D
(It's similar to my other question done with python but now I need it in js also)
Atm it's something like this:
(Apart of a much bigger code so keep that in mind)
setInitalCountOfAllAttribute( state, { payload }: PayloadAction<CountOfEachAttribute[] | null> ) {
if (payload === null) {
state.countOfAllAttribute = null;
} else {
state.countOfAllAttribute = payload;
}
},
setCountOfAllAttribute(state, { payload }: PayloadAction<Attribute>) {
if (state.countOfAllAttribute !== null) {
state.countOfAllAttribute.map(
(countOfEachAttribute: CountOfEachAttribute) => {
// Find the trait type
if (countOfEachAttribute.trait_type === payload.trait_type) {
// initiate the trait count array to store all the trait values and add first trait value
if (countOfEachAttribute.trait_count === null) {
const new_trait_count = { value: payload.value, count: 1 };
countOfEachAttribute.trait_count = [new_trait_count];
countOfEachAttribute.total_variations++;
}
// Trait array already existed.
else {
// Check if value already present or not
const checkValue = (obj: any) => obj.value === String(payload.value);
const isPresent = countOfEachAttribute.trait_count.some(checkValue)
const isPresent2 = countOfEachAttribute.trait_count.find((elem: any) => elem.value === String(payload.value))
// Value matched, increase its count by one
if (isPresent2) {
countOfEachAttribute.trait_count &&
countOfEachAttribute.trait_count.map((trait) => {
if (trait.value === payload.value) {
trait.count++;
}
});
}
// Value doesn't match, add a new entry and increase the count of variations by one
else {
const new_trait_count = { value: payload.value, count: 1 };
countOfEachAttribute.trait_count = [
...countOfEachAttribute.trait_count,
new_trait_count,
];
countOfEachAttribute.total_variations++;
}
}
}
}
);
}
},
You can merge all arrays and use Array.reduce.
const input1 = {
"name": "Ben",
"description": "Ben",
"attributes": [{
"type": "Background",
"value": "Default"
},
{
"type": "Hair-color",
"value": "Brown"
}
]
}
const input2 = {
"name": "Ice",
"description": "Ice",
"attributes": [{
"type": "Background",
"value": "Green"
},
{
"type": "Hair-color",
"value": "White"
}
]
}
const input3 = {
"name": "Itay",
"description": "Itay",
"attributes": [{
"type": "Background",
"value": "Default"
},
{
"type": "Hair-color",
"value": "Brown"
}
]
}
const mergedInput = [input1, input2, input3];
const result = mergedInput.reduce((acc, item) => {
item.attributes.forEach(attrItem => {
const existType = acc.find(e => e.trait_type == attrItem.type);
if (existType) {
var existAttr = existType.trait_count.find(e => e.value == attrItem.value);
if (existAttr) {
existAttr.count++;
} else {
existType.trait_count.push({
value: attrItem.value,
count: 1
});
existType.total_variations++;
}
} else {
acc.push({
trait_type: attrItem.type,
trait_count: [{
value: attrItem.value,
count: 1
}],
total_variations: 1
})
}
});
return acc;
}, []);
console.log(result);
I suggest instead of creating an array for trait_count to make it an object so you don't have to iterate over it whenever you are adding a new attribute. In the snippet below I'm using the value of the attribute as a sort of hash that allows the access to the given property without having to call the Array.prototype.find function
const input1 = {"name":"Ben","description":"Ben","attributes":[{"type":"Background","value":"Default"},{"type":"Hair-color","value":"Brown"}]};
const input2 = {"name":"Ice","description":"Ice","attributes":[{"type":"Background","value":"Green"},{"type":"Hair-color","value":"White"}]};
const input3 = {"name":"Itay","description":"Itay","attributes":[{"type":"Background","value":"Default"},{"type":"Hair-color","value":"Brown"}]};
function countAtributes(input, totalCounts={}) {
input.attributes.forEach((attribute) => {
if (!totalCounts[attribute.type])
totalCounts[attribute.type] = {trait_type: attribute.type, trait_count: {}, total_variations: 0};
if (!totalCounts[attribute.type].trait_count[attribute.value]) {
totalCounts[attribute.type].trait_count[attribute.value] = {value: attribute.value, count: 1};
totalCounts[attribute.type].total_variations+=1;
}
else totalCounts[attribute.type].trait_count[attribute.value].count +=1;
})
}
const totalCounts = {};
countAtributes(input1, totalCounts);
countAtributes(input2, totalCounts);
countAtributes(input3, totalCounts);
console.log(totalCounts);
It could be turned into the array afterwards with Object.values if necessary
I believe it is a much better approach to what you had before as you don't have to iterate over the tables of trait_counts. In theory it should significantly reduce the time taken. Iterating over the array and checking a condition each time is much slower than key lookup in Javascript object
Related
I am trying to move everything in the Array Results outside and into the original object
this is the object
{
"Name": "John",
"Results": [
{
"Type": "DB",
"Immediate_Action": "No",
}
]
}
It should look like this
{
"Name": "John",
"Type": "DB",
"Immediate_Action": "No",
}
What I have so far is this
const mapOscarResults = ({ data }) => {
return data.map(entry => {
let mapped = {...entry};
entry.Results.forEach(key => {
let Type = mapped[key.Type]
if (mapped[key]) {
mapped[key].push(entry.Results[key]);
} else {
mapped[key] = [entry.Results[key]];
}
});
return mapped;
});
};
You can simply spread the Results array into an Object.assign() call.
const input = { "Name": "John", "Results": [{ "Type": "DB", "Immediate_Action": "No", }, { "Another": "value" }] };
const { Results, ...refactored } = input;
Object.assign(refactored, ...Results);
console.log(refactored)
This code works for your example:
const { Results: results, ...rest } = {
"Name": "John",
"Results": [
{
"Type": "DB",
"Immediate_Action": "No",
}
]
}
const res = {...rest, ...results.reduce((prev, curr) => ({
...prev,
...curr
}), {})}
console.log(res)
But I don't know what you expect when the Results array has more than one element.
In that condition, if this code does not fill your needs, ask me to change it.
however, it will join first Result with index 0, you can expand it
const data = {
"Name": "John",
"Results": [
{
"Type": "DB",
"Immediate_Action": "No",
}
]
}
const mapOscarResults = (data) => {
for (let i in Object.keys(data)){
if (Array.isArray(data[Object.keys(data)[i]])){
newKey = data[Object.keys(data)[i]][0]
data = {... data, ...newKey}
delete data[Object.keys(data)[i]]
}
}
return data
};
console.log(mapOscarResults(data))
Example JSON file:
[
{
"discordId": "9273927302020",
"characters": [
{
"name": "Rare_Character",
"value": 1
},
{
"name": "Ultra_Rare_Character",
"value": 1
}
]
}
]
Let's just say for example I ran this simple gacha and got 4 characters:
let i = 1
var picks = []
while(i <= 4){
const { pick } = gacha.simple(alpha)
picks.push(pick)
i++
}
Now, picks has an array like this:
[
{
"name": "Common_Character"
},
{
"name": "Ultra_Rare_Character"
},
{
"name": "Common_Character"
},
{
"name": "Rare_Character"
}
]
How do I increment the value in My Example JSON file based on the name from what I got in my gacha results picks while ignoring the Common_Character and only passing those Rare and Ultra_Rare ones?
I've tried filtering them like this:
var filter = picks.filter(t => t.name === 'Rare_Character' || t.name === 'Ultra_Rare_Character')
Now I don't know how to increase those values in my JSON file and what if in the gacha results I got two Rare_Characters or Ultra_Rare_Character
I'm using fs to read my JSON file but I just don't know the logic to increase values
const src = [
{
"discordId": "9273927302020",
"characters": [
{
"name": "Rare_Character",
"value": 1
},
{
"name": "Ultra_Rare_Character",
"value": 1
}
]
}
];
const gacha = [
{
"name": "Common_Character"
},
{
"name": "Ultra_Rare_Character"
},
{
"name": "Common_Character"
},
{
"name": "Rare_Character"
}
];
const updateValues = (src, gacha) => {
const gachaSums = gacha.reduce((collector, current) => {
collector[current.name] = (collector[current.name] | 0) + 1;
return collector;
}, {});
src.characters.forEach(srcChar => {
gachaSums[srcChar.name] = srcChar.value + (gachaSums[srcChar.name] | 0);
});
src.characters = Object.entries(gachaSums).map(([key, value]) =>
({ name: key, value: value })
);
return src;
}
console.log(updateValues(src[0], gacha));
Maybe this version could help
For one of my e-commerce application requirement, I have a nested array of the form (Sample):
const data = [
{
"id": 1,
"group": "upper-wear",
"labels": [
{
"type": "shirts",
"quantity": "20",
},
],
popular: true
},
{
"id": 2,
"group": "bottom-wear",
"lables": [
{
"type": "trousers",
"quantity": "31",
},
],
popular: true
},
]
To this array, I need to insert new objects to the array 'labels' if the group value equals 'upper-wear'.
const newDataToInsert = [
{
"type": 'blazers',
"quantity": 19
},
]
This is what I tried so far, considering that for now I only need to insert to single label (i.e. 'upper-wear') (in future, there can be multiple labels category 'upper-wear', 'bottom-wear', to be inserted into):
const updatedArray = data.map((datum) => {
if (datum.group === 'upper-wear') {
return {
...datum,
labels: [...datum.labels, ...newDataToInsert]
};
}
});
console.log(updatedArray);
But there seems to be a silly issue that I am missing as the result returns like this:
[
{
id: 1,
group: 'upper-wear',
labels: [ [Object], [Object] ],
popular: true
},
undefined
]
I know there may be better approaches available, but this is what I can think of as the minimum solution for now.
any help to resolve the current or any better solution will be highly appreciated.
Try with this
updatedArray = data.map((d) => {
if (d.group && d.group === 'upper-wear') {
return { ...d, labels: d.labels.concat(newDataToInsert) }
} else {
return d;
}
})
const data = [
{
"id": 1,
"group": "upper-wear",
"labels": [
{
"type": "shirts",
"quantity": "20",
},
],
popular: true
},
{
"id": 2,
"group": "bottom-wear",
"lables": [
{
"type": "trousers",
"quantity": "31",
},
],
popular: true
},
];
const newDataToInsert = [
{
"type": 'blazers',
"quantity": 19
},
];
const updatedArray = data.map((d) => {
if (d.group && d.group === 'upper-wear') {
return { ...d, labels: d.labels.concat(newDataToInsert) }
} else {
return d;
}
});
console.log(updatedArray)
Explaination
Here while mapping the data, we check for the condition
IF
If it matches then we will first copy the whole object from the variable b return { ...b }
after that we take another variable with the same name lables return { ...d, labels: d.labels.concat(newDataToInsert) },As per the JSON default nature the new variable with the same name will hold the latest value
Here in labels we first take a copy of old data and then merge it with newDataToInsert array labels: d.labels.concat(newDataToInsert), It will merge 2 arrays and store them in JSON with the name labels
Else
In else we just return the current values else { return d; }
You don't actually need to iterate with map over the array. Just find an object in the array and change what you want.
const data=[{id:1,group:"upper-wear",labels:[{type:"shirts",quantity:"20"}],popular:true},{id:2,group:"bottom-wear",lables:[{type:"trousers",quantity:"31"}],popular:true}];
const newDataToInsert=[{type:"blazers",quantity:19}];
data.find(({ group }) => group === 'upper-wear')?.labels.push(...newDataToInsert);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You're not returning all objects from your map. you're only returning a result when your criteria is met. This is resulting in your undefined objects...
const data = [
{ "id": 1, "group": "upper-wear", "labels": [ { "type": "shirts", "quantity": "20", }, ], popular: true },
{ "id": 2, "group": "bottom-wear", "lables": [ { "type": "trousers", "quantity": "31", }, ], popular: true },
]
const newDataToInsert = [ { "type": 'blazers',"quantity": 19 }, ]
const updatedArray = data.map(datum => {
if (datum.group === 'upper-wear') datum.labels = [...datum.labels, ...newDataToInsert]
return datum
});
console.log(updatedArray);
You can use Array#find to locate the desired group and then change labels for the group found. There are two options depending on how many items you would like to insert. Use Array#push to add the desired item; use forEach for more than one item:
const searchgroup = "upper-wear";
const target = data.find(({group}) => group === searchgroup);
target.labels.push(...newDataToInsert); //For one item to insert
//newDataToInsert.forEach(label => target.labels.push( label )); //For more than one item
const data = [{"id": 1, "group": "upper-wear", "labels": [{"type": "shirts", "quantity": "20"},],popular: true }, {"id": 2, "group": "bottom-wear", "lables": [{"type": "trousers", "quantity": "31", },],popular: true}];
const newDataToInsert = [{"type": 'blazers', "quantity": 19}];
//group to find
const searchgroup = "upper-wear";
//target element in data
const target = data.find(({group}) => group === searchgroup);
//check if group was found
if( target ) {
//if there's only one product in newDataToInsert us this:
//target.labels.push(...newDataToInsert);
//if you have more than one product to be inserted use this; also works for one
newDataToInsert.forEach(label => target.labels.push( label ));
} else {
console.log( `No such group found: ${searchgroup}!` );
}
console.log( data );
there is a list of users
filterData = [
{
"position":"lawyer",
"department_positions":[],
"group_positions":[
{"group":{"id":2,"code":"234","name":"group1"},"lead":false},
{"group":{"id":1,"code":"123","name":"group12"},"lead":true}
]
},
{
"position":"director",
"department_positions":[
{"department":{"id":3,"code":"333","name":"subDep"},"lead":false}
],
"group_positions":[
{"group":{"id":2,"code":"234","name":"group1"},"lead":false},
{"group":{"id":1,"code":"123","name":"group12"},"lead":true}
]
},
{
"position":"director",
"department_positions":[],
"group_positions":[]
}
]
and list of filters
categories = {
"position":["lawyer","director"],
"group_positions":["group1","group12"],
"department_positions":["generalDep", "subDep"]
}
It is necessary to filter users taking into account the fact that several filters can be selected at the same time. For example, i want to find user with position = "director" and AND group_positions = "group1" AND department_positions = "subDep"
my code doesn't allow filtering by multiple conditions. how can i fix it?
this.filter = this.filterData.filter(item => {
for (let key in this.categories) {
if (item[key].find(el =>
this.categories[key].includes(
el.group?.name || el.department?.name
)
)) {
return true
}
}
return false
})}
This is a good place to employ an es6 class to give behavior to the object being filtered. Augment each object to determine if it matches the "category" object.
(from the example data, this assumes the OP is looking for a "product of sums" match: for all of the category keys match at least one of the category values)
class FilterMe {
constructor(item) {
Object.assign(this, item);
}
namesForKey(key) {
switch (key) {
case 'position':
return [this.position]; // always answer an array
case 'group_positions':
return this.group_positions.map(gp => gp.group.name);
case 'department_positions':
return this.department_positions.map(dp => dp.department.name);
default:
return [];
}
}
// return true if a single filter key-value pair is matched
matchesFilterKeyValue(filterKey, filterOptions) {
const myNames = this.namesForKey(filterKey);
const matches = filterOptions.filter(e => myNames.includes(e));
return matches.length > 0;
}
// return true if all filter key-values pairs are matched
matchesFilter(filter) {
return Object.entries(filter).every(keyValue => {
return this.matchesFilterKeyValue(...keyValue);
})
}
}
const filterData = [{
"position": "lawyer",
"department_positions": [],
"group_positions": [{
"group": {
"id": 2,
"code": "234",
"name": "group1"
},
"lead": false
}, {
"group": {
"id": 1,
"code": "123",
"name": "group12"
},
"lead": true
}]
},
{
"position": "director",
"department_positions": [{
"department": {
"id": 3,
"code": "333",
"name": "subDep"
},
"lead": false
}],
"group_positions": [{
"group": {
"id": 2,
"code": "234",
"name": "group1"
},
"lead": false
}, {
"group": {
"id": 1,
"code": "123",
"name": "group12"
},
"lead": true
}]
},
{
"position": "director",
"department_positions": [],
"group_positions": []
}
]
const categories = {
"position": ["lawyer", "director"],
"group_positions": ["group1", "group12"],
"department_positions": ["generalDep", "subDep"]
}
// convert the filterData to the objects and test them...
let objects = filterData.map(d => new FilterMe(d));
let matches = objects.filter(o => o.matchesFilter(categories))
console.log(matches)
You can try something like this:
let filtered = example.filter(item => {
let valid = false
if (item.includes('something')) {
valid = true
}
if (!valid) {
// check second condition
}
return valid
})
Use a temporary placeholder so you don't immediately have to return true/false.
I have a following JsonArray:
arr = [
{
"name": "test",
"alias": "alias1",
"type": 1
},
{
"name": "test",
"type": 0
},
{
"name": "abc",
"alias": "alias2",
"type": 1
}
]
And I want to find using a variable value (which may contain alias / key). So basically the first preference of find should be alias, and if alias with the same value is not found then it should search in "name" and where "alias" is not present.
Normally it would have gone like:
_.find(arr, {
alias: value
})
But I want the code to return me the obj where name = value , if alias=value is not found
1) Ex: value = "alias1"
Expected==>
{
"name": "test",
"alias": "alias1",
"type": 1
}
2) Ex: value = "test"
Expected==>
{
"name": "test",
"type": 0
}
You'll need to use find (_.find() or Array.find()) to look for the alias match, and if none found use find again to look for a name match:
const findAliasOrName = (value, arr) =>
_.find(arr, { alias: value }) || _.find(arr, { name: value });
const arr = [{"name":"test","type":0},{"name":"test","alias":"alias1","type":1},{"name":"abc","alias":"alias2","type":1}]
console.log(findAliasOrName('alias1', arr));
console.log(findAliasOrName('test', arr));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
you can do this as follow:
if the alias key is available in obj then match the value with alias else match the value with name key using ternary operator
var arr = [
{
"name": "test",
"alias": "alias1",
"type": 1
},
{
"name": "test",
"type": 0
},
{
"name": "abc",
"alias": "alias2",
"type": 1
}
];
const findValue = (arr, value) => {
return _.find(arr, (elem) => {
return elem.alias ? elem.alias === value : elem.name === value;
});
}
console.log(findValue(arr, 'alias1'));
console.log(findValue(arr, 'test'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
You need to take a custom callback which takes the two possible properties for checking the value.
function find(array, value) {
return _.find(array, o => o.alias === value || o.name === value)
}
var array = [{ name: "abc", alias: "abc_alias", type: 1 }, { name: "tofind", type: 2 }, { name: "def", alias: "al1", type: 3 }];
console.log(find(array, 'abc_alias'));
console.log(find(array, 'tofind'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js">