Related
if I have an array of strings like:
['person,item,cost,amount',
'John,shoes,200,2']
how could I convert this into an object that resembles:
{
'John':[
{
item:'shoes',
cost:'200',
amount:'2',
totalPriceForItems:'400'
}
If I understand correctly, you may try something like this:
const convert = data => {
const [columnsText, ...items] = data;
const columns = columnsText.split(',');
return items.reduce((acc, text) => {
const { person, ...entries } = Object.fromEntries(text.split(',').map((value, i) => [columns[i], value]));
entries.totalPriceForItems = String(entries.cost * entries.amount);
if(acc[person]) {
acc[person].push(entries);
} else {
acc[person] = [entries];
}
return acc;
}, {});
};
const result = convert([
'person,item,cost,amount',
'John,shoes,200,2',
'Bob,glasses,50,3',
'John,shirts,100,5',
]);
console.log(result);
According to your comment,
I have 8 lines of 'John,shoes,200,2' but with different amounts in the
same array. The 'person,item,cost,amount' is only mentioned once at
the start of the array
What I understand is that you have a csv with headers and multiple rows.
If that is the case, then your data would resemble something like this:
data = [
'person,item,cost,amount',
'John,shoes,200,2',
'Adam,pants,60,1',
'Kelly,skirt,180,2',
'John,skirt,150,3'
]
Then you could consider the following approach, that is generic enough to adapt to different headers, and multiple data rows with repeated keys (person names).
// here, you define a function to transform each row of your data,
// like parsing numeric attributes and calculating the totals
function transform(row) {
row.cost = Number.parseInt(row.cost)
row.amount = Number.parseInt(row.amount)
row.total = row.cost * row.amount
return row
}
// The following logic is generic, and can be used
// to map and aggregate any kind of csv with headers
hdrs = data.shift().split(',').slice(1)
rows = data.map(r => r.split(',')).reduce((acc, [n, ...kvs]) =>
({ ...acc, [n]: [...acc[n] || [], transform(Object.fromEntries(kvs.map((v, i) => [hdrs[i], v])))] }), {})
Output:
{
John: [
{ item: "shoes", cost: 200, amount: 2, total: 400 },
{ item: "skirt", cost: 150, amount: 3, total: 450 }],
Adam: [
{ item: "pants", cost: 60, amount: 1, total: 60 }],
Kelly: [
{ item: "skirt", cost: 180, amount: 2, total: 360 }]
}
I have an array, that looks like this(size changes):
[
{ '385090261019131915': 34 },
{ '746430449240375297': 2 },
{ '810189312175374408': 1 },
{ '830832432680009789': 8 },
{ '850073735272988692': 1 }
]
The first value is the member id, the second how many messages the user has.
How can I sort the array, to get the first 10 members, sorted by their messages send?
The code:
if(command === 'leaderboard'){
const list = []
fs.readdirSync('./db/user/messages').forEach(file => {
const user = JSON.parse(fs.readFileSync(`./db/user/messages/${file}` , 'utf-8'))
userid = file.replace('.json','');
const entry = {[userid] : user.userall}
list.push(entry)
})
}
To sort an array by numbers, you can use the .sort() method with a compare function that subtracts the second value from the first one:
const arr = [34, 2, 1, 8, 1]
const sorted = arr.sort((a, b) => b - a)
console.log({ sorted })
As you're using objects, you should sort by an object key, but you're using the user ID as the key, so you don't know them. You can, however, get the value using the [Object.values()][2] method to get the value(s) and sort by them:
const arr = [
{ '385090261019131915': 34 },
{ '746430449240375297': 2 },
{ '810189312175374408': 1 },
{ '830832432680009789': 8 },
{ '850073735272988692': 1 }
]
const sorted = arr.sort((a, b) => Object.values(b)[0] - Object.values(a)[0])
console.log({ sorted })
Don't forget that Object.values() returns an array so you'll need to compare the first element.
However, instead of using the user ID as the key and the points as the value, I'd use two different keys in the object, one for the ID and one for the score:
const list = [
{ id: '385090261019131915', score: 34 },
{ id: '746430449240375297', score: 2 },
{ id: '810189312175374408', score: 1 },
{ id: '830832432680009789', score: 8 },
{ id: '850073735272988692', score: 1 }
]
const sortedList = list.sort((a, b) => b.score - a.score)
console.log({ sortedList })
And the final code:
if (command === 'leaderboard') {
const list = []
fs.readdirSync('./db/user/messages').forEach((file) => {
const user = JSON.parse(
fs.readFileSync(`./db/user/messages/${file}`, 'utf-8'),
)
const userId = file.replace('.json', '')
list.push({ id: userId, score: user.userall })
});
// sort by score
const sortedList = list.sort((a, b) => b.score - a.score)
}
I am using Axios to execute a GET request to a public API, I need to combine the names if they are the same and add the values up to only show the top 20 (It's a large dataset) based on the highest to lowest amounts(ascending order).
Axios Response
[
{
name: "foo1",
value: "8123.30"
},
{
name: "foo1",
value: "2852.13"
},
{
name: "foo2",
value: "5132.23"
},
{
name: "foo1",
value: "1224.20"
},
{
name: "foo2",
value: "1285.23"
}
1200...
];
Expected Output
[
{ name: "foo1",
value: "12199.63" // from all combined "foo1" amounts in the dataset
},
{
name: "foo2",
value: "6417.46" // from all combined "foo2" amounts in the dataset
},
18..
]
I tried to do something like this....
const fetchData = () => {
return axios.get(url)
.then((response) => response.data)
};
function onlyWhatINeed() {
const newArr = []
return fetchData().then(data => {
const sortedData = data.sort((a, b) => parseFloat(a.value) - parseFloat(b.value));
// I need to loop through the dataset and add all the "values" up
// returning only the top 20 highest values in an array of those objects
newArr.push(sortedData)
})
}
But I am confused as to how to push this data to a new array of the sorted data (top 20 values in ascending order) and use this data in my web application. I am a bit new to creating REST APIs so if you could provide articles and/or resources so I can understand a little more that would be an awesome bonus!
You can combine the entries that share the same name using a map, then sort the map and keep the first twenty elements :
function onlyWhatINeed() {
const newArr = []
return fetchData().then(data => {
let map = new Map();
data.forEach(d => {
if(!map.has(d.name)) {
map.set(d.name, parseFloat(d.value));
} else {
map.set(d.name, map.get(d.name) + parseFloat(d.value));
}
})
return Array.from(map.entries()).sort((a, b) => a.value - b.value).slice(0, 20);
})
}
Since you're dealing with a large dataset, I recommend that you handle this server side instead of offloading the sorting to your clients.
async function fetchData(){
const { data } = await axios.get(url);
let newArr = []
data.forEach((e,i) => {
let index = newArr.findIndex(el => el.name === e.name);
if(index !== -1 ) newArr[index].value += parseFloat(e.value); //add to the value if an element is not unique
if(index === -1 ) newArr.push({...e, value: parseFloat(e.value)}); //push to the array if the element is unique and convert value to float
});
return newArr.sort((a,b) => a.value - b.value).slice(0,20);//returns an array of 20 elements after sorting
}
Please do more research on how to work with arrays and objects in general.
If you happen to already be using lodash, then here's a functional-style solution using lodash chaining. Probably not optimal performance, but could be useful for relatively small datasets.
const _ = require('lodash');
const data = [
{
name: "foo1",
value: "8123.30"
},
{
name: "foo1",
value: "2852.13"
},
{
name: "foo2",
value: "5132.23"
},
{
name: "foo1",
value: "1224.20"
},
{
name: "foo2",
value: "1285.23"
},
{
name: "foo3",
value: "1000.00"
},
{
name: "foo3",
value: "2000.00"
}
];
// 1. convert string values to floats
// 2. group by name
// 3. sum values by name
// 4. sort by descending value
// 5. take top 20
const output =
_(data)
.map(obj => ({
name: obj.name,
value: parseFloat(obj.value)
}))
.groupBy('name')
.map((objs, key) => ({
name: key,
value: _.sumBy(objs, 'value')
}))
.orderBy(['value'], 'desc')
.slice(0, 20)
.value();
console.log('output:', output);
I have an array of objects that represents a set of tours but there are different prices for the same tour like this:
let tours = [{'id':1, price:200},
{'id':1, price:300},
{'id':3, price:150},
{'id':2, price:110},
{'id':3, price:120},
{'id':2, price:100}]
So, I would like to pick the lowest price available by the tour ID and push it into a new array as unique tour with the lowest price.
So the result would be:
result = [{'id':1, price:200},
{'id':3, price:120},
{'id':2, price:100},]
I tried methods in Lodash like _.minBy()but it returns one from all the array.
Lodash Solution
You can _.groupBy() ids, than _.map() the result, and take the lowest of each group with _.minBy():
const tours = [{"id":1,"price":200, prop: 'prop1' },{"id":1,"price":300, prop: 'prop1'},{"id":3,"price":150},{"id":2,"price":110},{"id":3,"price":120},{"id":2,"price":100}];
const result = _(tours)
.groupBy('id')
.map((group) => _.minBy(group, 'price'))
.value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
VanillaJS Solution
Reduce the tours to a Map with the ids as key. On each iteration take the group with lowest price. When done, spread the Map.values() back to an array:
const tours = [{"id":1,"price":200, prop: 'prop1' },{"id":1,"price":300, prop: 'prop1'},{"id":3,"price":150},{"id":2,"price":110},{"id":3,"price":120},{"id":2,"price":100}];
const lowest = [...tours.reduce((r, o) => {
const { id, price } = o;
const current = r.get(id);
if(!current || price < current.price) r.set(id, { ...o });
return r;
}, new Map()).values()];
console.log(lowest);
Or you can use simply reduce and keep updating minimum value in the accumulator
tours.reduce( (acc, c) => {
acc[c.id] = acc[c.id] ? Math.min( c.price, acc[c.id] ) : c.price; //update min value in accumulator
return acc; // return accumulator
} ,{}) //initialize accumulator to {}
Demo
let tours = [{
'id': 1,
price: 200
},
{
'id': 1,
price: 300
},
{
'id': 3,
price: 150
},
{
'id': 2,
price: 110
},
{
'id': 3,
price: 120
},
{
'id': 2,
price: 100
}
];
var output = tours.reduce((acc, c) => {
acc[c.id] = acc[c.id] ? Math.min(c.price, acc[c.id]) : c.price;
return acc;
}, {});
console.log(output);
I have this object data:
[ RowDataPacket {
id: 59,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 60,
steamid: '76561198220437096',
product_id: 24,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 250,
website: 'csgo500' },
RowDataPacket {
id: 61,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 62,
steamid: '76561198345348530',
product_id: 6,
status: 1,
date: 2017-12-18T20:05:55.000Z,
message: null,
name: 'wal gruche',
amount: 100,
website: 'csgoatse' }
Im trying to sort this data with steamid and website, i managed to sort this only by one value like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid] = [item];
}
});
My idea was to make two dimensional array but for some reason i cant do it like this:
var list = groupedOrders[item.steamid][item.website];
It throws me an error "Cant read property ... of undefined"
Now my code looks like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid][item.website];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid][item.website] = [item];
}
});
Do you have any ideas how to fix this errors?
The problem is that var list = groupedOrders[item.steamid][item.website] is actually saying:
var temp = groupedOrders[item.steamid];
var list = temp[item.website];
There is no entry at groupedOrders[item.steamid] and so line one sets temp to undefined. The second line tries to index into undefined which is an error.
You would have to split the code out and essentially do the whole one-key grouping twice:
var outerList = groupedOrders[item.steamid];
if (!outerList)
outerList = groupedOrders[item.steamid] = {};
var innerList = outerList[item.website];
if (innerList)
innerList.push(item);
else
outerList[item.website] = [item];
(I have not tested this code but it is the right shape.)
The following works by creating a recursive groupBy grouping function for each of the fields supplied as an argument.
These dynamically created groupBy functions are then invoked one by one, passing the result between, starting with the supplied data.
Each groupBy function instance creates an object and adds properties to it corresponding to the key values for the field being grouped.
By calling these groupBy functions successively, we create a progressively more nested tree of objects, with groups at each successive level marked as being groups using a symbol.
The final result is a nest (a tree!) of objects, with keys corresponding to the field used for indexing at that level.
Finally, we flatten the nest and the final order is visible.
const flatten = o => Object.values(o).reduce((acc, c) => (Array.isArray(c) ? [...acc, ...c] : typeof c === 'object' ? [...acc, ...flatten(c)] : [...acc, c]), []);
const flow = (...fns) => data => fns.reduce((acc, c) => c(acc), data);
const GROUP = Symbol('group');
const asGroup = (result = []) => ((result[GROUP] = true), result);
const isGroup = o => o[GROUP];
const groupBy = field => (data, key) =>
data.reduce((acc, c) =>
((key = c[field]), (acc[key] ?
(acc[key].push(c), acc) :
((acc[key] = asGroup([c])), acc))), {});
const recurse = (test) => (transform) => o =>
test(o)
? transform(o)
: Object.entries(o).reduce(
(acc, [k, v]) => (test(v) ?
((acc[k] = transform(v)), acc) :
((acc[k] = recurse(test)(transform)(v)), acc)), {});
const group = (...fields) => flow(...fields.map(flow(groupBy, recurse(isGroup))), flatten);
const rows = asGroup([
{
id: 0,
steamid: '2',
website: 'a'
},
{
id: 1,
steamid: '2',
website: 'b'
},
{
id: 2,
steamid: '2',
website: 'a'
},
{
id: 3,
steamid: '1',
website: 'b'
},
{
id: 4,
steamid: '0',
website: 'b'
}
]);
console.log(JSON.stringify(group('steamid', 'website')(rows), null, 2));