Related
I have this dataset:
const dataset = [
{ date: "2022-01-01", category: "red", value: 10 },
{ date: "2022-01-01", category: "blue", value: 20 },
{ date: "2022-01-01", category: "gold", value: 30 },
{ date: "2022-01-01", category: "green", value: 40 },
{ date: "2022-01-02", category: "red", value: 5 },
{ date: "2022-01-02", category: "blue", value: 15 },
{ date: "2022-01-02", category: "gold", value: 25 },
{ date: "2022-01-02", category: "green", value: 35 }
];
And I need to create a stacked barchart. To do that I used the d3 stack() function.
The result I need is this:
const stackedDataset = [
{ date: "2022-01-01", category: "red", value: 10, start: 0, end: 10 },
{ date: "2022-01-02", category: "red", value: 5, start: 0, end: 5 },
{ date: "2022-01-01", category: "blue", value: 20, start: 10, end: 30 },
{ date: "2022-01-02", category: "blue", value: 15, start: 5, end: 20 },
{ date: "2022-01-01", category: "gold", value: 30, start: 30, end: 60 },
{ date: "2022-01-02", category: "gold", value: 25, start: 20, end: 45 },
{ date: "2022-01-01", category: "green", value: 40, start: 60, end: 100 },
{ date: "2022-01-02", category: "green", value: 35, start: 45, end: 80 }
]
So the same data but with a start and end property computed by d3.
I created a function that takes in input dataset and returns stackedDataset:
export function getStackedSeries(dataset: Datum[]) {
const categories = uniq(dataset.map((d) => d[CATEGORY])) as string[];
const datasetGroupedByDateFlat = flatDataset(dataset);
const stackGenerator = d3.stack().keys(categories);
const seriesRaw = stackGenerator(
datasetGroupedByDateFlat as Array<Dictionary<number>>
);
const series = seriesRaw.flatMap((serie, si) => {
const category = categories[si];
const result = serie.map((s, sj) => {
return {
[DATE]: datasetGroupedByDateFlat[sj][DATE] as string,
[CATEGORY]: category,
[VALUE]: datasetGroupedByDateFlat[sj][category] as number,
start: s[0] || 0,
end: s[1] || 0
};
});
return result;
});
return series;
}
export function flatDataset(
dataset: Datum[]
): Array<Dictionary<string | number>> {
if (dataset.length === 0 || !DATE) {
return (dataset as unknown) as Array<Dictionary<string | number>>;
}
const columnToBeFlatValues = uniqBy(dataset, CATEGORY).map(
(d) => d[CATEGORY]
);
const datasetGroupedByDate = groupBy(dataset, DATE);
const datasetGroupedByMainCategoryFlat = Object.entries(
datasetGroupedByDate
).map(([date, datasetForDate]) => {
const categoriesObject = columnToBeFlatValues.reduce((acc, value) => {
const datum = datasetForDate.find(
(d) => d[DATE] === date && d[CATEGORY] === value
);
acc[value] = datum?.[VALUE];
return acc;
}, {} as Dictionary<string | number | undefined>);
return {
[DATE]: date,
...categoriesObject
};
});
return datasetGroupedByMainCategoryFlat as Array<Dictionary<string | number>>;
}
As you can see, the functions are specific for Datum type. Is there a way to modify them to make them works for a generic type T that has at least the three fields date, category, value?
I mean, I would like to have something like this:
interface StackedStartEnd {
start: number
end: number
}
function getStackedSeries<T>(dataset: T[]): T extends StackedStartEnd
Obviously this piece of code should be refactored to make it more generic:
{
[DATE]: ...,
[CATEGORY]: ...,
[VALUE]: ...,
start: ...,
end: ...,
}
Here the working code.
I'm not a TypeScript expert so I need some help. Honestly what I tried to do was to modify the function signature but I failed and, anyway, I would like to make the functions as generic as possible and I don't know how to start.
Do I need to pass to the functions also the used columns names?
Thank you very much
I tried to make a more generic approach as you suggest mixing the two functions. By default, seems like your getStackedSeries function does not need to know about date and category properties, you can use a Generic Type to ensure just the value property, as we need to know that to calculate start and end values.
The full implementation can be viewed here on codesandbox.
export function getStackedSeries<T extends Datum>(
data: T[],
groupByProperty: PropertyType<T>
) {
const groupedData = groupBy(data, (d) => d[groupByProperty]);
const acumulatedData = Object.entries(groupedData).flatMap(
([_, groupedValue]) => {
let acumulator = 0;
return groupedValue.map(({ value, ...rest }) => {
const obj = {
...rest,
value: value,
start: acumulator,
end: acumulator + value
};
acumulator += value;
return obj;
});
}
);
return acumulatedData;
}
The getStackedSeries() now receives a data property that extends Datum type, which is:
export interface Datum {
value: number;
}
With that and a second property called groupByProperty we can define the groupBy clause and return all flatten by flatMap.
You probably notice that the return type now is defined by typescript dynamically by the use of a generic <T>. For example:
const dataGroupedByDate: (Omit<{
date: string;
category: string;
value: number;
}, "value"> & {
value: number;
start: number;
end: number;
})[]
You can also type this part of the function, but makes sense to let the compiler works for you and generate the types automatically based on input.
You could group by date for start/end and take another grouping by category for the result set.
const
dataset = [{ date: "2022-01-01", category: "red", value: 10 }, { date: "2022-01-01", category: "blue", value: 20 }, { date: "2022-01-01", category: "gold", value: 30 }, { date: "2022-01-01", category: "green", value: 40 }, { date: "2022-01-02", category: "red", value: 5 }, { date: "2022-01-02", category: "blue", value: 15 }, { date: "2022-01-02", category: "gold", value: 25 }, { date: "2022-01-02", category: "green", value: 35 }],
result = Object
.values(dataset
.reduce((r, { date, category, value }) => {
const
start = r.date[date]?.at(-1).end ?? 0,
end = start + value,
object = { date, category, value, start, end };
(r.date[date] ??= []).push(object);
(r.category[category] ??= []).push(object);
return r;
}, { date: {}, category: {} })
.category
)
.flat();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
How do I implement this properly?
const tree = buildTree(1, shuffleArray([
{ type: 'string', source_id: 1, name: 'foo', value: 'asdf' },
{ type: 'integer', source_id: 1, name: 'bar', value: 123 },
{ type: 'object', source_id: 1, name: 'nested', value: 2 },
{ type: 'object', source_id: 2, name: 'nested', value: 3, array: true },
{ type: 'boolean', source_id: 3, name: 'random', value: true },
{ type: 'string', source_id: 3, name: 'another', value: 'hello' },
{ type: 'object', source_id: 2, name: 'nested', value: 4, array: true },
{ type: 'boolean', source_id: 4, name: 'random', value: false },
{ type: 'string', source_id: 4, name: 'another', value: 'world' },
{ type: 'object', source_id: 2, name: 'nested', value: 5, array: true },
{ type: 'boolean', source_id: 5, name: 'random', value: true },
{ type: 'string', source_id: 5, name: 'another', value: 'awesome' },
]))
function buildTree(startId, array) {
const map = array.reduce((m, x) => {
m[x.source_id] = m[x.source_id] ?? {}
if (x.array) {
m[x.source_id][x.name] = m[x.source_id][x.name] ?? []
m[x.source_id][x.name].push({ id: x.value })
} else {
m[x.source_id][x.name] = x.value
}
return m
}, {})
// ??? getting lost...
}
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array
}
where the "expected tree" would be something like this:
const expectedTree = {
id: 1,
foo: 'asdf',
bar: 123,
nested: {
id: 2,
nested: [
{
id: 3,
random: true,
another: 'hello'
},
{
id: 4,
random: false,
another: 'world'
},
{
id: 5,
random: true,
another: 'awesome'
}
]
}
}
The shuffleArray is just to show that the records could be in any order, and the id (source_id) property is not necessarily in incremental order (actually in my case they are UUIDs with the hierarchy not really in any particular order). Each "record" in buildTree is a "property" record basically like this:
create table object_properties {
uuid id;
uuid source_id; // the object which has this property
string name; // the property name
uuid value; // the property value object
}
// ...and same for boolean, integer, etc. properties
create table string_properties {
uuid id;
uuid source_id; // the object which has this property
string name; // the property name
string value; // the property value string
}
In my buildTree I can kind of imagine creating a map from the source_id (the base object node which has property name), to the names, to the values. But then maybe iterating over the source IDs, looking for objects nested inside the name values, and converting them to objects instead of just IDs. But this is getting hard to comprehend and I'm sure there is an easier way somehow.
What is an algorithm to build an "object tree" from this flat list of records?
In my situation, I am fetching a bunch of deeply nested property objects, recursively, and need to stitch back together an object tree out of them.
It looks like the name "nested" plays a special role. When it occurs, the corresponding value property does not hold a literal value to assign to the named property (as is the case with other names), but is a reference to an existing source_id value.
This means your code needs to deal with that name specifically and then establish the parent-child relationship. This relationship is further influenced by the array property.
I would define buildTree as follows, making use of a Map, which is built first using its constructor argument:
function buildTree(startId, arr) {
const map = new Map(arr.map(({source_id}) => [source_id, { id: source_id }]));
for (const {source_id, name, value, array} of arr) {
if (name !== "nested") {
map.get(source_id)[name] = value;
} else if (array) {
(map.get(source_id).nested ??= []).push(map.get(value));
} else {
map.get(source_id).nested = map.get(value);
}
}
return map.get(startId);
}
// Code below has not changed
function shuffleArray(array) { for (var i = array.length - 1, j, temp; i > 0; i--) {j = Math.floor(Math.random() * (i + 1));temp = array[i];array[i] = array[j];array[j] = temp;} return array;}
const tree = buildTree(1, shuffleArray([{ type: 'string', source_id: 1, name: 'foo', value: 'asdf' },{ type: 'integer', source_id: 1, name: 'bar', value: 123 },{ type: 'object', source_id: 1, name: 'nested', value: 2 },{ type: 'object', source_id: 2, name: 'nested', value: 3, array: true },{ type: 'boolean', source_id: 3, name: 'random', value: true },{ type: 'string', source_id: 3, name: 'another', value: 'hello' },{ type: 'object', source_id: 2, name: 'nested', value: 4, array: true },{ type: 'boolean', source_id: 4, name: 'random', value: false },{ type: 'string', source_id: 4, name: 'another', value: 'world' },{ type: 'object', source_id: 2, name: 'nested', value: 5, array: true },{ type: 'boolean', source_id: 5, name: 'random', value: true },{ type: 'string', source_id: 5, name: 'another', value: 'awesome' },]))
console.log(tree);
Note that the order in which objects are pushed into arrays is defined by the original order of the objects. Since this input array is shuffled, the output may show arrays in different ordering on separate runs. Something similar holds for object keys (see Object property order)
You should try Array.prototype.group(). Please refer below document.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/group
const inventory = [
{ name: 'asparagus', type: 'vegetables', quantity: 5 },
{ name: 'bananas', type: 'fruit', quantity: 0 },
{ name: 'goat', type: 'meat', quantity: 23 },
{ name: 'cherries', type: 'fruit', quantity: 5 },
{ name: 'fish', type: 'meat', quantity: 22 }
];
const result = inventory.group(({ type }) => type);
/* Result is:
{
vegetables: [
{ name: 'asparagus', type: 'vegetables', quantity: 5 },
],
fruit: [
{ name: "bananas", type: "fruit", quantity: 0 },
{ name: "cherries", type: "fruit", quantity: 5 }
],
meat: [
{ name: "goat", type: "meat", quantity: 23 },
{ name: "fish", type: "meat", quantity: 22 }
]
}
*/
I have 2 arrays with current week dates and investments with value and date. I want to return an array with the values that have corresponding dates between the 2 arrays.
My non-working solution is:
const daysOfWeek = [
"20-06-2022",
"21-06-2022",
"22-06-2022",
"23-06-2022",
"24-06-2022",
"25-06-2022",
"26-06-2022",
]
const investmentsData = [{
value: 0.77,
date: "21-06-2022"
},
{
value: 1.50,
date: "22-06-2022"
},
{
value: 0.80,
date: "20-06-2022"
},
{
value: 1.00,
date: "21-06-2022"
},
{
value: 0.77,
date: "20-06-2022"
},
{
value: 0.79,
date: "22-06-2022"
},
{
value: 0.73,
date: "18-06-2022"
},
{
value: 1.29,
date: "19-06-2022"
}
]
const result = investmentsData.flatMap((dayValue) => {
const getDayValue = daysOfWeek.filter((day) => {
return dayValue.date === day;
});
return getDayValue;
});
const filteredResult = result.filter((val) => !!val);
console.log(filteredResult)
// ["21-06-2022", "22-06-2022", "20-06-2022", "21-06-2022", "20-06-2022", "22-06-2022"]
When what I need is:
[0.77, 1.50, 0.80, 1.00, 0.77, 0.79]
Probably the filter inside the map is not the best option as it´s going to return the value of the first array (which is a date).
I also have the problem that result returns also the undefined. I then run filteredResult to remove all the undefined in the result. I guess this is a job that can be done with one function all together.
Take it step by step:
Filter investmentsData on whether or not daysOfWeek contains the date
From the filtered values, return the value.
const daysOfWeek = ["20-06-2022", "21-06-2022", "22-06-2022", "23-06-2022", "24-06-2022", "25-06-2022", "26-06-2022"];
const investmentsData = [
{ value: 0.77, date: "21-06-2022" },
{ value: 1.50, date: "22-06-2022" },
{ value: 0.80, date: "20-06-2022" },
{ value: 1.00, date: "21-06-2022" },
{ value: 0.77, date: "20-06-2022" },
{ value: 0.79, date: "22-06-2022" },
{ value: 0.73, date: "18-06-2022" },
{ value: 1.29, date: "19-06-2022" }
]
const result = investmentsData
.filter(d => daysOfWeek.includes(d.date))
.map(d => d.value);
console.log(result);
I nave an array:
const arr = [
{ name: "aa", type: "total", count: 28394 },
{ name: "aa", type: "featured", count: 4 },
{ name: "aa", type: "noAnswers", count: 5816 },
{ name: "ba", type: "total", count: 148902 },
{ name: "ba", type: "featured", count: 13 },
{ name: "ba", type: "noAnswers", count: 32527 },
{ name: "cc", type: "total", count: 120531 },
{ name: "cc", type: "featured", count: 6 },
{ name: "cc", type: "noAnswers", count: 24170 }
];
const arrResult = [
{ name: "aa", total: 28394, featured: 4, noAnswers: 5816 },
{ name: "ba", total: 148902, featured: 13, noAnswers: 32527 },
{ name: "cc", total: 120531, featured: 6, noAnswers: 24170 }
];
I come up with this code:
let output = [];
const unique = [...new Set(arr.map(item => item.name))];
for(const key of unique) {
let result = arr.filter(x => {
return x.name === key;
});
output.push({
name: key,
// need to get the rest of the properties here
// total
// featured
// noAnswers
});
}
The only one thing I can not figure out is how to get the property names.
Any ideas?
You can try something like this:
Idea:
Create a hashMap so you can group objects via name.
Then, add necessary properties to this group.
Finally, loop over keys and create final object with name property added back.
const arr = [ { name: "aa", type: "total", count: 28394 }, { name: "aa", type: "featured", count: 4 }, { name: "aa", type: "noAnswers", count: 5816 }, { name: "ba", type: "total", count: 148902 }, { name: "ba", type: "featured", count: 13 }, { name: "ba", type: "noAnswers", count: 32527 }, { name: "cc", type: "total", count: 120531 }, { name: "cc", type: "featured", count: 6 }, { name: "cc", type: "noAnswers", count: 24170 } ];
const hashMap = arr.reduce((acc, item) => {
acc[item.name] = acc[item.name] || {};
acc[item.name][item.type] = item.count;
return acc;
}, {});
const result = Object.keys(hashMap).map((name) => Object.assign({}, {name}, hashMap[name] ));
console.log(result)
Working:
What I'm doing is I'm creating a new object for every new name. So, this: acc[item.name] = acc[item.name] || {}; checks if the entry is unavailable or not.
If unavailable, return a new object.
If available, return same object's reference.
So for any given name, you will only refer to same object.
Now this: acc[item.name][item.type] = item.count sets the properties. As we are referring to same object, you are setting property in one place. So if you have duplicate entries, say
[
{ name: "aa", type: "total", count: 28394 },
{ name: "aa", type: "total", count: 123},
]
output will have total: 123 instead.
So, at the end, you have a structure like:
{
aa: {
total: <something>,
feature: <something>,
...
}
}
Now all you have to do is merge the name in this object and return the value. You can also create the object with name property as default (as done by adiga). Thats something I didn't think while answering. So crediting instead of answering.
You can use reduce and destructuring like this:
The idea is to create an object with key as the name property and value as the final objects you need in the output. So, that you can simply use Object.values to get the final array:
const arr=[{name:"aa",type:"total",count:28394},{name:"aa",type:"featured",count:4},{name:"aa",type:"noAnswers",count:5816},{name:"ba",type:"total",count:148902},{name:"ba",type:"featured",count:13},{name:"ba",type:"noAnswers",count:32527},{name:"cc",type:"total",count:120531},{name:"cc",type:"featured",count:6},{name:"cc",type:"noAnswers",count:24170}];
const merged = arr.reduce((acc,{name,type,count}) =>
((acc[name] = acc[name] || {name})[type] = count, acc)
,{})
console.log(Object.values(merged))
This is equivalent to :
const arr=[{name:"aa",type:"total",count:28394},{name:"aa",type:"featured",count:4},{name:"aa",type:"noAnswers",count:5816},{name:"ba",type:"total",count:148902},{name:"ba",type:"featured",count:13},{name:"ba",type:"noAnswers",count:32527},{name:"cc",type:"total",count:120531},{name:"cc",type:"featured",count:6},{name:"cc",type:"noAnswers",count:24170}];
/* Our goal is to create a merged object like this:
{
"aa": {
"name": "aa",
"total": 28394,
"featured": 4,
"noAnswers": 5816
},
"ba": {
"name": "ba",
"total": 148902,
....
},
"cc": {
"name": "cc",
......
}
}
The advantage of using object accumulator is we can access it like this: acc[name]
*/
const merged = arr.reduce((acc, {name,type,count} /*Destructuring*/) => {
/* if the accumulator doesn't have the current "name" key,
create new object
else use the existing one;
{name} is same as {name: name}
*/
acc[name] = acc[name] || {name};
/* To the inner object,
add a key with the "type" value and assign it to "count" value
*/
acc[name][type] = count;
// return the accumulator
return acc;
}, {})
// use Object.values to get the value part of the merged obejct into an array
console.log(Object.values(merged))
var op = {name : key};
for(i=0; i < result.length; i++){
op[result[i].type] = result[i].count;
}
output.push(op);
just adding this will work fine. However your code is not the most efficient.
Hashing based on name will make it faster
const arr = [
{ name: "aa", type: "total", count: 28394 },
{ name: "aa", type: "featured", count: 4 },
{ name: "aa", type: "noAnswers", count: 5816 },
{ name: "ba", type: "total", count: 148902 },
{ name: "ba", type: "featured", count: 13 },
{ name: "ba", type: "noAnswers", count: 32527 },
{ name: "cc", type: "total", count: 120531 },
{ name: "cc", type: "featured", count: 6 },
{ name: "cc", type: "noAnswers", count: 24170 }
];
let output = [];
const unique = [...new Set(arr.map(item => item.name))];
for(const key of unique) {
let result = arr.filter(x => {
return x.name === key;
});
var op = {name : key};
for(i=0; i < result.length; i++){
op[result[i].type] = result[i].count;
}
output.push(op);
}
console.log(output);
The following is the most efficient way of doing it :
const arr = [
{ name: "aa", type: "total", count: 28394 },
{ name: "aa", type: "featured", count: 4 },
{ name: "aa", type: "noAnswers", count: 5816 },
{ name: "ba", type: "total", count: 148902 },
{ name: "ba", type: "featured", count: 13 },
{ name: "ba", type: "noAnswers", count: 32527 },
{ name: "cc", type: "total", count: 120531 },
{ name: "cc", type: "featured", count: 6 },
{ name: "cc", type: "noAnswers", count: 24170 }
];
var hash = {};
var result = [];
for(var i=0; i < arr.length; i++){
if(!arr[i].name in hash)
hash[arr[i].name] = {}
let temp = {};
temp[arr[i].type] = arr[i].count;
hash[arr[i].name] = Object.assign(temp, hash[arr[i].name]);
}
for(var key in hash)
result.push({name : key, ...hash[key]})
console.log(result)
You can use find operator of javascript to grab the desired row from arrResult Change your code like below-
for(const key of unique) {
let result = arr.filter(x => {
return x.name === key;
});
var currResult = arrResult.find(x => x.name == key);
output.push({
name: key,
// need to get the rest of the properties here
total: currResult.total,
featured: currResult.featured,
noAnswers: currResult.noAnswers
});
}
JSFiddle: https://jsfiddle.net/ashhaq12345/z8royg5w/
const arr = [
{ name: "aa", type: "total", count: 28394 },
{ name: "aa", type: "featured", count: 4 },
{ name: "aa", type: "noAnswers", count: 5816 },
{ name: "ba", type: "total", count: 148902 },
{ name: "ba", type: "featured", count: 13 },
{ name: "ba", type: "noAnswers", count: 32527 },
{ name: "cc", type: "total", count: 120531 },
{ name: "cc", type: "featured", count: 6 },
{ name: "cc", type: "noAnswers", count: 24170 }
];
const names = [...new Set(arr.map(item => item.name))]
const output = {};
names.forEach(name => {output[name] = {}});
arr.forEach(item => {
output[item.name][item.type] = item.count
});
const result = Object.entries(output).map(([name, rest]) => ({name, ...rest}))
console.log(result);
const arrResult = [
{ name: "aa", total: 28394, featured: 4, noAnswers: 5816 },
{ name: "ba", total: 148902, featured: 13, noAnswers: 32527 },
{ name: "cc", total: 120531, featured: 6, noAnswers: 24170 }
];
You can simply use for loop to iterate over your array and take a temp array and take map and fill the map using required data and then push your map into temp array like following.
const arr = [
{ name: "aa", type: "total", count: 28394 },
{ name: "aa", type: "featured", count: 4 },
{ name: "aa", type: "noAnswers", count: 5816 },
{ name: "ba", type: "total", count: 148902 },
{ name: "ba", type: "featured", count: 13 },
{ name: "ba", type: "noAnswers", count: 32527 },
{ name: "cc", type: "total", count: 120531 },
{ name: "cc", type: "featured", count: 6 },
{ name: "cc", type: "noAnswers", count: 24170 }
];
let result = [];
for( var i = 0; i < arr.length; i++)
{
let data = {};
if( arr[i].type == 'total')
{
data.name = arr[i].name;
data.total = arr[i].count;
data.featured = arr[i+1].count;
data.noAnswers = arr[i+2].count;
result.push(data);
}
}
console.log(result);
I have an array of object like this:
let messageScoreData = {
messagescore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 2605.4
},
{
userid: "5bacc98431481e0520856df8",
score: 1013.2
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 41
},
{
userid: "5bc6d0bb26f1bb1b44a790c9",
score: 29
}
],
messagescorebefore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 3754
},
{
userid: "5bacc98431481e0520856df8",
score: 1259.8
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 98
},
{
userid: "5bced078d62b321d08f012af",
score: 22
},
{
userid: "5bcec1ad11302529f452b31e",
score: 6
},
{
userid: "5c10afec8c587d2fac8c356e",
score: 6
},
{
userid: "5c07b7f199848528e86e9359",
score: 3
},
{
userid: "5bed1373f94b611de4425259",
score: 2
},
{
userid: "5c21ccff833a5006fc5a98af",
score: 2
},
{
userid: "5c21ccff82e32c05c4043410",
score: 1
}
]
};
Now we will provide the weight-age value i.e messagescorebefore array have 0.4 value and messagescore have 0.6 value;
For that I have the algorithm which sequentialize the value with weight-age value. i.e
var result = messageScoreData;
var columns = [
{
name: "messagescorebefore",
value: 0.4
},
{
name: "messagescore",
value: 0.6
}
];
var total = {};
for (let column of columns) {
for (let userid of result[column.name]) {
var alphabet = userid.userid;
if (total[alphabet]) {
total[alphabet] += column.value;
} else {
total[alphabet] = column.value;
}
}
}
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value);
console.log(valueholder);
By this Algo output is :
[ { name: '5bacc8c6563a882a1ca7756a', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c6', value: 1 },
{ name: '5bacc98431481e0520856df8', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c9', value: 0.6 },
{ name: '5bcec1ad11302529f452b31e', value: 0.4 },
{ name: '5bced078d62b321d08f012af', value: 0.4 },
{ name: '5c07b7f199848528e86e9359', value: 0.4 },
{ name: '5bed1373f94b611de4425259', value: 0.4 },
{ name: '5c21ccff833a5006fc5a98af', value: 0.4 },
{ name: '5c21ccff82e32c05c4043410', value: 0.4 },
{ name: '5c10afec8c587d2fac8c356e', value: 0.4 } ]
Problem is userid: "5bacc98431481e0520856df8" will come on second position on both array but after final calculation this will come under 3rd position which is wrong.
expected output will be like this:
[ { name: '5bacc8c6563a882a1ca7756a', value: 1 },
{ name: '5bacc98431481e0520856df8', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c6', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c9', value: 0.6 },
{ name: '5bced078d62b321d08f012af', value: 0.4 },
{ name: '5bcec1ad11302529f452b31e', value: 0.4 },
{ name: '5c10afec8c587d2fac8c356e', value: 0.4 },
{ name: '5c07b7f199848528e86e9359', value: 0.4 },
{ name: '5bed1373f94b611de4425259', value: 0.4 },
{ name: '5c21ccff833a5006fc5a98af', value: 0.4 },
]
Any help is really appreciated for this. Thanks in advance
Actually, you want to preserve relative order of elements. normal sort function is not guaranteed to preserve relative order. so we need some tricks to keep relative order like below.
let messageScoreData = {
messagescore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 2605.4
},
{
userid: "5bacc98431481e0520856df8",
score: 1013.2
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 41
},
{
userid: "5bc6d0bb26f1bb1b44a790c9",
score: 29
}
],
messagescorebefore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 3754
},
{
userid: "5bacc98431481e0520856df8",
score: 1259.8
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 98
},
{
userid: "5bced078d62b321d08f012af",
score: 22
},
{
userid: "5bcec1ad11302529f452b31e",
score: 6
},
{
userid: "5c10afec8c587d2fac8c356e",
score: 6
},
{
userid: "5c07b7f199848528e86e9359",
score: 3
},
{
userid: "5bed1373f94b611de4425259",
score: 2
},
{
userid: "5c21ccff833a5006fc5a98af",
score: 2
},
{
userid: "5c21ccff82e32c05c4043410",
score: 1
}
]
};
var result = messageScoreData;
var columns = [
{
name: "messagescorebefore",
value: 0.4
},
{
name: "messagescore",
value: 0.6
}
];
var total = [];
for (let column of columns) {
for (let userid of result[column.name]) {
var alphabet = userid.userid;
if (total[alphabet]) {
total[alphabet] += column.value;
} else {
total[alphabet] = column.value;
}
}
}
let res = Object.keys(total).map((k, idx) => {
return {
name: k,
value: total[k],
index: idx
}
})
var output = res.sort((f, s) => {
if (s.value < f.value) return -1;
if (s.value > f.value) return 1;
return f.index - s.index
})
console.log("output : ", output)
The observed behaviour is expected since you are sorting the values in a descending way: .sort((f, s) => s.value - f.value);. From your example it seems that you want to sort the entries lexicographically on the names. In that case you should sort according to the names:
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => f.name.localeCompare(s.name));
If you want to sort them primarily on the values (descending) and secondarily on the names (ascending) then do:
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value || f.name.localeCompare(s.name));
In this case, if two entries have the same value the difference s.value - f.value will be 0. Since this is a falsy value, f.name.localeCompare(s.name) will be evaluated, effectively sorting the values lexicographically on their name.
If you want to sort the entries based on their values but retain the original order for entries with the same value you can do the following:
const entries = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
const valueholder = entries.sort((f, s) => s.value - f.value || arr.indexOf(f) - arr.indexOf(s));
The reason we need to explicitly sort on their original order is because the built-in sorting algorithm is not (guaranteed to be) stable. Note that the above sorting is not very efficient since we use indexOf. I leave it as an exercise to first loop through the array and accumulate all indexes in a map that maps names to indexes. As such, when sorting you can look up the indexes rather than computing them.
If you're looking for stable sort, i.e. preserving the original order of elements of the array with equal value, you have to add a comparison of the indexes of the key array (assuming that this has the proper ordering):
const keys = Object.keys(total);
const valueholder = keys
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value || keys.indexOf(f.name) < keys.indexOf(s.name));