Reputation: 3392
I have an array of arrays in javascript. And want to perform some kind of calculations.
My array looks like on the picture below:
Where: - First value is a code. - Second value is a size. - Third value of each array is a count.
What I'm trying to do, is to find all groups, where all counts are 0.
For example:
What I have tried:
var a = [];
$.each(arr[21].splits, function(idx, val) {
console.log(val);
var current_code = arr[21].splits[idx][0];
a[current_code] = [];
a[current_code].push(arr[21].splits[idx]);
});
But can't find the right solution.. Thanks!
The data set: {"code":"sjm","splits":[["FOG","L","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["FOG","S","7"],["FOG","M","0"],["FOG","XXL","6"],["BLK","LT","30"],["BLK","XLT","23"],["BLK","XXXLT","0"],["BLK","L","102"],["BLK","XL","302"],["BLK","XXXL","64"],["BLK","S","25"],["BLK","XXLT","0"],["BLK","M","485"],["BLK","XXL","159"],["BGE","L","106"],["BGE","XL","41"],["BGE","XXXL","15"],["BGE","S","4"],["BGE","M","39"],["BGE","XXL","0"],["RED","L","36"],["RED","XL","41"],["RED","XXXL","8"],["RED","S","5"],["RED","M","19"],["RED","XXL","2"],["NVY","L","0"],["NVY","XL","0"],["NVY","XXXL","0"],["NVY","S","28"],["NVY","M","0"],["NVY","XXL","0"]]}
P.S On the screen above - no color codes, where all the values in group are 0. So, nothing to output.
Upvotes: 2
Views: 194
Reputation: 12796
I am going to guess (since you talk about grouping), that you would first reduce
and then filter
You could reduce to get your arrays grouped by the first index, you could go at it like
function groupBy( arr, t ) {
return arr.reduce( (agg, c) => {
const key = t(c);
if (!agg[key]) {
agg[key] = [];
}
agg[key].push( c );
return agg;
}, {} );
}
which you could then use as
const result = groupBy( arr[21], item => item[0] );
given you back an object like
{ "NVY": [["NVY", "S", "28"], ...] }
And then you can find the ones that have a total of 0
"count"
function find( obj, predicate ) {
return Object.keys( obj ).filter( key => predicate( obj[key] ) );
}
which you could then use like
const groups = find( result, items => !items.some( i => i[2] !== "0" ) );
Giving you a result of (in case NVY didn't have any items that where not "0")
["NVY", "..."]
Which you could then combine with your code
property
So, if you combine all that, you would get something like
const set = {"code":"sjm","splits":[["FOG","L","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["FOG","S","7"],["FOG","M","0"],["FOG","XXL","6"],["BLK","LT","30"],["BLK","XLT","23"],["BLK","XXXLT","0"],["BLK","L","102"],["BLK","XL","302"],["BLK","XXXL","64"],["BLK","S","25"],["BLK","XXLT","0"],["BLK","M","485"],["BLK","XXL","159"],["BGE","L","106"],["BGE","XL","41"],["BGE","XXXL","15"],["BGE","S","4"],["BGE","M","39"],["BGE","XXL","0"],["RED","L","36"],["RED","XL","41"],["RED","XXXL","8"],["RED","S","5"],["RED","M","19"],["RED","XXL","2"],["NVY","L","0"],["NVY","XL","0"],["NVY","XXXL","0"],["NVY","S","28"],["NVY","M","0"],["NVY","XXL","0"]]};
function groupBy( arr, t ) {
return arr.reduce( (agg, c) => {
const key = t(c);
if (!agg[key]) {
agg[key] = [];
}
agg[key].push( c );
return agg;
}, {} );
}
function find( obj, predicate ) {
return Object.keys( obj ).filter( key => predicate( obj[key] ) );
}
const result = groupBy( set.splits, item => item[0] );
const groups = find( result, items => !items.some( i => i[2] !== "0" ) );
console.log( groups );
// and combined
console.log( groups.map( g => `${set.code}-${g}` ) );
// for verification
console.log( result );
Upvotes: 2
Reputation: 8660
You can create an Inventory Constructor that allows for you to parse your data however you'd like and in as many ways as you'd like.
code:
function Inventory( from ) {
return {
data: from,
getBy: function( type ) {
if ( ![ "code", "size", "quantity" ].includes( type.toLowerCase() ) ) throw new Error( "Incorrect Inventory::getBy Type" );
return function( value ) {
return Inventory(
from.filter( ( [ code, size, quantity ] ) =>
new Function( "code, size, quantity", `return ${type} === '${value.toUpperCase()}'` )( code, size, quantity )
)
);
}
}
}
}
let data={splits:[["FOG","L","0"],["FOG","M","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["NVY","M","0"],["NVY","L","0"],["NVY","S","0"]]};
function Inventory(from) {
return {
data: from,
getBy: function(type) {
if (!["code", "size", "quantity"].includes(type.toLowerCase())) throw new Error("Incorrect Inventory::getBy Type");
return function(value) {
return Inventory(from.filter(([code, size, quantity]) => new Function("code, size, quantity", `return ${type} === '${value.toUpperCase()}'`)(code, size, quantity)));
}
}
}
}
let Inv = Inventory(data.splits),
noQuantity = Inv.getBy("quantity")("0").data;
console.log("Items with 0 Quantity: ");
console.log(noQuantity);
On the surface this probably seems like overkill, and in fact it may be. I know there are other fine answers, but I really felt they were lacking in optimal use.
Why?
This means that though they may answer your question, they're mostly not adaptable, which is important to all code as needs mutate, evolve, and edge cases need to be dealt with.
My answer provides all of the above, with very little code, and the explanation below ( if you choose to read it ) should give you a thorough explanation of how it works.
We'll start from the beginning and create a simple Constructor.
Let's call it Inventory
because that seems to be what your data references.
function Inventory(from) {
return {
}
}
The first thing we need to do is to store the data that we receive in the function. This can be done like so:
function Inventory(from) {
return {
data: from
}
}
This means that when we call the constructor with our Inventory data:
let Inv = Inventory(data.splits);
Within Inv
we now have:
{
data: [ Inventory Data ]
}
let data={splits:[["FOG","L","0"],["FOG","M","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["NVY","M","0"],["NVY","L","0"],["NVY","S","0"]]};
function Inventory(from) {
return {
data: from
}
}
let Inv = Inventory(data.splits);
console.log(Inv);
To manipulate this data in a way where we can dig into it and get our requested results, we can actually use a filter
method on our array.
As an example we can adjust our constructor to look for specific quantities like this:
function Inventory(from) {
return {
data: from,
getByQuantity: function(quantity) {
return from.filter(([, , q]) => quantity === q);
}
}
}
let data={splits:[["FOG","L","0"],["FOG","M","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["NVY","M","0"],["NVY","L","0"],["NVY","S","0"]]};
function Inventory(from) {
return {
data: from,
getByQuantity: function(quantity) {
return from.filter(([, , q]) => quantity === q);
}
}
}
let Inv = Inventory(data.splits),
noQty = Inv.getByQuantity("0");
console.log( noQty );
This is all well and good.
But what if we need to get all of the data with the code
NVY?
Or what if we need to get all of the data with the quantity
1 within the code FOG ?
Our current pattern requires a lot of boilerplate code to generate anything more than our specified quantity result!
How do we mitigate our code and bolster functionality?
To be more useful we can utilize a Templating Pattern combined with our filter
to provide us with the ability to get any result from our data and then continue to drill down until we've gotten exactly what we need.
We can do this by adjusting our constructor. I'll walk through the steps to make it easier to understand.
Step 1:
getBy
to make it variable in use. type
parameter, that is checked to make sure it's validcode:
function Inventory(from) {
return {
data: from,
getBy: function(type) {
if (!["code", "size", "quantity"].includes(type.toLowerCase())) throw new Error("Incorrect Inventory::getBy Type");
}
}
}
Step 2:
value
value
is what we'll check against our type
while filtering
code:
function Inventory(from) {
return {
data: from,
getBy: function(type) {
if (!["code", "size", "quantity"].includes(type.toLowerCase())) throw new Error("Incorrect Inventory::getBy Type");
return function(value) {
}
}
}
Step 3:
Template
our type
and value
into a conditional, and return a boolean (true/false) of whether or not it matches.filter
we can search for anythingcode:
function Inventory(from) {
return {
data: from,
getBy: function(type) {
if (!["code", "size", "quantity"].includes(type.toLowerCase())) throw new Error("Incorrect Inventory::getBy Type");
return function(value) {
return from.filter(([code, size, quantity]) =>
new Function("code, size, quantity", `return ${type} === '${value.toUpperCase()}'`)(code, size, quantity)
);
}
}
}
}
Re-usability aside this is enough to provide a one-time search of your data
based on any parameter, be that code, size, or quantity
let data={splits:[["FOG","L","0"],["FOG","M","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["NVY","M","0"],["NVY","L","0"],["NVY","S","0"]]};
function Inventory(from) {
return {
data: from,
getBy: function(type) {
if (!["code", "size", "quantity"].includes(type.toLowerCase())) throw new Error("Incorrect Inventory::getBy Type");
return function(value) {
return from.filter(([code, size, quantity]) =>
new Function("code, size, quantity", `return ${type} === '${value.toUpperCase()}'`)(code, size, quantity)
);
}
}
}
}
let Inv = Inventory(data.splits);
console.log(" FOG Code: ");
console.log( Inv.getBy("code")("FOG") );
console.log(" Size Medium: ");
console.log( Inv.getBy("size")("M") );
In practice as far as your question goes, you do get your result. BUT you'll notice that it's difficult to get all Medium sizes within NVY - though it is possible. It would look something like:
let allMNVY = Inventory(
Inventory(data.splits)
.getBy("code")("NVY")
)
.getBy("size")("M");
This is not ideal.
How can we fix this?
This last step allows us to do multiple searches within our data to continuously drill down until we get the data we want.
This is done through Recursion.
Step 4:
To retain the ability to continue searching, we want to return an Inventory Constructor with the returned data
as the parameter
.
code:
function Inventory(from) {
return {
data: from,
getBy: function(type) {
if (!["code", "size", "quantity"].includes(type.toLowerCase())) throw new Error("Incorrect Inventory::getBy Type");
return function(value) {
return Inventory(from.filter(([code, size, quantity]) => new Function("code, size, quantity", `return ${type} === '${value.toUpperCase()}'`)(code, size, quantity)));
}
}
}
}
This constructor then allows us to continue to dig through our data, in very few lines of code. The caveat is that when we finish our search, we must look at the data
property.
Our previous example of all Medium sizes within code NVY:
let allMNVY = Inventory(
Inventory(data.splits)
.getBy("code")("NVY")
)
.getBy("size")("M");
Our new example of all Medium sizes within code NVY:
let allMNVY = Inventory(data.splits)
.getBy("code")("NVY")
.getBy("size")("M")
.data;
As you can see it's much more sensible because we know exactly what occurs in this example.
data.splits
into our inventoryitems
with code
NVYitems
with size
Mdata
instead of making another search.function Inventory(from) {
return {
data: from,
getBy: function(type) {
if (!["code", "size", "quantity"].includes(type.toLowerCase())) throw new Error("Incorrect Inventory::getBy Type");
return function(value) {
return Inventory(from.filter(([code, size, quantity]) => new Function("code, size, quantity", `return ${type} === '${value.toUpperCase()}'`)(code, size, quantity)));
}
}
}
}
let noQty = Inventory(data.splits).getBy("quantity")("0");
let data={splits:[["FOG","L","0"],["FOG","M","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["NVY","M","0"],["NVY","L","0"],["NVY","S","0"]]};
function Inventory(from) {
return {
data: from,
getBy: function(type) {
if (!["code", "size", "quantity"].includes(type.toLowerCase())) throw new Error("Incorrect Inventory::getBy Type");
return function(value) {
return Inventory(from.filter(([code, size, quantity]) => new Function("code, size, quantity", `return ${type} === '${value.toUpperCase()}'`)(code, size, quantity)));
}
}
}
}
let Inv = Inventory(data.splits),
noQuantity = Inv.getBy("quantity")("0").data,
allFog = Inv.getBy("code")("FOG").data,
allNvyNoQuantity = Inv.getBy("code")("NVY").getBy("quantity")("0").data;
console.log("noQuantity : " + noQuantity);
console.log("allFog : " + allFog);
console.log("allNvyNoQuantity : " + allNvyNoQuantity);
Edit after thinking about this a while, I decided to add demo code for a comparison instead of exact matching
Note Because of the number of examples within this answer, StackOverflow doesn't seem to be rendering the results of the console.log
in the following examples. You may need to open your own console to verify, but they do work!
The last thing you may want to consider instead of explicit matching, is a comparison operation to filter fluid matches.
With our above code if we want to grab the following:
We would need to look twice at the data
. Once for "0"
and again for "1"
.
To adjust this we can provide the ability to make a comparison. This way we can simply say:
We do this by adjusting our constructor very slightly and adding a new method called filterBy
:
filterBy: function( comparison ) {
return Inventory( from.filter( ( [ code, size, quantity ] ) => new Function( "code, size, quantity", `return ${comparison};`)( code, size, quantity ) ) );
}
It is almost exactly the same as our prior Templating Function, except this method will take a string and use it to compare against our data set.
let data={splits:[["FOG","L","0"],["FOG","M","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["NVY","M","0"],["NVY","L","0"],["NVY","S","0"]]};
function Inventory( from ) {
return {
data: from,
getBy: function( type ) {
if ( ![ "code", "size", "quantity" ].includes( type.toLowerCase() ) ) throw new Error( "Incorrect Inventory::getBy Type" );
return function( value ) {
return Inventory( from.filter( ( [ code, size, quantity ] ) => new Function( "code, size, quantity", `return ${type} === '${value.toUpperCase()}'` )( code, size, quantity ) ) );
}
},
filterBy: function( comparison ) {
return Inventory( from.filter( ( [ code, size, quantity ] ) => new Function( "code, size, quantity", `return ${comparison};`)( code, size, quantity ) ) );
}
}
}
let Inv = Inventory(data.splits),
negativeQuantity = Inv.filterBy("Number(quantity) < 0").data;
positiveQuantity = Inv.filterBy("Number(quantity) > 0").data;
console.log("Negative Quantity:");
console.log(negativeQuantity);
console.log("Positive Quantity:");
console.log(positiveQuantity);
Additionally it also has access to any and all of the types
( code, quantity, and size )
Note In our comparison string all of our types
(code, quantity, and size) are strings, and all lowercase. That is why we use Number(quantity)
and not just quantity
in the above example.
A huge bonus of our Templating
Pattern is that we can write any JavaScript comparison and it will work. Going back to our code
NVY and size
M example, we can now write:
let MNVY = Inv.filterBy("code == 'NVY' && size == 'M'").data;
let data={splits:[["FOG","L","0"],["FOG","M","0"],["FOG","XL","-1"],["FOG","XXXL","2"],["NVY","M","0"],["NVY","L","0"],["NVY","S","0"]]};
function Inventory( from ) {
return {
data: from,
getBy: function( type ) {
if ( ![ "code", "size", "quantity" ].includes( type.toLowerCase() ) ) throw new Error( "Incorrect Inventory::getBy Type" );
return function( value ) {
return Inventory( from.filter( ( [ code, size, quantity ] ) => new Function( "code, size, quantity", `return ${type} === '${value.toUpperCase()}'` )( code, size, quantity ) ) );
}
},
filterBy: function( comparison ) {
return Inventory( from.filter( ( [ code, size, quantity ] ) => new Function( "code, size, quantity", `return ${comparison};`)( code, size, quantity ) ) );
}
}
}
let Inv = Inventory(data.splits),
MNVY = Inv.filterBy("code == 'NVY' && size == 'M'").data;
console.log("NVY and M : " + MNVY);
Though a bit lengthy, I hope this helps!
Happy coding!
Upvotes: 2
Reputation: 2063
Here is a reusable function which can take different counts to search for a given array with the structure that you have given:
function findByCount(array, code, countToFind) {
var result = [];
$.each(array, function (index, subArray) {
var count = subArray[2];
var groupCode = code + "-" + subArray[0];
if (count == countToFind && result.indexOf(groupCode) === -1) {
result.push(groupCode);
}
});
return result;
}
console.log(findByCount(data.splits, data.code, 0));
Upvotes: 1