I'm working on a project that involves two dimensional arrays of objects. I've been working to try to figure out this answer for a while now, and I have some ideas of how to solve it, but I'm a bit stumped.
Let's assume there are animal shelters in City A and that each can hold 50 animals. Animals are coming in from different parts of the state, but the amount of animals from one place can never be more than 50. Here's an example of the animals coming in.
let animal_shelter_capacity =< 50;
let array 1 = [
{ "region": "NE", quantity: 25 },
{ "region": "NW", quantity: 21 },
{ "region": "SE", quantity: 43 },
{ "region": "SW", quantity: 18 },
{ "region": "Central", quantity: 20}
]
In this example, the animals from NE (25) and NW (21) would go to one shelter (46 animals in total), the animals from SE (43) would go to another shelter (43 animals in total), and the animals from SW (18) and Central (20) would go to a third shelter (38 animals in total). The number of animals in one shelter can never be greater than 50.
So, I need to produce an array that looks like this:
let array2 = [
[ { "region": "NE", quantity: 25 }, { "region": "NW", quantity: 21 }],
[ { "region": "SE", quantity: 43 } ],
[ { "region": "SW", quantity: 18 }, { "region": "Central", quantity: 20} ]
]
I'm able to loop through array1 using forEach, but when it comes to adding until a certain value is reached, then creating a new array of arrays, I'm a little stumped on how to proceed to do this.
Here's what I have so far:
let array2 = [] //instantiate array
array1.forEach(function(element, index, array)
{
let sum = 0;
let capacity = 50;
for (let j = 0; j < array1.length; j++)
{
sum += array1[j].quantity;
if (sum >= capacity)
{
//create the new array consisting of the regions, push it into the larger array2
}
}
})
I'm not sure how to continue doing this. I know I need to do the following:
1. find a way to cut off the addition sequence once the quantity reaches 50
2. reset the sequence
3. form the new arrays and push them into a larger array
Can anyone provide any advice on how to proceed?
Try this. Loop through shelters, if it can fit, add it to the current shelter list. If not, save the current shelter roster and start a new one. After the loop, make sure to save the current roster being written
const locations = [{
"region": "NE",
"quantity": 25
},
{
"region": "NW",
"quantity": 21
},
{
"region": "SE",
"quantity": 43
},
{
"region": "SW",
"quantity": 18
},
{
"region": "Central",
"quantity": 20
}
]
const shelterRoster = [];
const capacity = 50;
let count = 0;
for (let location of locations) {
let shelter = shelterRoster[shelterRoster.length - 1];
if (!shelter || count + location.quantity > capacity) {
shelterRoster.push([location]);
count = 0;
} else {
shelter.push(location);
}
count += location.quantity
}
console.log(shelterRoster);
You can approach this with reduce(), using a custom object as the initial accumulator. When the reduce finish, you will need to do an extra line of code for get your final result.
const animal_shelter_capacity = 50;
const array1 = [
{"region": "NE", quantity: 25},
{"region": "NW", quantity: 21},
{"region": "SE", quantity: 43},
{"region": "SW", quantity: 18},
{"region": "Central", quantity: 20}
];
let obj = array1.reduce((res, curr) =>
{
let test = (curr.quantity + res.c >= animal_shelter_capacity);
return {
r: test ? [...res.r, res.a] : res.r,
c: test ? curr.quantity : res.c + curr.quantity,
a: test ? [curr] : [...res.a, curr]
};
},{r:[], c:0, a:[]});
let newArr = [...obj.r, obj.a];
console.log(newArr);
On the previous code, the accumulated object have the next keys:
r: The array of shelters generated progressively.
c: The counter of animals of the current shelter (see next).
a: The current shelter of animals.
When the reduce finish, the last shelter (that on the property a) will not be on the array of shelters. So, we have to put it manually (this is what the extra line does).
The first point I've come up with, is you need to sort input data first. because your given input ( as asked in the question ) is not the only possible way of having data.
You can have some data like:
let array1 = [
{ "region": "NE", quantity: 25 },
{ "region": "NW", quantity: 21 },
{ "region": "Central", quantity: 20 },
{ "region": "SE", quantity: 43 },
{ "region": "SW", quantity: 18 },
]
and in this example, we should have pushed central and SW together, but not sorting the input at first place will result central and SW in different arrays.
So, conclusion. I think this is gonna work:
var make = function( arr ) {
var res = [],
currentArr = [];
arr.forEach( v => {
sum += v.quantity;
if ( sum <= capacity ) {
currentArr.push( v );
} else {
res.push( currentArr );
currentArr = [ v ];
sum = v.quantity;
}
});
res.push( currentArr );
return res;
},
array1 = [
{ "region": "NE", quantity: 25 },
{ "region": "NW", quantity: 21 },
{ "region": "Central", quantity: 20 },
{ "region": "SE", quantity: 43 },
{ "region": "SW", quantity: 18 }
],
sum = 0,
result,
capacity = 50;
array1.sort( ( a, b ) => {
return a.quantity - b.quantity;
});
console.log( array1 );
result = make( array1 );
console.log( result );
Related
I have the following array
Array["MyArray",
{
"isLoaded":true,
"items":
[{
"id":"4",
"name":"ProductA",
"manufacturer":"BrandA",
"quantity":1,
"price":"25"
},{
"id":"1",
"name":"ProductB",
"manufacturer":"BrandB",
"quantity":5,
"price":"20"
}],
"coupons":null
}
]
I need to load product names and their quantity from the array.
const result = [key, value].map((item) => `${item.name} x ${item.quantity}`);
Here's one possible way to achieve the desired result:
const getProductsAndQuantity = ([k , v] = arr) => (
v.items.map(it => `${it.name} x ${it.quantity}`)
);
How to use it within the context of the question?
localforage.iterate(function(value, key, iterationNumber) {
console.log([key, value]);
const val2 = JSON.parse(value);
if (val2 && val2.items && val2.items.length > 0) {
console.log(val2.items.map(it => `${it.name} x ${it.quantity}`).join(', '))
};
});
How it works?
Among the parameters listed in the question ie, value, key, iterationNumber, only value is required.
The above method accepts the key-value pair as an array (of 2 elements) closely matching the console.log([key, value]); in the question
It uses only v (which is an object). On v, it accesses the prop named items and this items is an Array.
Next, .map is used to iterate through the Array and return each product's name and quantity in the desired/expected format.
Test it out on code-snippet:
const arr = [
"MyArray",
{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}
];
const getProductsAndQuantity = ([k, v] = arr) => (
v.items.map(
it => `${it.name} x ${it.quantity}`
)
);
console.log(getProductsAndQuantity());
I understood. You should learn about array methods such as map, filter, reduce. Here you go...
const items = [{
"id":"4",
"name":"ProductA",
"manufacturer":"BrandA",
"quantity":1,
"price":"25"
},{
"id":"1",
"name":"ProductB",
"manufacturer":"BrandB",
"quantity":5,
"price":"20"
}];
const result = items.map((item) => `${item.name} x ${item.quantity}`);
console.log(result);
I think I understand the question to say that the input is an array of objects, each containing an array of items. The key is that a nested array requires a nested loop. So, we iterate the objects and their internal items (see the lines commented //outer loop and // inner loop below)
Also, half-guessing from the context, it looks like the that the OP aims to assemble a sort of invoice for each object. First a demo of that, (and see below for the version simplified to exactly what the OP asks)...
const addInvoice = obj => {
let total = 0;
// inner loop
obj.invoice = obj.items.map(i => {
let subtotal = i.quantity * i.price;
total += subtotal
return `name: ${i.name}, qty: ${i.quantity}, unit price: ${i.price}, subtotal: ${subtotal}`
});
obj.invoice.push(`invoice total: ${total}`);
}
const objects = [{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}]
// outer loop
objects.forEach(addInvoice);
console.log(objects);
If my guess about the goal went to far, just remove the unit price, subtotal and total lines from the invoice function...
const objects = [{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}]
const summaryString = obj => {
return obj.items.map(i => `${i.name}, ${i.quantity}`);
}
const strings = objects.map(summaryString);
console.log(strings);
In my JavaScript, I have an integer that represents an order quantity, and an object of all quantities offered:
{
"1": {
"quantity": 1,
"price": 10
},
"2": {
"quantity": 2,
"price": 20
},
"6": {
"quantity": 6,
"price": 50
},
"12": {
"quantity": 12,
"price": 80
}
}
I need to find the object with a quantity value greater than my order quantity, but smaller than the quantity value of the next object in the sequence.
For example, if my order quantity is 8, I need to isolate:
"6": {
"quantity": 6,
"price": 50
},
So I can get the correct price. I've tried various LoDash methods, but nothing seems to be quite right. Is there a way that I can achieve this?
You could take the keys and reverse the array and find the smaller or equal count.
function find(object, quantity) {
return Object.keys(object).reverse().find(v => v <= quantity);
}
var object = { 1: { quantity: 1, price: 10 }, 2: { quantity: 2, price: 20 }, 6: { quantity: 6, price: 50 }, 12: { quantity: 12, price: 80 } };
console.log(find(object, 8));
Based on your description, I'm assuming what you're trying to do is find the record corresponding to the lowest quantity higher than the input quantity. The example doesn't actually match that description though. If you're looking for the opposite (next largest, smaller than the input) you could flip the two inequalities.
some_prices = {
"1": {
"quantity": 1,
"price": 10
},
"2": {
"quantity": 2,
"price": 20
},
"6": {
"quantity": 6,
"price": 50
},
"12": {
"quantity": 12,
"price": 80
}
}
getNextQuantityPrice = (prices, quantity) => {
const largerQuantities = Object.values(prices)
.filter(value => value.quantity > quantity);
if (largerQuantities.length === 0) return null;
return largerQuantities
.reduce((min, next) => next.quantity < min.quantity ? next : min)
}
console.log(JSON.stringify(getNextQuantityPrice(some_prices, 2)));
If you just need to iterate through keys in a Javascript object, this has been answered many times.
var prices = {
"1": {
"quantity": 1,
"price": 10
},
"2": {
"quantity": 2,
"price": 20
},
"6": {
"quantity": 6,
"price": 50
},
"12": {
"quantity": 12,
"price": 80
}
};
for (qty in prices) {
// Do stuff
console.log(qty);
}
If you just need to tease out the quantities available (your object keys), use Object.keys(prices), which returns an array you can loop over.
Once the data are in an array, you can do another for loop, or start getting fancy with forEach(), filter(), and the like.
Like the other answers, I find the best way to deal with the basic question is to reverse the list. But rather than depending upon the original object being ordered the way you like, I think it's best to sort it up front, and then we can just do so in reverse order.
const nextQuantity = (quantities) => {
const reversed = Object .values (quantities) .sort (({quantity: q1}, {quantity: q2}) => q2 - q1)
return (requested) => reversed .find (({quantity}) => quantity < requested)
}
const quantities = {1: {quantity: 1, price: 10}, 2: {quantity: 2, price: 20}, 6: {quantity: 6, price: 50}, 12: {quantity: 12, price: 80}}
console .log (
nextQuantity (quantities) (8)
)
This also gives us a chance to store an intermediate function that doesn't need to reverse the list on every call. nextQuantity(quantities) returns a function you can call repeatedly with each requested quantity.
If you really want the output format from the question (that is, not just {quantity: 6, price: 50} but that result wrapped in a single-property object like {'6': {quantity: 6, price: 50}}) then you could rewrite it as
const nextQuantity = (quantities) => {
const reversed = Object .values (quantities) .sort (({quantity: q1}, {quantity: q2}) => q2 - q1)
return (requested) => {
const q = reversed .find (({quantity}) => quantity < requested)
return {[q.quantity]: q}
}
}
But I personally would find that format much harder to work with.
I am trying to aggregate and transform the following json :
[
{
"orderId" : "01",
"date" : "2017-01-02T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 12,
"itemQuantity": 10
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 4
}
]
},
{
"orderId": "02",
"date" : "2017-01-08T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 15,
"itemQuantity": 2
},
{
"itemId": 101,
"itemCost": 20,
"itemQuantity": 5
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 1
}
]
},
{
"orderId": "03",
"date" : "2017-02-08T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 15,
"itemQuantity": 2
},
{
"itemId": 101,
"itemCost": 20,
"itemQuantity": 5
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 1
}
]
}]
into an object that is grouped by itemId, and then aggregated by quantity, and aggregated by total cost (item cost * item quantity for each order) by month. Example:
[
{
"itemId": 100,
"period": [
{
"month": "01/17",
"quantity": 12,
"cost": 130
}
]
},
{
"itemId": 101,
"period": [
{
"month": "01/17",
"quantity": 5,
"cost": 100
},
{
"month": "02/17",
"quantity": 5,
"cost": 100
}
]
},
{
"itemId": 102,
"period": [
{
"month": "01/17",
"quantity": 5,
"cost": 125
},
{
"month": "02/17",
"quantity": 1,
"cost": 25
}
]
}
]
I have a small indention on my desk in which I have been beating my head trying to figure how to do this using native map/reduce or lodash.
You can do like this:
var orders = [{orderId:"01",date:"2017-01-02T06:00:00.000Z",items:[{itemId:100,itemCost:12,itemQuantity:10},{itemId:102,itemCost:25,itemQuantity:4}]},{orderId:"02",date:"2017-01-08T06:00:00.000Z",items:[{itemId:100,itemCost:15,itemQuantity:2},{itemId:101,itemCost:20,itemQuantity:5},{itemId:102,itemCost:25,itemQuantity:1}]},{orderId:"03",date:"2017-02-08T06:00:00.000Z",items:[{itemId:100,itemCost:15,itemQuantity:2},{itemId:101,itemCost:20,itemQuantity:5},{itemId:102,itemCost:25,itemQuantity:1}]}];
// First, map your orders by items
var items = {};
orders.forEach(function(order) {
// set the month of each order
var month = new Date(order.date);
month = ('0' + (month.getMonth() + 1)).slice(-2) + '/' + String(month.getFullYear()).slice(-2);
// for each item in this order
order.items.forEach(function(item) {
// here we already have both keys: "id" and "month"
// then, we make sure they have an object to match
var id = item.itemId;
if (!items[id]) {
items[id] = {};
}
if (!items[id][month]) {
items[id][month] = { cost:0, quantity:0 };
}
// keep calculating the total cost
items[id][month].cost += item.itemCost * item.itemQuantity;
items[id][month].quantity += item.itemQuantity;
});
});
// Now, we format the calculated values to your required output:
var result = Object.keys(items).map(function(id) {
var obj = {
itemId: id,
period: Object.keys(items[id]).map(function(month) {
items[id][month].month = month;
return items[id][month];
}),
};
return obj;
});
console.log(result);
Hope it helps.
You could use this transformation:
const result = Object.values(myList.reduce( (acc, o) => {
const month = o.date.substr(5,2) + '/' + o.date.substr(2,2);
return o.items.reduce ( (acc, item) => {
const it = acc[item.itemId] || {
itemId: item.itemId,
period: {}
},
m = it.period[month] || {
month: month,
quantity: 0,
cost: 0
};
m.cost += item.itemCost * item.itemQuantity;
m.quantity += item.itemQuantity;
it.period[month] = m;
acc[item.itemId] = it;
return acc;
}, acc);
}, {})).map( o =>
Object.assign({}, o, { period: Object.values(o.period) })
);
const myList = [
{
"orderId" : "01",
"date" : "2017-01-02T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 12,
"itemQuantity": 10
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 4
}
]
},
{
"orderId": "02",
"date" : "2017-01-08T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 15,
"itemQuantity": 2
},
{
"itemId": 101,
"itemCost": 20,
"itemQuantity": 5
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 1
}
]
},
{
"orderId": "03",
"date" : "2017-02-08T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 15,
"itemQuantity": 2
},
{
"itemId": 101,
"itemCost": 20,
"itemQuantity": 5
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 1
}
]
}];
const result = Object.values(myList.reduce( (acc, o) => {
const month = o.date.substr(5,2) + '/' + o.date.substr(2,2);
return o.items.reduce ( (acc, item) => {
const it = acc[item.itemId] || {
itemId: item.itemId,
period: {}
},
m = it.period[month] || {
month: month,
quantity: 0,
cost: 0
};
m.cost += item.itemCost * item.itemQuantity;
m.quantity += item.itemQuantity;
it.period[month] = m;
acc[item.itemId] = it;
return acc;
}, acc);
}, {})).map( o =>
Object.assign({}, o, { period: Object.values(o.period) })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think the other answers out there do a pretty good job from the vanilla angle, so I wanted to take a stab at a more lodash-intensive approach since you mentioned it as a tag. This is mainly just a fun challenge, but I hope the solution is elegant enough for you to lift components from.
Before we begin, I'll be using both the vanilla lodash module and the functional programming flavor of lodash. Let fp be the functional programming module and _ be vanilla (and let orders be your original data structure). Also, as a challenge, I'll do my best to minimize vanilla JS methods and arrow funcs to maximize lodash methods and function creation methods.
First, let's get all the items in a row, paired with their order information:
const items = _.flatMap(orders, o=> _.map(o.items, i=> [i, o]));
I know I said I wanted to minimize arrow functions, but I couldn't think of any other way to get the order object to the end of the chain. Challenge yourself to rewrite the above in terms of a composition (e.g. fp.compose or _.flow) and see what happens.
I'd say now's as good a time as any to group up our pairs by the item id:
const id_to_orders = _.groupBy(items, fp.get('[0].itemId'));
Here, fp.get('[0].itemId') gives us a function which, given an array, returns the itemId of the first element (in our case, we have a list of pairs, the first element of which is the item, the second of which is the relevant order object). Therefore, id_to_orders is a map from an item's ID to a list of all the times it was ordered.
This id_to_orders map looks pretty close to the data structure we're after. At a high level, all that's left is transforming the order data for each item into the quantity and cost, grouped by month.
const result = _.mapValues(id_map, fp.flow(
// Arrange the item's orders into groups by month
fp.groupBy(month)
// We're done with the order objects, so fp.get('[0]') filters them
// out, and the second function pairs the item's cost and quantity
, fp.mapValues(fp.flow(
fp.map(fp.flow(fp.get('[0]'), i=> [i.itemCost, i.itemQuantity]))
// Sum up the cost (left) and quantity (right) for the item for the month
, fp.reduce(add_pair, [0, 0])))
// These last couple lines just transform the resulting data to look
// closer to the desired structure.
, _.toPairs
, fp.map(([month, [cost, count]])=> ({month, cost, count}))
));
And the helpers month and add_pair referenced above:
function month([item, order]){
const date = new Date(order.date)
, month = date.getMonth() + 1
, year = date.getFullYear().toString().slice(-2);
return `${month}/${year}`;
}
function add_pair(p1, p2){
return [p1[0] + p2[0], p1[1] + p2[1]];
}
Just out of curiosity (or sadism), let's see what this whole thing would look like chained together as a single pipeline:
const get_order_data = fp.flow(
fp.flatMap(o=> _.map(o.items, i=> [i, o]))
, fp.groupBy(fp.get('[0].itemId'))
, fp.mapValues(fp.flow(
fp.groupBy(month)
, fp.mapValues(fp.flow(
fp.map(fp.flow(fp.get('[0]'), i=> [i.itemCost, i.itemQuantity]))
, fp.reduce(add_pair, [0, 0])))
, _.toPairs
, fp.map(([month, [cost, count]])=> ({month, cost, count})))
));
const result = get_order_data(orders);
You'll notice this composed version has a lot more fp (as opposed to _). If you're curious why it's easier this way, I encourage you to read the lodash FP guide.
jsfiddle with everything.
Finally, if you'd like to transform the result from the code above exactly into the output format you mentioned in your post, here's what I recommend:
const formatted = _.keys(result).map(k=> ({itemId: k, periods: result[k]}));
I've been trying to do this for an hour now and I can't figure it out.
I have this:
data = [
{
"_id": {
"cid": "gbrzjauzju",
"source": "iwowrzlocc"
},
"revenue": 0,
"leads": 484,
"count": 25400
},
{
"_id": {
"cid": "lewyhgnnhz",
"source": "iwowrzlocc"
},
"revenue": 0,
"leads": 166,
"count": 9821
},
]
I am passing in filters as variable filters with ['cid', 'source'] so I can access it as filters[0] filters[1]
What I am trying to do this is:
arr = {}
for item in data
arrdata =
revenue: item.revenue
leads: item.leads
clicks: item.count
arr['results'][item._id.filters[0]][item._id.filters[1]] = arrdata
I want to set the value of cid and source as the key names as the key name so it would be like this:
results =
iwowrzlocc =
lewyhgnnhz =
revenue: 0
leads: 166
clicks: 9821
gbrzjauzju =
revenue: 0
leads: 484
clicks: 25400
How exactly would I accomplish this? Thank you in advance!
Have you tried to not use the "dot syntax" in item._id.filters[0]?
If I were you, I'd try to split the last statement:
var arr = {};
var item_results = {};
var item_cid = {};
for (i in data) {
var item = data[i];
var array_data = {
revenue: item.revenue,
leads: item.leads,
count: item.count
};
item_cid[item._id[filters[1]]] = array_data;
item_results[item._id[filters[0]]] = item_cid;
};
arr['results'] = item_results;
Just to make things more readable and easier to identify possible problems in your code. I hope I could help you in some way! :)
I have the below dictionary in which I want to get the length all notifications (4).
[
{
"name": "3 Bedroom Fixer Upper",
"city": "Santa Rosa",
"id": 1,
"state": "CA",
"date_added": "2/3/14",
"completion_status": "20",
"notifications": [
"Quarterly Checklist",
"Pre-Summer",
"Annual Checklist"
],
"sq_ft": 2200,
"year_built": 1994,
"bedrooms": 3,
"bathrooms": 2.5,
"lot_size": 2.5
},
{
"name": "Ski Cabin",
"city": "Tahoe",
"id": 2,
"state": "CA",
"date_added": "3/3/14",
"completion_status": "45",
"notifications": [
"Quarterly Checklist"
],
"sq_ft": 1950,
"year_built": 1984,
"bedrooms": 3.5,
"bathrooms": 2,
"lot_size": 3
}
];
I am able to get the length of a single objects notifications (example all_properties[0].notifications.length = 3) but all_properties.notifications.length does not return anything. What would the syntax be to return the length of all notifications? (4)
http://jsfiddle.net/dakra/U3pVM/
Sorry if I used the incorrect lingo regarding JSON dictionaries (I am new to them.)
You can use some functional programming with Array.prototype.reduce:
function findSum(arr) {
return arr.reduce(function (a, b) {
return a + b.notifications.length;
}, 0);
}
and call it like findSum(all_properties).
For multiple arrays in array called arrays:
var totalSum = 0;
for (var i = 0; i < arrays.length; i += 1) {
totalSum = findSum(arrays[i]);
}
you can use the Array's reduce function
var data = [...your data array...]
var num = data.reduce(function(a,b){
return a+b.notifications.length;
},0);
console.log("Number of notifications:",num);