Javascript Array as More Readable Array - javascript

I have a data set like following table.
+------+---------+----+----+----+----+-------+----------+
| Year | Subject | A | B | C | F | Total | PassRate |
+------+---------+----+----+----+----+-------+----------+
| 2015 | Maths | 12 | 20 | 10 | 5 | 47 | 80 |
| 2015 | Sinhala | 18 | 14 | 5 | 10 | 47 | 75 |
| 2016 | Maths | 25 | 15 | 4 | 8 | 52 | 25 |
| 2016 | Sinhala | 20 | 12 | 2 | 18 | 52 | 60 |
+------+---------+----+----+----+----+-------+----------+
I want to store those data in JavaScript array. So I have following code.
var firstArray = [];
firstArray.push(['Year', 'Subject', 'A', 'B', 'C', 'F', 'Total', 'PassRate']); // headers
firstArray.push([2015, 'Maths', 12, 20, 10, 5, 47, 80]); // 1st row
firstArray.push([2015, 'Sinhala', 18, 14, 5, 10, 47, 75]) // 2nd row
console.log(firstArray);
If I need to read how many "B",s in Maths for 2015, I need to run firstArray[1][3].
That is not readable. I mean it is hard to find what it means firstArray[1][3].
So is there way to build my array more readable way like firstArray[2015]['maths'] if I want to read how many "B",s in Maths for 2015

Sounds like you want an object indexed by year, containing objects indexed by subject:
const years = {
'2015': {
Maths: {
A: 12, B: 20, C: 10, F: 5, Total: 47, PassRate: 80
},
Sinhala: {
A: 18, B: 14, C: 5, F: 10, Total: 47, PassRate: 75
},
},
'2016': {
// ...
}
}
console.log(years['2015'].Maths);

Your purpose is correct, readability of code is very important.
It's not easy and there is no right path to follow, but I try to give you some hints on how to change your code.
First point: naming.
This is very hard, and often even experienced developer need to rename variables as they can't get the proper name at the first time.
Your variable is firstArray and this of course have low meaning and you can just say it is an array and it is the first...
Nothing about what the array is containing.
A better name could be studentsScoreByYear.
The improvement of this name is that it try to address the meaning of the content.
Then the index ad magic numbers.
Of course you need numbers to get a field from an array, and if you just use the proper integer in the code is very complicated to remember what this field is.
A way is to avoid array and use hash map, in javascript plain objects.
So you can give to each field a name.
If you can't abbandon the array for whatever reason, you can improve here too, just use constants to save the proper indexes:
var MATHS = 1;
var SCORE_OF_B = 3;
var studentsScoreByYear= [
['Year', 'Subject', 'A', 'B', 'C', 'F', 'Total', 'PassRate'],
[2015, 'Maths', 12, 20, 10, 5, 47, 80],
[2015, 'Sinhala', 18, 14, 5, 10, 47, 75]
];
console.log(studentsScoreByYear[MATHS][SCORE_OF_B]);
There are other ways of course, this is just a possibility.

A list of objects is what you want to represent your data in a readable format.
As for selecting data:
You can filter an array by using the filter method on the array.
You can change the contents or isolate a parameter with the map method on the array.
You can perform arithmetic operations with the reduce method on the array.
var data = [
{ year: 2015, subject: 'Maths', a: 12, b: 20, c: 10, f: 5, total: 47, passrate: 80 },
{ year: 2015, subject: 'Sinhala', a: 18, b: 14, c: 5, f: 10, total: 47, passrate: 75 },
{ year: 2016, subject: 'Maths', a: 25, b: 15, c: 4, f: 8, total: 52, passrate: 25 },
{ year: 2016, subject: 'Sinhala', a: 20, b: 12, c: 2, f: 18, total: 52, passrate: 60 },
];
console.log("Present All:");
console.log(data
.map(function (row) {
return [row.year, row.subject, row.a, row.b, row.c, row.f, row.total, row.passrate].join(", ");
})
.join("\n"));
console.log("Count `B` in `Maths` in 2015");
console.log(data
.filter(function (row) { return row.year === 2015 && row.subject === "Maths"; })
.map(function (row) { return row.b; })
.reduce(function (accumulator, currentValue) { return accumulator + currentValue; }, 0));
Sidenote
The eagle-eyed out there has probably already spotted that the map and filter calls are redundant in the "Present All" example as i could simply have put:
data.reduce(function (accumulator, row) {
return accumulator + (row.year === 2015 && row.subject === "Maths" ? row.b : 0);
}, 0);
While the above code is "smarter", i think the more verbose answer using map and filter will be of more use to anyone trying to learn JavaScript array handling and JavaScript objects.

Related

How to display data within nested object array in Angular Ionic? [duplicate]

This question already has answers here:
Angular2 nested *ngFor
(2 answers)
Closed 1 year ago.
In my Ionic Angular app, I'm trying to display the below array that contains several workout exercises.
Each exercise consits of several sets:
exercises = [
{
name: 'Leg Extension Machine',
sets: [
{ setNo: 1, reps: 8, weight: 40 },
{ setNo: 2, reps: 10, weight: 36 },
{ setNo: 3, reps: 15, weight: 33 },
],
},
{
name: 'Leg Press Machine',
sets: [
{ setNo: 1, reps: 8, weight: 80 },
{ setNo: 2, reps: 10, weight: 72 },
{ setNo: 3, reps: 15, weight: 65 },
],
},
{
name: 'Rear Leg Elevated Split Squat (Bench)',
sets: [
{ setNo: 1, reps: 8, weight: 8 },
{ setNo: 2, reps: 10, weight: 8 },
{ setNo: 3, reps: 15, weight: 8 },
],
},
];
I'm able to use ngFor to display the exercise names like so:
<ion-card *ngFor="let exercise of exercises">
<ion-card-header>
<ion-card-title>
{{exercise.name}}
</ion-card-title>
</ion-card-header>
</ion-card>
It displays like so:
Now, I am trying to display each set under each Exercise Name.
I've managed to log data related to each set to the console using the below nested loops, but I don't know how to get this data to the front end:
ngOnInit() {
for (let i = 0; i < this.exercises.length; i++) {
this.exercises[i].sets;
console.log('---', this.exercises[i].name);
for (let x = 0; x < this.exercises[i].sets.length; x++) {
console.log('---', this.exercises[i].sets[x].weight);
}
}
}
This should work:
<ion-card *ngFor="let exercise of exercises">
<ion-card-header>
<ion-card-title>
{{exercise.name}}
</ion-card-title>
</ion-card-header>
<ion-card-content>
<div *ngFor="let set of exercise.sets">
<ul>
<li><strong>Nr.</strong> {{set.setNo}}</li>
<li><strong>Reps</strong> {{set.reps}}</li>
<li><strong>Weight</strong> {{set.weight}}</li>
</ul>
</div>
</ion-card-content>
</ion-card>
You already have the current object of your exercise assigned to exercise so you can then retreive the sets from that variable and iterate over these sets.
In the template, you need to use 3 loops and for displaying the individual set by keys and value you need to leverage the keyvalue pipe.
<div *ngFor="let exercise of exercises">
<h2>{{ exercise.name }}</h2>
<div *ngFor="let set of exercise.sets">
<span *ngFor="let prop of set | keyvalue">
{{ prop.key }}: {{ prop.value }}
</span>
</div>
</div>
Here's a stackblitz

sort and filter an object array with lodash (or vanilla javascript)

I have this object I want to sort and filter by retaining only the 2 highest values by object.
obj={ A :[{
asset: 9,
biodiversity: 4,
infrastructure: 15,
deflation: 11,
energy: 9
}],
B:[{
asset: 12,
biodiversity: 10,
infrastructure: 9,
deflation: 7,
energy: 15
}],
C:[{
asset: 2,
biodiversity: 12,
infrastructure: 6,
deflation: 6,
energy: 8
}]}
I would like to sort the objects by their values and filter out the 2 highest e.g:
{A :[{
infrastructure: 15,
deflation: 11
}],
B:[{
energy: 15,
asset: 12
}],
C:[{
biodiversity: 12,
energy: 8
}]}
I have tried this for sorting:
Object.keys(obj).forEach((a) => _.sortBy(obj[a][0])))
But that is wrong obviously.
I am using lodash but will accept vanilla javascript solution as well.
You could get the entries of the inner objects and sort by value descending, get the top two key/value pairs and build a new object from it.
const
data = { A: [{ asset: 9, biodiversity: 4, infrastructure: 15, deflation: 11, energy: 9 }], B: [{ asset: 12, biodiversity: 10, infrastructure: 9, deflation: 7, nergy: 15 }], C: [{ asset: 2, biodiversity: 12, infrastructure: 6, deflation: 6, energy: 8 }]},
result = Object.fromEntries(Object
.entries(data)
.map(([k, a]) => [k, a.map(o => Object.fromEntries(Object
.entries(o)
.sort((a, b) => b[1] - a[1])
.slice(0, 2)
))])
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
(Edit/Note: This is based on the code you originally posted. I'm glad to see you updated your question and got rid of the wrapping array.)
Here's a relatively functional approach.
The secondLargestValue function finds the threshold value within each object.
The copyWithoutValsBelowThreshold function gives us a modified copy of the object.
We loop though the entries in the top-level object and apply these two functions.
See comments in the code for further clarification.
let json = getArray(); // Identifies the original array
// Defines `secondLargestVal` function
const secondLargestVal = someObj =>
// Gets second item of array after sorting numerically in descending order
Object.values(someObj).sort((a, b) => b - a)[1];
// Defines `copyWithoutValsBelowThreshold` function
const copyWithoutValsBelowThreshold = ( (someObj, threshold) => {
// This function doesn't mutate the original value
clone = Object.assign({}, someObj); // Copies obj
for(let prop in clone){
// Loops through properties, deleting non-qualifying ones
if(clone[prop] < threshold){
delete clone[prop];
}
}
return clone;
});
// Defines our main function
const mutateJson = () => {
let entries = Object.entries(json[0]);
entries = entries.map(entry => {
// `entry[0]` is the property name (eg: 'A') -- we don't actually use this
// `entry[1]` is the value (in this case, an array containing a single object)
let obj = entry[1][0]; // Identifies the actual object
const threshold = secondLargestVal(obj); // Identifies minimum value
// Updates the entry, deleting properties whose values are too low
entry[1][0] = copyWithoutValsBelowThreshold(obj, threshold);
return entry;
});
json[0] = Object.fromEntries(entries); // Replaces the top-level object
}
// Calls the main function
mutateJson();
console.log(json);
// Provides the original array
function getArray(){
return [{ A :[{
asset: 9,
biodiversity: 4,
infrastructure: 15,
deflation: 11,
energy: 9
}],
B:[{
asset: 12,
biodiversity: 10,
infrastructure: 9,
deflation: 7,
energy: 15
}],
C:[{
asset: 2,
biodiversity: 12,
infrastructure: 6,
deflation: 6,
energy: 8
}]}]
}

Sorting into groups of 40

I need a program that can take input from a user with data quantities and length (feet and inches or just inches) and group the items into groups of 40.
I originally started trying to accomplish this in Excel, but I'm not sure it can be done.
var cutList = [
{ qty: 36, feet: 28, inch: 3 },
{ qty: 6, feet: 27, inch: 8 },
{ qty: 12, feet: 27, inch: 3 },
{ qty: 6, feet: 25, inch: 8 },
{ qty: 16, feet: 25, inch: 3 },
{ qty: 22, feet: 22, inch: 8 },
{ qty: 12, feet: 12, inch: 3 },
];
Group 1 would have all 36 from the first item plus 4 from the second.
Group 2 would have the remaining 2 items from the second line plus all 12 from the 3rd, and down the line until 40 is hit again. It's been a while since I've done any programming, but I was hoping I could do something like this in JavaScript and embed it in an HTML file for people to use. Ideally they'd be able to copy and paste the data from an Excel spreadsheet into this.
There was an answer posted on Friday that I've been playing with and it seems to work. I don't know if it was deleted or edited but I don't see it now. Here is the code:
var cutList = [ { qty: 36, feet: 28, inch: 3 },
{ qty: 6, feet: 27, inch: 8 },
{ qty: 12, feet: 27, inch: 3 },
{ qty: 6, feet: 25, inch: 8 },
{ qty: 16, feet: 25, inch: 3 },
{ qty: 22, feet: 22, inch: 8 },
{ qty: 12, feet: 12, inch: 3 }],
limit = 40,
used = 0,
result = cutList.reduce((r, { qty, feet, inch }, i) => {
var rest, min;
while (qty) {
if (used === 0) r.push([]);
min = Math.min(limit - used, qty)
r[r.length - 1].push({ qty: min, feet, inch, limit });
qty -= min;
used += min;
if (used === limit) used = 0;
}
return r;
}, []);
console.log(result);
So to whoever posted this on Friday, Thank You.
My interpretation of the requirement follows.
// input:
var cutList = [
{ qty: 36, feet: 28, inch: 3 },
{ qty: 6, feet: 27, inch: 8 },
{ qty: 12, feet: 27, inch: 3 },
{ qty: 6, feet: 25, inch: 8 },
{ qty: 16, feet: 25, inch: 3 },
{ qty: 22, feet: 22, inch: 8 },
{ qty: 12, feet: 12, inch: 3 },
]
// proposed output:
let manifest = [
{batchName:A, batch: [{feet: 12, inch: 3}, ... to 40]},
{batchName:B, batch: [{}, {}, {}, ... to 40]},
{batchName:C, batch: [{}, {}, {}, ... to 40]}
]
If the above datagram accurately reflects what you're looking for then the pseudocode can be completed, as both precursor to writing test cases and the algorithm.
// pseudo:
.get line {cutListItem}, sequentially
.set {runningTotal}
.write new {batchName} to {manifest}
do while
.test {cutListItemQty} !== 0
.enumerate decremeent (--) {cutListItemQty} from {cutListItem}
while {runnintTotal} + {qty} in {cutListItem} <= {batchSizeLimit}
do .write to {manifest}
do until either condition:
{cutListItemQty} from {custListItem} === 0
.read {cutList} .get next {cutListItem}
{runningTotal} === {batchSizeLimit}
.init {runningTotal}
.loopback new {batchName}
// considerations
With the sequential method, A chief benefit is being able to stop at any time to then add or change entries which have not yet been processed. Generally, sequential processing offers better overall workflow integration, too, being able to fit asynchronously into a larger process involving the flow of data from creation through various mutations of use. Potential loss is minimized to what remains of the generated list and its progenitor cutlist entries.
At the level of final processing {qty} would be aggregated into batch, and dropped from the datagram, but you might want a reference to the ids of the originating cutlist entries contained in each batch. That speaks to your cutlist, outlining the potential need for identifiers on each entry. We're simply mapping rows and columns here... So, to extend the datagram layout, I'm wondering how you'd view, say, another array entry for each batch, something like: {originsList: [id#3, id#1, id#...], ...}?
I've learned a lot here, so I feel you're in the right place to discover how to put together a working model of your data for a site. And given the programming language internal data type match to your cutlist, from a technical perspective, you appear to be on the right track.
Edit:
The following doesn't hold a candle to what is an artfully concise and complete answer provided by Nina Scholz. Reading her elegant submission again, and comparing it to the original question, I learned my understanding of the problem was limited, as she deeply dives into a min/max solution.
She sets the bar high, and it is a pleasure to read her work. I will continue to learn from that depth of insight as my practical knowledge of Javascript matures in terms of control flow and data structures.
In any case, my code follows:
let cutList = [
{ qty: 36, feet: 28, inch: 3 },
{ qty: 6, feet: 27, inch: 8 },
{ qty: 12, feet: 27, inch: 3 },
{ qty: 6, feet: 25, inch: 8 },
{ qty: 16, feet: 25, inch: 3 },
{ qty: 22, feet: 22, inch: 8 },
{ qty: 12, feet: 12, inch: 3 },
]
// add id field to cutlist retaining this in the array for check against final manifest entries
cutList.filter((item) => item.qty !== 0).forEach((item, idx) => {item.id = idx + 1})
// could expand objects in number of qty to this or new array, keeping original array entries
// Or, decrement the item.qty as each new replica is made with item.subId as an identifier
let qtyTotal = 0
let pooled = []
// console.log(cutList)
// expand cutlist entries by qty to new array, pooled, adding subId to each entry
let arrays = cutList.map((item, idx) => {
// forEach works here, probably better, too - lots of bloatiness going on in either case, trails of replicated lists, not a good production solution
// aggregate entry qty for a total as each entry(item) is parsed
qtyTotal += item.qty
for (let i = 0; i < item.qty; i++) {
let o = {}
o = Object.assign(o, item)
o.subId = i
o.id - i
// console.log(o) // console.dir(o) would be better
pooled.push(o)
}
return pooled
})
// the list manifest will be chunked from
// console.dir(pooled)
// console.log('arrays.length: ', arrays.length)
// make sure the sum of qty matches the number
let valid
if (qtyTotal === pooled.length) {
valid = true
} else {
valid = false
}
console.log(`qty total: ${qtyTotal} pooled total: ${pooled.length}
The manifest data is ${valid ? 'ready.' : 'not ready for use.' }`)
// no exit, program continues
console.log("Producing manifest...")
// chunk pool by batchsize
let chunkedList = []
function produceManifest(start, batchsize) {
let i = start
let k = 0
do {
let qty, feet, inch, id, subId
let x = []
let j = 0
do {
// assign objects from the pool into batchsized arrays
// console.dir(pooled[i]);
// if (typeof pooled[i] === undefined) break;
try {
({qty, feet, inch, id, subId} = pooled[i])
i ++
} catch(e) {
// console.error(e)
// break
}
x[j] = {qty, feet, inch, id, subId}
// console.log(x[j])
j ++
if (i >= pooled.length) break;
// console.log("getting j: " + j)
} while (j <= batchsize - 1) // index in reference array starts at 0
chunkedList[k] = [...x]
// chunkedList.push[x]
// console.dir(chunkedList)
k ++
} while (i <= pooled.length - 1)
// console.dir(chunkedList)
return chunkedList
}
// manifest is an array containing batches of the number requested
// do the job
let start = 0
let batchsize = 40
let printable = produceManifest(start, batchsize)
console.dir(printable)
console.log('debug')

How can I get the largest number value along with the username from this array?

Im trying to get the user & value with the highest number from this array but have had no luck in my searches. I'm starting to wonder if my array is poorly written.
{
"radtech2": 1,
"conorlarkin4": 25,
"jdon2001": 15,
"nobel_veo": 101,
"frapoden": 1,
"duckyboy17": 31,
"faeded": 30,
"jimbob20001": 17,
"leb0wski": 15,
"3cavalry": 2,
"hardoak22": 25,
"deep_slide": 10000,
"sillywil": 7
}
const users = {
"radtech2": 1,
"conorlarkin4": 25,
"jdon2001": 15,
"nobel_veo": 101,
"frapoden": 1,
"duckyboy17": 31,
"faeded": 30,
"jimbob20001": 17,
"leb0wski": 15,
"3cavalry": 2,
"hardoak22": 25,
"deep_slide": 10000,
"sillywil": 7
};
const highestUser = users => Object.keys(users).reduce(
(highest, current) => highest.val > users[current] ? highest : { user: current, val: users[current] },
{ user: undefined, val: -Infinity }
).user;
console.log(highestUser(users));
Use keys() and entries() methods to search your JSON object. Save largest value into e.g. const largest and then find out which key belongs to this value.
Let me try to squeeze it into a one-liner approach using Object.keys() and Array.reduce().
const users = {
"radtech2": 1,
"conorlarkin4": 25,
"jdon2001": 15,
"nobel_veo": 101,
"frapoden": 1,
"duckyboy17": 31,
"faeded": 30,
"jimbob20001": 17,
"leb0wski": 15,
"3cavalry": 2,
"hardoak22": 25,
"deep_slide": 10000,
"sillywil": 7
};
const res = Object.keys(users).reduce((a, b) => users[a] > users[b] ? a : b);
console.log(res);
How the above code works is that I get the array of keys from the users object, and I use reduce to get the highest possible value and return the corresponding property from the array obtained from Object.keys().
What you show in your question is an Object, not an Array; however, it does need to be turned into an array in order to work with it.
You can do that with Object.entries(), which will return an array of all the key/value pairs in the object.
Then you can use Array.reduce() to extract the one with the largest value.
const data = {
"radtech2": 1,
"conorlarkin4": 25,
"jdon2001": 15,
"nobel_veo": 101,
"frapoden": 1,
"duckyboy17": 31,
"faeded": 30,
"jimbob20001": 17,
"leb0wski": 15,
"3cavalry": 2,
"hardoak22": 25,
"deep_slide": 10000,
"sillywil": 7
}
let winner = Object.entries(data).reduce((a, b) => (a[1] > b[1]) ? a : b)
console.log(winner)

How to combine a single array of objects based on multiple properties

Utilizing javascript, I have an array of objects that looks like this:
id | date | store | type | txn | failed
------------------------------------------------
1 | 10-02-18 | 32 | short | 4 | false
2 | 10-02-18 | 32 | long | null | true
3 | 10-03-18 | 32 | short | 7 | false
4 | 10-03-18 | 32 | long | 10 | false
I want to be able to transform this array into something that looks like this:
[
{
date: 10-02-18,
store: 32,
short: {
txn: 4,
failed: false,
},
long: {
txn: null,
failed: true,
},
},
{
date: 10-03-18,
store: 32,
short: {
txn: 7,
failed: false,
},
long: {
txn: 10,
failed: true,
},
}
]
You can see I would like to combine the "type", "txn" and "failed" properties with row that have the same "date" and "storeId", adding the "type" as a property and "txn" and "failed" as child properties of the "type". The "id" property could be ignored in the new array.
I use lodash quite a bit but that isn't a requirement for this. I'm just struggling to wrap my head around how to do this transformation.
You basically just need to create an object with keys that represent something unique to the groups you want. You could make keys that are concatenations of store_date for example and the object will only have one of those and it will be quick to get if you have the store and date. You can build an object like this with reduce. Once you have the object, you can simply call Object.values to get the array of values. For example:
let arr = [
{id:1, date: "10-02-18",store: 32, type: "short", tx: 4, failed: false},
{id:2, date: "10-02-18",store: 32, type: "long", tx: null, failed: true},
{id:3, date: "10-03-18",store: 32, type: "short", tx: 7, failed: false},
{id:4, date: "10-03-18",store: 32, type: "long ", tx: 10, failed: false}
]
let obj = arr.reduce((obj, {id, date, store, type, tx, failed}) => {
// make a unique key
key = `${date}_${store}`
// if we haven't seen this key add it with the outline of the object
if(!obj[key]) obj[key] = {date, store}
// add specific type to either the found obj or the new one
obj[key][type] = {tx, failed}
return obj
}, {})
// obj is an object keyed to date_store
// get just the values
console.log(Object.values(obj))

Categories