Javascript runtime optimizing array modifications - javascript

I have a problem optimizing the runtime for my code. Ideally, what I'm trying to achieve is that all the operations below is performed in a single loop, so that I don't have to run through the dataset many times as I'm doing now (very large dataset!)
The code is transforming aggData to an array on the following format: [0: 0, 1: 0, 2: 0, 3: 43, 4: 121, 5: 0, ....], where each number represents a year in the interval, if the interval is (1800-2020) 0 will represent the count for 1800, 1 will be 1801 and so on ..
aggData is an array of objects on the following format: {key_as_string: "1900-01-01T00:00:00.000Z", key: -2208988800000, doc_count: 17}. The start-year is the first year with a doc_count higher than 0.
Below I provide a description of what each step does as the code is now:
Here I am changing the format of each object in the list to be : {year: number, count: number}
const formatAggData = aggData
.map((item: AggData) => {
return { year: moment(item.key_as_string).year(), count: item.doc_count };
});
This function creates an array of objects with the from as start year and to as end year, if the year already exists in existingArray it uses the count from there, if not it sets count to 0.
function fillYears(from: number, to: number, existingArray: YearCount[]) {
const existingObject: { [year: string]: number } = { year: null };
existingArray.map((x: YearCount) => (existingObject[x.year] = x.count));
const yearsArray = [];
for (let i = from; i <= to; i++) {
yearsArray.push({
year: i,
count: existingObject[i] || 0,
});
}
return yearsArray;
}
Converts year values to count-values, where the first year in the list will be 0 with the corresponding value, second will be 1 with corresponding value and so on..
const resultList = fillYears(min, max, formatAggData).map(
(item: YearCount) => item.count,
);

I was looking at you code. can't you do it like this? it looks like you don't need to know the year at this moment
function fillYears(from: number, to:number, existingYears: YearCount[]) {
for (let i = from; i <= to; i++) {
yearsArray.push({
year: i,
count: existingYears[i].doc_count || 0,
});
}
}

Related

Object.values() not updating variable

I have the variable G.playerStatsDifference defined as an array of objects:
playerStatsDifference: [{
carpenter: 0,
wood: 0,
gunman: 0,
gunpowder: 0,
merchant: 0,
gold: 0,
fleet: 0,
flagship: 0,
}, {
carpenter: 0,
wood: 0,
gunman: 0,
gunpowder: 0,
merchant: 0,
gold: 0,
fleet: 0,
flagship: 0,
}]
The point of this variable is to calculate the difference between G.playerStats which frequently changes.
My function to calculate the difference is:
const oldPlayerStats = JSON.parse(JSON.stringify(G.playerStats));
statsDifference(G, oldPlayerStats);
for (let p = 0; p < 2; p++) {
for (let s = 0; s < 8; s++) {
Object.values(G.playerStatsDifference[p])[s] = Object.values(G.playerStats[p])[s] - Object.values(oldPlayerStats[p])[s];
}
}
The expected output would be to have playerStatsDifference
When running some tests I did some console logging and it gave me the correct calculations, but the G.playerStatsDiffence would not update.
Here is some of that testing, with the calulations being correct:
console.log("Current wood is " + Object.values(G.playerStats[0])[1]); //Current wood is 5
console.log("Old wood is " + Object.values(oldPlayerStats[0])[1]); //Old wood is 10
console.log(Object.values(G.playerStats[0])[1] - Object.values(oldPlayerStats[0])[1]); //-5
I thought maybe I was doing something wrong with the loops so I tried the following afterwards:
Object.values(G.playerStatsDifference[0])[1] = Object.values(G.playerStats[0])[1] - Object.values(oldPlayerStats[0])[1];
However this did not work either. Having said that, the following does work:
G.playerStatsDifference[0].wood = Object.values(G.playerStats[0])[1] - Object.values(oldPlayerStats[0])[1];
So it seems like I have some issue with the Object.values on G.playerStatsDifference. Any idea on why that is and how I can run that through the loop?
=====
EDIT: As those in the comments have pointed out my question is a bit confusing so I will try to clear it up here..
The G.playerStatsDifference value is supposed to track the difference between the previous value of G.playerStats and the current value of G.playerStats.
To do this I am setting the value of oldPlayerStats to equal G.playerStats and then updating G.playerStats to its new value.
I then need to run through the array of objects and subtract the value of G.playerStats from oldPlayerStats. This will produce the value of G.playerStatsDifference
That is what the loop is for, to go through each object key and do the calculation.
Hope this provides some clarity. Sorry for the poorly worded question.
const diffBetweenObjectValues = (a, b) => {
return Object.entries(a).reduce((result, [aKey, aVal]) => {
result[aKey] = aVal - (b[aKey] ?? 0);
return result;
}, {});
}
const stats = { a: 1, b: 2 };
const updatedStats = { a: 1, b: 1 };
// Initial player stats are { a: 1, b: 2 }
const player = { stats: stats, diff: {} };
// Set the diff, value is { a: 0, b: 1 }
player.diff = diffBetweenObjectValues(player.stats, updatedStats);
// Actually update the stats, value is { a: 1, b: 1 }
player.stats = updatedStats;
Note that if a key is present in b but not a it's ignored. Also note that this only works properly if all the property values are numeric.
You can put the state transition in a function and just run it when you need to update the stats (like every tick of the game loop).
Response to comment
Ok, lets add another helper function
const zip = (a, b) => a.map((x, i) => [x, b[i]]);
const players = [...]; // array of players
const statUpdates = [...]; // array of stat updates
zip(players, statUpdates).forEach(([player, stats]) => {
player.diff = diffBetweenObjectValues(player.stats, stats);
player.stats = stats;
});
Zip combines the array of players and the array of stat updates in to pairs, then iterate over them with forEach, destructure the bits back out, and run the update. You can also just use a for loop, which is faster but harder to read and easier to get wrong (e.g. off-by-one errors). I would stick with the version until/unless your profiler tells you it's too slow.
Update 2
const currentStats = [{ a: 1, b: 2 }, {a: 3, b: 2 }];
const updatedStats = [{ a: 0, b: 1 }, {a: 4, b: 1 }];
const diffedStats = zip(currentStats, updatedStats).map(([current, updated]) => {
return diffBetweenObjectValues(current, updated);
});
// for testing purposes, create an object with some random stats
const randomPlayerStats = () => Object.fromEntries(
['carpenter','wood','gunman','gunpowder','merchant','gold','fleet','flagship']
.map(k=>[k,Math.random()*10|0]));
// array of the last player stats recorded for each player
let lastPlayerStats = [];
// create a new object from the existing object, subtracting each entry
// from the old object from the entry from the new object
// note: uses the ?? operator so that if there is no last object yet,
// the last object value will be treated as being zero
const difference = (playerStats, lastPlayerStats) => {
let r = Object.fromEntries(Object.entries(playerStats).map(([k,v])=>
[k, v-(lastPlayerStats?.[k]??0)]));
lastPlayerStats = playerStats;
return r;
};
// simulate 5 rounds of the game, with 2 players in the game
const playerCount = 2;
const simulatedRounds = 5;
for(let c=0;c<simulatedRounds;c++) {
let playerStats = [...Array(playerCount).keys()].map(i=>randomPlayerStats());
let playerStatsDifference = playerStats.map((s,i)=>
difference(s, lastPlayerStats[i]??{}));
console.log('playerStats:');
console.log(playerStats);
console.log('playerStatsDifference:');
console.log(playerStatsDifference);
}

Find all objects in array that have time difference of 1 minute

How to find all objects where time difference between each consecutive object is less than 1 minute.
const input = [
{ id: 12,
time: '2018-03-01T12:34:00.000Z'},
{ id: 15,
time: '2018-03-02T09:25:20.000Z'},
{ id: 19,
time: '2018-03-04T07:14:20.000Z'},
{ id: 23,
time: '2018-04-01T10:24:00.000Z'},
{ id: 24,
time: '2018-04-01T10:24:40.000Z'},
{ id: 25,
time: '2018-04-01T10:25:10.000Z'}
]
expected output ===> [
{ id: 23,
time: '2018-04-01T10:24:00.000Z' },
{ id: 24,
time: '2018-04-01T10:24:40.000Z' },
{ id: 25,
time: '2018-04-01T10:25:10.000Z' }
]```
Assuming that there's only exactly one run of consecutive items in one minute, you can use the following algorithm:
Ensure your list is sorted, which in this case it seems is already in ascending time order, so I'll skip this step. You should not skip this step is the list isn't guaranteed to be sorted.
We want to find the first two consecutive items. Loop through each element of the array, starting from the second. For each item:
Check the difference in time between the previous item and the current one. If it is less than a minute, the previous and current items are the first consecutive entries. Stop the loop (break).
If we have iterated through the whole array without finding any consecutive entries, return an empty list.
We now want to find the rest of the consecutive items. Loop through each item starting from the item directly after the last known consecutive item. For each item:
If the time difference between the previous and current items is more than a minute, stop the loop (break).
Otherwise, the current item is also consecutive. Count it in, and continue the loop.
Return all known consecutive items.
Here is one such implementation:
// `entries` is of type `{ id: number; time: string; }`
function getConsecutiveEntries(entries) {
// Find the first two consecutive entries
let idx;
for (idx = 1; idx < entries.length; idx++) {
const prevTime = new Date(entries[idx - 1].time).getTime();
const curTime = new Date(entries[idx].time).getTime();
if (curTime - prevTime <= 60000) break;
}
if (idx === entries.length) return [];
const result = [entries[idx - 1], entries[idx]];
// Find the rest of consecutive entries
idx++;
while (idx < entries.length) {
const prevTime = new Date(entries[idx - 1].time).getTime();
const curTime = new Date(entries[idx].time).getTime();
if (curTime - prevTime > 60000) break;
result.push(entries[idx]);
idx++;
}
return result;
}
This implementation can be further optimized by memoizing the the results of Date to number conversions (the new Date(...).getTime()) calls, which happens twice for most entries.

How to iterate over an object to find out 'periods of time' in which the value is larger than 0?

I am building a booking app. I have created an object with times and the number of vacancies for each of these times.
{
1000: 1,
1030: 4,
1100: 4,
1130: 2,
1200: 0,
1230: 1,
1300: 0
//...
}
Times are separated in 30 minute intervals, but services can take longer than 30 minutes (but are all multiples of 30 themselves). E.g.: service1.duration = 90
I now need to build a script that identifies in which periods of time a service can be executed. In the example above, 90/30 = 3, so I would have to find 3 sequential keys in that object that have a value > 0.
There would be two in the example above: [1000, 1030, 1100] and [1030, 1100, 1130].
Ideally, the periods would be returned in an array just as the two I have exemplified.
Problem: I don't know how to iterate over both keys and values. I know Object.keys and Object.values can be used, but not how to combine them.
You can use both Object.keys and Object.values together, as they are both guaranteed to be in ascending order for number-like keys.
const times = {
1000: 1,
1030: 4,
1100: 4,
1130: 2,
1200: 0,
1230: 1,
1300: 0
//...
};
const getPeriods = time => {
const keys = Object.keys(times);
const values = Object.values(times);
const res = [];
for(let i = 0; i <= values.length - time; i++){
let works = true;
for(let j = i; j < i + time && works; j++){
if(values[j] <= 0){
works = false;
}
}
if(works){
res.push(keys.slice(i, i + time));
}
}
return res;
};
console.log(getPeriods(3));

Summing two ordered arrays of accumulative object values over time

I'm trying to figure out how to sum accumulatives values over time of two arrays, seems simple, but here's what complicates it: there might be missing dates from one of them.
When one has a value for a date and the other doesn't, we sum together that value that exists from one array and the last-seen (previous) value for a date of the other array (The example I give demonstrates this better).
Example, given two arrays of objects where in data2 there's a value for a date that data1 doesn't have:
var data1 = [
{date: "30-08-2019", value: 1},
{date: "03-09-2019", value: 2},
{date: "04-09-2019", value: 3}
]
var data2 = [
{date: "30-08-2019", value: 1},
{date: "02-09-2019", value: 2},
{date: "03-09-2019", value: 3},
{date: "04-09-2019", value: 4}
]
I want the result of summing these two together (data1 + data2) to be:
var result = [
{date: "30-08-2019", value: 2}, //{date: "30-08-2019", value: 1} + {date: "30-08-2019", value: 1}
{date: "02-09-2019", value: 3}, //{date: "30-08-2019", value: 1} + {date: "02-09-2019", value: 2}
{date: "03-09-2019", value: 5}, //{date: "03-09-2019", value: 2} + {date: "03-09-2019", value: 3}
{date: "04-09-2019", value: 7} //{date: "04-09-2019", value: 3} + {date: "04-09-2019", value: 4}
]
Since both arrays are ordered, the approach I thought was looping the array with more data and sum it with the values of the array with less data, keeping track of what are the last date values that the smaller data gives, like this:
for(let i = 0; i < biggerData.length; i++){
//both have values for a date that exists the in bigger date array, so we sum them together
if(smallerData[i][biggerData[i].date]){
biggerData[i].value+=smallerData[i][biggerData[i].date];
lastValue = smallerData[i][biggerData[i].date];
//array with less data has a missing date, then sum the last saved value it gave
}else{
biggerData[i].value+=lastValue;
}
}
There's a problem with this approach, what if the smaller array has a date that the bigger one doesn't? That value one won't be added to the final result in this case.
When going more further than this I started to loop one array like I showed before and then I loop over the other one to get the missing dates, but this just seems too complex and inefficient. I'm pretty sure there's a solution for doing this in one loop (or even not using loops at all).
I'm asking if anybody can figure out a better solution for this, I'm making this in JavaScript.
I used a bunch of helper variables and converted the dates into an easily sortable format. Going through all existing dates in chronological order makes it quite easy to keep track of the accumulated value for each array. The sorting is the inefficient part, since the rest has linear complexity. You could optimize the sorting by taking advantage of the fact that both arrays are already sorted, but I was too lazy to do this here :)
// Turn '30-08-2019' into '2019-08-30'
const getSortableDate = (dateString) => dateString.split('-').reverse().join('-');
// Enable direct lookup of values
const mapDatesToValues = (data) => {
const dates = {};
data.forEach((item) => {
dates[getSortableDate(item.date)] = item.value;
});
return dates;
};
// Source data
const data1 = [
{date: "30-08-2019", value: 1},
{date: "03-09-2019", value: 2},
{date: "04-09-2019", value: 3}
];
const data2 = [
{date: "30-08-2019", value: 1},
{date: "02-09-2019", value: 2},
{date: "03-09-2019", value: 3},
{date: "04-09-2019", value: 4}
];
// values for direct lookup
const dates1 = mapDatesToValues(data1);
const dates2 = mapDatesToValues(data2);
// Chronological order for all existing dates
const allDatesOrdered = Object.keys({ ...dates1, ...dates2 }).sort();
// Helper variables:
let acc1 = 0; // Accumulated value while iterating through data1
let acc2 = 0; // Accumulated value while iterating through data2
let existsIn1;
let existsIn2;
let value1; // Current value while iterating through data1
let value2; // Current value while iterating through data2
allDatesOrdered.forEach((date) => {
existsIn1 = dates1.hasOwnProperty(date);
existsIn2 = dates2.hasOwnProperty(date);
value1 = dates1[date];
value2 = dates2[date];
// Remember accumulated values
if (existsIn1) {
acc1 = value1;
}
if (existsIn2) {
acc2 = value2;
}
if (existsIn1 && existsIn2) {
console.log('sum for', date, 'is', value1 + value2, '(found in both arrays)');
} else {
if (existsIn1) {
console.log('sum for', date, 'is', value1 + acc2, '(only found in data1)');
} else {
console.log('sum for', date, 'is', value2 + acc1, '(only found in data2)');
}
}
});
Figured out a way of going through this quite efficiently, but I converted the dates to millisecond timestamps due to beeing more suitable in the context I'm using this. Because of this change I'm not going to put my answer as the correct one.
#timotgl answer does it without converting date values so therefore I'm marking it the correct answer, although the solution also contains a change in the date format (that didn't helped me in my case but can help others).
I'm basically doing a zip function, going through both arrays and the merged result is being pushed in a result array of objects in one go.
data1.forEach((item) => {
item.date = new Date(item.date).getTime();
});
data2.forEach((item) => {
item.date = new Date(item.date).getTime();
});
let mergedPortfolio = [], //final array of objects
data1Idx = 0, //indexes for each array of objects
data2Idx = 0,
data1Last, //keeping track of last values
data2Last,
date1, //current date value
date2,
value1,//current value
value2;
while(data1Idx < data1.length || data2Idx < data2.length){
//both arrays exist
if(data1Idx < data1.length && data2Idx < data2.length){
date1 = data1[data1Idx].date;
date2 = data2[data2Idx].date;
value1 = data1[data1Idx].value;
value2 = data2[data2Idx].value;
if(date1 < date2){
mergedPortfolio.push({date: date1, value: value1+data2Last});
data1Last = value1;
++data1Idx;
}else if(data1[data1Idx].date === data2[data2Idx].date){
mergedPortfolio.push({date: date1, value: value1+value2})
data1Last = value1;
data2Last = value2;
++data1Idx;
++data2Idx;
}else if(data1[data1Idx].date > data2[data2Idx].date){
mergedPortfolio.push({date: date2, value: data1Last+value2});
data2Last = value2;
++data2Idx;
}
//Working through the remaining items in one data1 array
}else if(data1Idx < data1.length){
date1 = data1[data1Idx].date;
value1 = data1[data1Idx].value;
mergedPortfolio.push({date: date1, value: value1+data2Last});
data1Last = value1;
++data1Idx;
//Working through the remaining items in the data2 array
}else if(data2Idx > data2.length){
date2 = data2[data2Idx].date;
value2 = data2[data2Idx].value;
mergedPortfolio.push({date: date2, value: value2+data1Last});
data2Last = value1;
++data2Idx;
}
}

Sort array into "rows" in JavaScript

I have an array of objects that is currently like this, in which entries are ordered by date and time:
var checkin_data = [
{id: 430, date: "2013-05-05", time: "08:24"},
{id: 435, date: "2013-05-06", time: "04:22"},
{id: 436, date: "2013-05-06", time: "05:36"},
{id: 437, date: "2013-05-06", time: "07:51"},
{id: 488, date: "2013-05-06", time: "08:08"},
{id: 489, date: "2013-05-06", time: "10:12"},
{id: 492, date: "2013-05-06", time: "13:18"},
{id: 493, date: "2013-05-06", time: "15:55"},
{id: 494, date: "2013-05-06", time: "18:55"},
{id: 498, date: "2013-05-06", time: "22:15"},
{id: 501, date: "2013-05-07", time: "11:40"},
{id: 508, date: "2013-05-07", time: "18:00"},
{id: 520, date: "2013-05-08", time: "04:48"},
{id: 532, date: "2013-05-09", time: "21:11"},
{id: 492, date: "2013-05-10", time: "11:45"},
{id: 601, date: "2013-05-11", time: "18:12"}
];
The dates represent a date in a particular week: I'd like to sort this array in order to lay it out in "rows", so the data needs to be re-sorted to lay out like this (note the order of the dates):
var checkin_data = [
{id: 430, date: "2013-05-05", time: "08:24"},
{id: 435, date: "2013-05-06", time: "04:22"},
{id: 501, date: "2013-05-07", time: "11:40"},
{id: 520, date: "2013-05-08", time: "04:48"},
{id: 532, date: "2013-05-09", time: "21:11"},
{id: 492, date: "2013-05-10", time: "11:45"},
{id: 601, date: "2013-05-11", time: "18:12"},
{id: 436, date: "2013-05-06", time: "05:36"},
{id: 508, date: "2013-05-07", time: "18:00"},
{id: 437, date: "2013-05-06", time: "07:51"},
{id: 488, date: "2013-05-06", time: "08:08"},
{id: 489, date: "2013-05-06", time: "10:12"},
{id: 492, date: "2013-05-06", time: "13:18"},
{id: 493, date: "2013-05-06", time: "15:55"},
{id: 494, date: "2013-05-06", time: "18:55"},
{id: 498, date: "2013-05-06", time: "22:15"}
];
Getting the data in that order would allow me to lay out a table like this:
Thanks, any help would be appreciated.
Here is a suggestion using functional methods:
Reduce the list into arrays of buckets based on day, and sort that list (this is like reading the table you've got on rows)
Iterate through the rows in order, clear out unused ones.
Here:
//first, we collapse the array into an array of buckets by day
half_sorted = checkin_data.reduce(function(accum,cur){
var bucket = new Date(cur.date).getDay();
accum[bucket].push(cur);
return accum;
},[[],[],[],[],[],[],[]]).map(function(day){
return day.sort(function(x,y){ // now we sort each bucket
return new Date("01-01-1990 "+x.time) - new Date("01-01-1990 "+y.time);
});
});
// At this point, we have an array with 7 cells looking like your table
// if we look at its columns.
// finally, we push to the result table.
var result = [];
var daysToClear = 7;
for(var i=0;daysToClear>0;i=(i+1)%7){
if(half_sorted[i] && half_sorted[i].length > 0){
result.push(half_sorted[i].pop());
}else if(half_sorted[i] && half_sorted[i].length === 0){
half_sorted[i] = null;
daysToClear--;
}
}
Working fiddle
First of all, I think you're going about this in the wrong way. Please see my note below the following code.
To do exactly as you've asked, here's one way:
// parsing the date strings ourselves avoids time zone problems
function dateFromString(string) {
var parts = string.split('-');
return new Date(parseInt(parts[0], 10),
parseInt(parts[1], 10) - 1,
parseInt(parts[2], 10));
}
The above is a utility function.
var i, l, dates = [[], [], [], [], [], [], []], item;
// place the objects into dow-sorted buckets
for (i = 0, l = checkin_data.length; i < l; i += 1) {
item = checkin_data[i];
dates[dateFromString(item.date).getDay()].push(item);
}
i = 0;
l = 0;
checkin_data = [];
while (true) { // instead of a for loop to handle the row-wrap manually
if (dates[i][l]) {
item = dates[i][l];
checkin_data.push(item);
}
i += 1;
if (i === 7) {
if (!item) {
break; // we had a complete row with no data
}
item = undefined;
l += 1;
i = 0;
}
}
checkin_data is now sorted in the order you requested.
Note: you really don't need the second loop, because it is doing most of the work you'll have to do again to use the provided array. So in your routine for writing out the table, instead just use the data structure that the first loop creates. You would of course need a slightly different bailout strategy since you don't want to create an extra blank row, but I'll leave that up to you.
After a bit of thought, though, I came up with another way to do it, if you don't mind adding a new key to your objects:
function dateFromString(string) {
var parts = string.split('-');
return new Date(parseInt(parts[0], 10),
parseInt(parts[1], 10) - 1,
parseInt(parts[2], 10));
}
var i, l, counts = [0, 0, 0, 0, 0, 0, 0], item, dow;
for (i = 0, l = checkin_data.length; i < l; i += 1) {
item = checkin_data[i];
dow = dateFromString(item.date).getDay();
item.sortKey = ++counts[dow] * 7 + dow;
}
checkin_data.sort(function(a, b) {
return a.sortKey - b.sortKey;
});
I've come up with a solution, maybe not the most elegant, but it's working:
var sorted_data = [], elements_to_dump = [], i, j, tmp;
while (checkin_data.length > 0) {
for (i = 0; i < checkin_data.length; i++) {
if (checkin_data[i-1]) {
if (checkin_data[i-1].date === checkin_data[i].date) {
continue;
}
}
sorted_data.push(checkin_data[i]);
elements_to_dump.push(checkin_data[i].id);
}
for (j = 0; j < elements_to_dump.length; j++) {
for (i = 0; i < checkin_data.length; i++) {
if (checkin_data[i].id === elements_to_dump[j]) {
tmp = checkin_data.splice(i, 1);
break;
}
}
}
}
I'd like to sort this array in order to lay it out in "rows", so the data needs to be re-sorted [into this linear representation]. Getting the data in that order would allow me to lay out a table
No, it does not need to be. Actually, that's one step too much, and the order of your intermediate result makes absolutely no sense. What you should do instead is construct a (weekday-) list of (entries-per-day-) lists:
var days = [];
for (var i=0, date=null, day; i<checkin_data.length; i++) {
var entry = checkin_data[i];
if (entry.date !== date)
days.push(day = []);
day.push(entry);
}
That's it, you have you two-dimensional format now. Well, maybe you will need to transpose it to get it into the table you wanted, but that's not too complicated either:
var header = [],
table = [header]; // or create a DOM or a HTML string or whatever
for (var i=0; i<days.length; i++)
header.push(days[i][0].date /* or the weekday name? */);
for (var r=0; !done; r++) {
var row = [],
done = true;
// create cells:
for (var i=0; i<days.length; i++)
if (days[i].length > r) {
row[i] = days[i][r].time;
done = false;
} else
row[i] = "";
}
if (!done)
table.push(row);
}
What you're trying to do is very simple. This is what I would do:
var groups = groupBy(checkin_data, "date"); // groups items based on date
var table = zipAll.apply(null, toArray(groups)); // zips corresponding elements
// of each group into an array
After this you may create your table as follows:
var header = getHeader(groups), rows = map(table, getRow);
document.body.appendChild(getTable(header, rows));
Of course the actual code would be much bigger (a little more than 100 lines of code) since you need to write the logic for groupBy, toArray, zipAll, map, getHeader, getRow, getTable, etc.
Luckily for you I had the time to go and write all this stuff. Hence you now have a working demo: http://jsfiddle.net/hZFJw/
I would suggest that you browse through my code and try to understand how it works. It's too much to explain in one answer.
Note: My solution may be more than 100 lines of code. However it's still a simple solution because:
The actual code which generates the table is just 4 lines long and is very simple to understand.
The rest of the code is composed of reusable functions like groupBy, zipAll and map. These functions are very small and simple to understand.
Overall by abstracting the program into reusable functions the size of the program has increased. However the complexity of the program has considerably decreased.
You could achieve the same result by tackling the problem in an imperative style like most other answers do. However doing so makes the program harder to understand. Compare my code with other answers and see for yourself.
You can sort array with Alasql JavaScript library.
alasql.fn.WEEKDAY = function(d) { // User-defined function
return (new Date(d)).getDay();
};
var res = alasql('SELECT *, WEEKDAY(date) AS dow FROM ? ORDER BY dow', [checkin_data]);
Try this example at jsFiddle.

Categories

Resources