nested object, array combination - arrays

So I have a dilemma.
I have the next code
const loc = [
{ location_key: [32, 22, 11], autoassign: 1 },
{ location_key: [41, 42], autoassign: 1 }
];
const bulkConfigs = [
{
dataValues: {
config_key: 100,
}
},
{
dataValues: {
config_key: 200,
}
}
];
I need to create an object looking like this:
config_key: here get the config key from from bulkConfigs,
location_key: here get the location_key,
autoassign: 1
Also I need this object created
config_key: config_key,
location_key: '',
autoassign: 1,
as many times as they are locations for each config_key, what I mean is in this example from config_key: 200 we will have 2 objects like this one and for config_key: 100 we will have 3 objects like this. I suppose this can be done with reduce ... also bulkConfigs and loc can have more then just 2 objects, but the number will be always the same, like if they are 3 bulkConfigs there will be also 3 loc, but location_key might be different, one can have 7 location_key, other 4, and the last one just 1.
So in other words, the arrys are always the same length and they are always in the same order so they have the same index. Only the location_key can change, and I need the object created as many times as location_key exist.
I have tried a few things, but I don't know when it comes to this stuff .... I just can't do, that's what happens when you start with react and not java script :)

Ok so I managed to do this using lodash, here is my solution, I know it's nested like hell and probably this could be done way easier, but for a newbie is good enough. Feel free to come with more elegant solutions.
If you have a similar problem, here is the solution.
A code sandbox so you can play with:
https://codesandbox.io/s/epic-field-bdwyi?file=/src/index.js
import _ from "lodash";
const locs = [{ location_key: [32, 22, 11] }, { location_key: [41, 42] }];
const bulkConfigs = [
{
dataValues: {
config_key: 100
}
},
{
dataValues: {
config_key: 200
}
}
];
// map over the array of bulckConfigs and get indexes
const mergedArrays = _.map(bulkConfigs, (bulkConfig, i) => {
// create the object that we need
const objectNeed = {
// flatMap over the locs array to get flat values from objects in it
location_key: _.flatMap(locs, ({ location_key }, index) => {
// match the indexs of both arrays
if (index === i) {
// return the location_key values for each config
return location_key;
} else {
// compact to remove the undefinded values returned
return _.compact();
}
}),
config_key: bulkConfig.dataValues.config_key,
autoassign: 1
};
return objectNeed;
});
// now we just need to crate the same object as many locations and use flatMap to flatten the objects
const allObjects = _.flatMap(mergedArrays, mergedArray => {
const yy = _.map(mergedArray.location_key, location => {
const zz = {
location_key: location,
config_key: mergedArray.config_key,
autoassign: 1
};
return zz;
});
return yy;
});
console.log(allObjects);
And the more elegant version of it :)
const getConfigs = (locEl, index) => {
return _.map(locEl.location_key, (locationKey) => {
return {
location_key: locationKey,
config_key: bulkConfigs[index].dataValues.config_key,
autoassign: 1,
};
});
};
const configLocations = _.chain(locs)
.map(getConfigs)
.flatten()
.value();
console.log(configLocations);

Related

Filtering JSON object to get the average of 2 objects

I'm doing an React assignment for school but I'm a bit stuck and I can't find the right answer.
I have a data file with the following data:
const students = [
{
"name": "Evelyn",
"assignment": "SCRUM",
"difficultyRating": 3,
"funRating": 4
},
{
"name": "Evelyn",
"assignment": "W1D1-1",
"difficultyRating": 3,
"funRating": 3
},
{
"name": "Evelyn",
"assignment": "W1D2-1",
"difficultyRating": 1,
"funRating": 3
}
]
This goes on, there are 10 student and different assignments. What I need to do is get the average of the difficultyRating and funRating per assignment and use this data in a Victory Graph to display. Victory is working but it's not displaying the average.
I have this function already which takes all the assignments and fills it with the other data but I don't know what to do in the next step, how can I make a new Array of objects that I can use in the Victory Chart which displays the average difficulty/funrating per assignment.
The code I have so far is this:
const groupByAssignment = (objectArray, property) => {
return objectArray.reduce(function (total, obj) {
let key = obj[property];
if (!total[key]) {
total[key] = [];
}
total[key].push(obj);
return total;
}, {});
}
let groupedAssignments = groupByAssignment(students, 'assignment');
In the Victory Graph the output looks like this now:
<VictoryBar
style={{
data: {
fill: "#ff0b03",
},
}}
barWidth={2}
data={props.data}
x="assignment"
y="difficultyRating"
/>
)}
What I need is a data piece that has every assignment with the difficulty/rating averaged from all 10 students.
The following will give the average values of fun rating and difficulty rating. I have added more entries as there was only one record for each assignment.
const students = [ { name: "Evelyn", assignment: "SCRUM", difficultyRating: 3, funRating: 4, }, { name: "Pqr", assignment: "SCRUM", difficultyRating: 4, funRating: 2, }, { name: "Evelyn", assignment: "W1D1-1", difficultyRating: 3, funRating: 3, }, { name: "Evelyn", assignment: "W1D2-1", difficultyRating: 1, funRating: 3, }, { name: "Abc", assignment: "W1D2-1", difficultyRating: 5, funRating: 4, }, ];
const groupByAssignmentWithAverage = (objectArray, property) => {
return objectArray.reduce(
(prevValue, { difficultyRating, funRating, [property]: key }) => {
// key is the value of in obj with property as data name
if (!prevValue[key]) {
// create a new entry for each assignment type
prevValue[key] = {
difficultyRatingAvg: difficultyRating,
funRatingAvg: funRating,
count: 1,
};
} else {
// get the previous count and average values
const {count, difficultyRatingAvg, funRatingAvg} = prevValue[key];
prevValue[key] = {
difficultyRatingAvg:
(difficultyRatingAvg + difficultyRating) /
(count + 1),
funRatingAvg: (funRatingAvg + funRating) / (count + 1),
count: count + 1,
};
}
return prevValue;
},
{}
);
};
let output = groupByAssignmentWithAverage(students, "assignment");
console.log(output);
My solution would be to seprate the objects with same assignment name, and then just find the average of that particular assignment by mapping over individual arrays of objects which we get from initial step.
result = students.reduce(function (r, a) {
r[a.assignment] = r[a.assignment] || [];
r[a.assignment].push(a);
return r;
}, Object.create(null));
console.log(result);
Now you can easily format data according to the graph package you are using.

How to return objects that have matching value when comparing to a separate array

In my state I have an object called foodLog which holds all entries a user enters with one of the keys being foodSelectedKey and I'm trying to return all entries that have a matching value from that key with a different array called foodFilter.
However, this doesn't work and errors out saying foodLog.filter() isn't a function - I've looked this up and it's because it's an Object (I think). Any help would be greatly appreciated!
state = {
// log food is for the logged entries
foodLog: {},
// used for when filtering food entries
foodFilter: [],
};
findMatches = () => {
let foodLog = this.state.foodLog;
let foodFilter = this.state.foodFilter;
let matched = foodLog.filter((item) => {
return foodLog.foodsSelectedKey.map((food) => {
return foodFilter.includes(food);
});
});
};
I guess the reason behind the error Is not a function is that the object can not be looped. By that it means you can not iterate an object with differend variables inside, if it has no index to be iterated like an array. The same goes for map(), find() and similar functions which MUST be run with arrays - not objects.
As far as I understand you have an object named foodLog which has an array named foodsSelectedKey. We need to find intersected elements out of foodFilter with the array. This is what I came up with:
state = {
// log food is for the logged entries
foodLog: {
foodsSelectedKey: [
{ id: 1, name: "chicken" },
{ id: 2, name: "mashroom" }
]
},
// used for when filtering food entries
foodFilter: [
{ id: 1, name: "chicken" },
{ id: 2, name: "orange" }
]
};
findMatches = () => {
let foodLog = this.state.foodLog;
let foodFilter = this.state.foodFilter;
let matched = foodLog.foodsSelectedKey.filter((key) =>
{
for (let i=0; i<foodFilter.length;i++){
if(foodFilter[i].name===key.name)
return true
}
return false;
}
);
return matched;
};
The Output is filtered array, in this case, of one element only:
[{
id: 1
name: "chicken"
}]
In order to check the output - run console.log(findMatches()). Here is the CodeSandbox of the solution. (check console at right bottom)

typescript how to find inside an array that is already in an array?

I want to find a value inside an array that is already inside an array.
To give an example of my array:
[
{
ConcessionId: 1,
ConcessionName: "Coyotes",
KnownAs: [
{
TeamId: 1,
Name: "Arizona Coyotes",
},
{
TeamId: 2,
Name: "Phoenix Coyotes",
}
]
},
{
ConcessionId: 2,
ConcessionName: "Devils",
KnownAs: [
{
TeamId: 3,
Name: "Colorado Rockies",
},
{
TeamId: 4,
Name: "New-Jersey Devils",
}
]
}
]
What I want is when Icall my function it returns me the team name.
For example, I the parameter value is 3, I want Colorado Rockies as a name:
public getInfo(_TeamID) {
const concession: ConcessionInfo[] = this.concessionList$.filter(function (x) {
x.KnownAs.filter( (y)=> {
y.TeamId= +_TeamID;
return y.Name;
})
})
}
I try so many different way with filter. But never get something good. Never works.
I can make a double .foreach , for each array. but I think a better method exist than making a double loop.
Thanks
Instead of using the filter method (which is in fact working similar as a for loop), you could do forEach on both arrays. For your current data structure, there is no other way around it.
getInfo = (_TeamID) => {
let teamName = '';
this.concessionList$.forEach(entry => {
entry.KnownAs.forEach(team => {
if(team.TeamId === _TeamID){
teamName = team.Name;
return; // break the loop.
}
})
});
return teamName;
}
Here is a working example
https://stackblitz.com/edit/double-for-lopp
EDIT
If you have a look at the polyfill implementation of filter from Mozilla https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter which is in equivalent to the native implementation of filter, you can see that it is looping through the whole array, the same way as a forEach loop. The difference is that the filter method will return a new array based on the boolean condition inside the callback function, while a forEach loop does not return anything.
Assuming myArray is contains the data you provided.
The following code will work if you're using Typescript 3.7 and above.
public getInfo(teamId: number): string | undefined {
const team = this.concessionList$
.map(concession => concession.KnownAs)
.reduce((a, b) => a.concat(b), [])
.find(team => team.TeamId === teamId)
return team ? team.Name : undefined
}
Usage:
this.getInfo(3) // Colorado Rockies
Ok how this work?
You have to understand what is find. For example:
const result = [{name: 'foo', age: 1}, {name: 'bar', age: 2}]
.find(people => people.name === 'foo')
console.log(result) // {name: 'foo', age: 1}

array with objects containing array

Sorry guys if my way of asking the question in the title is not correct.
I am working on a project on react js and I am getting the data like this
[
{
"count": [
{
"1": 16
},
{
"1": 149
}
],
"day": "2019-08-27"
}
]
now this is my first time I am dealing with this kind of data and I really have no idea how can I show it like this I am really sorry guys I literally can't even show what I have tried because it does not seem relevant
[
{
count: 165
day:"2019-08-27"
}
}
Assuming the data you're getting is under a variable called data you could use reduce:
The below makes the assumption the count is always an array of objects with just 1 key called '1'.
const newData = data.map(datum => {
datum.count = datum.count.reduce((count, item) => {
return count + item['1']
}, 0)
return datum
})
You can try something like this:
let arr = [
// item
{
count: [
{
"1": 16
},
{
"1": 149
}
],
day: "2019-08-27"
}
];
arr.map(item => {
Object.keys(item).map(key => {
console.log(item[key])
// if item[key] is iterable
if(Array.isArray(item[key])) {
item[key].map(val => {
console.log(item)
})
} else {
console.log(item[key])
}
});
});
The concept is that for Objects you do a Object.keys().something and for an array you do a arr.map(item => ...)

array.groupBy in TypeScript

The basic array class has .map, .forEach, .filter, and .reduce, but .groupBy i noticably absent, preventing me from doing something like
const MyComponent = (props:any) => {
return (
<div>
{
props.tags
.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
}
</div>
)
}
I ended up implementing something myself:
class Group<T> {
key:string;
members:T[] = [];
constructor(key:string) {
this.key = key;
}
}
function groupBy<T>(list:T[], func:(x:T)=>string): Group<T>[] {
let res:Group<T>[] = [];
let group:Group<T> = null;
list.forEach((o)=>{
let groupName = func(o);
if (group === null) {
group = new Group<T>(groupName);
}
if (groupName != group.key) {
res.push(group);
group = new Group<T>(groupName);
}
group.members.push(o)
});
if (group != null) {
res.push(group);
}
return res
}
So now I can do
const MyComponent = (props:any) => {
return (
<div>
{
groupBy(props.tags, (t)=>t.category_name)
.map((group)=>{
return (
<ul key={group.key}>
<li>{group.key}</li>
<ul>
{
group.members.map((tag)=>{
return <li key={tag.id}>{tag.name}</li>
})
}
</ul>
</ul>
)
})
}
</div>
)
}
Works pretty well, but it is too bad that I need to wrap the list rather than just being able to chain method calls.
Is there a better solution?
You can use the following code to group stuff using Typescript.
const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
list.reduce((previous, currentItem) => {
const group = getKey(currentItem);
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {} as Record<K, T[]>);
// A little bit simplified version
const groupBy = <T, K extends keyof any>(arr: T[], key: (i: T) => K) =>
arr.reduce((groups, item) => {
(groups[key(item)] ||= []).push(item);
return groups;
}, {} as Record<K, T[]>);
So, if you have the following structure and array:
type Person = {
name: string;
age: number;
};
const people: Person[] = [
{
name: "Kevin R",
age: 25,
},
{
name: "Susan S",
age: 18,
},
{
name: "Julia J",
age: 18,
},
{
name: "Sarah C",
age: 25,
},
];
You can invoke it like:
const results = groupBy(people, i => i.name);
Which in this case, will give you an object with string keys, and Person[] values.
There are a few key concepts here:
1- You can use function to get the key, this way you can use TS infer capabilities to avoid having to type the generic every time you use the function.
2- By using the K extends keyof any type constraint, you're telling TS that the key being used needs to be something that can be a key string | number | symbol, that way you can use the getKey function to convert Date objects into strings for example.
3- Finally, you will be getting an object with keys of the type of the key, and values of the of the array type.
you could add the function to the array prototype in your app (note some don't recomend this: Why is extending native objects a bad practice?):
Array.prototype.groupBy = function(/* params here */) {
let array = this;
let result;
/* do more stuff here*/
return result;
};
Then create an interface in typescript like this:
.d.ts version:
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
OR in a normal ts file:
declare global {
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
}
Then you can use:
props.tags.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
A good option might be lodash.
npm install --save lodash
npm install --save-dev #types/lodash
Just import it import * as _ from 'lodash' and use.
Example
_.groupBy(..)
_.map(..)
_.filter(..)
Instead of groupby use reduce. Suppose product is your array
let group = product.reduce((r, a) => {
console.log("a", a);
console.log('r', r);
r[a.organization] = [...r[a.organization] || [], a];
return r;
}, {});
console.log("group", group);
During the TC39 meeting of December 2021, the proposal introducing the new Array.prototype.groupBy and Array.prototype.groupByToMap function has reached stage 3 in the specification process.
https://github.com/tc39/proposal-array-grouping
https://github.com/tc39/proposals/commit/b537605f01df50fd4901be5ce4aa0d02fe6e7193
Here's how both functions are supposed to look like according to the README linked above:
const array = [1, 2, 3, 4, 5];
// groupBy groups items by arbitrary key.
// In this case, we're grouping by even/odd keys
array.groupBy((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// => { odd: [1, 3, 5], even: [2, 4] }
// groupByToMap returns items in a Map, and is useful for grouping using
// an object key.
const odd = { odd: true };
const even = { even: true };
array.groupByToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
// => Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
While not a 100% guaranty that it will really end up in a future version of JavaScript in the form described above (there's always a chance that the proposal can be adjusted or dropped, notably for compatibility reasons), it's nevertheless a strong commitment to have this groupBy feature offered in the standard lib soon.
By ripple effect, it also means that these functions will be also available in TypeScript.

Resources