I need to transform an array to html table sorted by time, add rank and calculate time loss for the first place.
Array example:`
var array = [
["McNulty Oscar", "USA", "108:45.1"],
["Johansson Anton", "SWE", "87:48.9"],
["Schneider Florian", "SUI", "dns"],
["Rio Nicolas", "FRA", "57:45.1"]
];`
Final HTML table should look like this:
1 Rio Nicolas FRA 57:45.1
2 Johansson Anton SWE 87:55.9 30:10.8
3 McNulty Oscar USA 107:45.2 51:03.1
Schneider Florian SUI dns dns
My idea how to do it is in Jquery transform Array to HTML table and then sort it using tablesorter plugin. I still don't know when and how to calculate the time loss. Is it good way or am I totally out?
There is no simple solution to your situation, unfortunately. The values that you are dealing with are neither time values (as far as JS is concerned . . . see the JS Date object for more info on time: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date ), nor are they numbers that you can directly subtract, without manipulation.
Your best bet is to split up the values and do the subtraction in the long drawn out way . . . use var timeElements[2] = each.split(".") to get an array of the minutes/seconds/tenths-of-seconds for each value. At that point, you can either:
Do the subtraction the long way . . . subtract each value separately, carrying over values between the "columns", as needed, or
Convert the minutes and seconds into tenths of seconds, combine all three values into "total tenths-of-seconds", do the subtraction, and then convert them back to the three separate values, to get the difference.
The second option is probably the easier logic to pull off, even if it's a lot of steps.
There is also one other option, if you can do without the time differences . . . if you can prepend zeros to all of the time values, so that each of the minutes, seconds and tenths-of-seconds always have the same number of characters, AND if "dns" is the only other value that will ever occur, besides a time, a direct < comparison of the strings will still actually work for sorting, due to the way that greater than/less than works when used on strings. For example:
"057:45.1" is less than "087:48.9"
"087:48.9" is less than "108:45.1"
"108:45.1" is less than "123.54.7"
"123.54.7" is less than "243.04.6"
"243.04.6" is less than "dns"
This really isn't an optimal approach . . . it doesn't take much to throw off this kind of string comparison . . . but it's still worth mentioning, if you can be very sure of the values that you are getting.
. . . and if you can skip that whole "time difference" part of it. :D
I put together this demo using tablesorter and the tablesorter build table widget.
HTML
<div id="result"></div>
Script
$(function () {
var array = [
["Name", "Country", "Time", "Diff"],
["McNulty Oscar", "USA", "108:45.1", ""],
["Johansson Anton", "SWE", "87:48.9", ""],
["Schneider Florian", "SUI", "dns", ""],
["Rio Nicolas", "FRA", "57:45.1", ""]
],
// time column (zero-based index)
timeColumn = 3,
diffColumn = 4,
// convert min:sec.ms => sec.ms
convertTime = function (time) {
var val;
if (time && /:/.test(time)) {
val = time.split(':');
return parseFloat(val[0] || 0) * 60 + parseFloat(val[1] || 0);
} else {
return Number.POSITIVE_INFINITY;
}
},
// convert sec.ms => min:sec.ms
formatTime = function (time) {
var minutes = parseInt(time / 60);
return minutes + ':' + (time - (minutes * 60)).toFixed(1);
},
calculateDiff = function (table) {
var $tds, minTime,
$table = $(table),
$trs = $table.children('tbody').children(),
times = [];
$trs.each(function () {
$tds = $(this).children();
times.push(convertTime($tds.eq(timeColumn).text()));
});
minTime = Math.min.apply(Math, times);
$trs.each(function (i) {
var diff = times[i] - minTime,
result = times[i] === Number.POSITIVE_INFINITY ? 'dns' :
diff === 0 ? '' : formatTime(diff);
$(this).children()
.eq(0).html(result !== 'dns' ? i + 1 : '').end()
.eq(diffColumn).html(result);
})
.trigger('update');
};
$('#result').tablesorter({
theme: 'blue',
emptyTo: 'zero',
widgets: ['zebra', 'build'],
sortList: [[3, 0]],
widgetOptions: {
build_source: array,
build_footers: {
rows: 0
},
build_numbers: {
// include row numbering column?
addColumn: "Rank",
// make column sortable?
sortable: true
}
},
initialized: function (table) {
calculateDiff(table);
}
});
});
Update: Modified code and demo to include rank column and sort.
Related
Javavscript help (array at bottom)
I have a 2D array which holds a date of month in one column (a string) and an integer next to it i.e. array= ['Oct 2020' , 23456], ['Nov 2020' , 34567], ['Dec 2020' , -4567]...etc I have to work out:
total amount of months in this array (which I've managed),
the total of all the profit/loss form the integers (which I've managed alt methods welcome it'll help me learn more),
the average difference of profit/loss between each month's by calculating the difference between each month first then adding and dividing by total number of months,
the month with the biggest profit in the array
the month with biggest lost in the array.
I'm able to provide the code. I think I understand how to do this in a 1D array but with a 2D array the results didn't work. Please could I have some help and a walk through of your answer. I don't mind how its worked out but if you could have an example with the reduce function in your answer and an answer without that would be great if not no problem.
var finances = [
['Jan-2010', 867884],
['Feb-2010', 984655],
['Mar-2010', 322013],
['Apr-2010', -69417],
['May-2010', 310503],
['Jun-2010', 522857],
['Jul-2010', 1033096],
['Aug-2010', 604885],
['Sep-2010', -216386],
['Oct-2010', 477532],
['Nov-2010', 893810],
['Dec-2010', -80353],
['Jan-2011', 779806],
['Feb-2011', -335203],
['Mar-2011', 697845],
['Apr-2011', 793163],
['May-2011', 485070],
['Jun-2011', 584122],
['Jul-2011', 62729],
['Aug-2011', 668179],
['Sep-2011', 899906],
['Oct-2011', 834719],
['Nov-2011', 132003],
['Dec-2011', 309978],
['Jan-2012', -755566],
['Feb-2012', 1170593],
['Mar-2012', 252788],
['Apr-2012', 1151518],
['May-2012', 817256],
['Jun-2012', 570757],
['Jul-2012', 506702],
['Aug-2012', -1022534],
['Sep-2012', 475062],
['Oct-2012', 779976],
['Nov-2012', 144175],
['Dec-2012', 542494],
['Jan-2013', 359333],
['Feb-2013', 321469],
['Mar-2013', 67780],
['Apr-2013', 471435],
['May-2013', 565603],
['Jun-2013', 872480],
['Jul-2013', 789480],
['Aug-2013', 999942],
['Sep-2013', -1196225],
['Oct-2013', 268997],
['Nov-2013', -687986],
['Dec-2013', 1150461],
['Jan-2014', 682458],
['Feb-2014', 617856],
['Mar-2014', 824098],
['Apr-2014', 581943],
['May-2014', 132864],
['Jun-2014', 448062],
['Jul-2014', 689161],
['Aug-2014', 800701],
['Sep-2014', 1166643],
['Oct-2014', 947333],
['Nov-2014', 578668],
['Dec-2014', 988505],
['Jan-2015', 1139715],
['Feb-2015', 1029471],
['Mar-2015', 687533],
['Apr-2015', -524626],
['May-2015', 158620],
['Jun-2015', 87795],
['Jul-2015', 423389],
['Aug-2015', 840723],
['Sep-2015', 568529],
['Oct-2015', 332067],
['Nov-2015', 989499],
['Dec-2015', 778237],
['Jan-2016', 650000],
['Feb-2016', -1100387],
['Mar-2016', -174946],
['Apr-2016', 757143],
['May-2016', 445709],
['Jun-2016', 712961],
['Jul-2016', -1163797],
['Aug-2016', 569899],
['Sep-2016', 768450],
['Oct-2016', 102685],
['Nov-2016', 795914],
['Dec-2016', 60988],
['Jan-2017', 138230],
['Feb-2017', 671099]
];
code for how many months:
let monthsTotal = finances.length;
console.log("Total months: ", monthsTotal);
my first attempt to try and find the total profits/losses (i.e. sum of all integers). It just printed out the array in a messy form
const netTotal =finances.reduce((sum, curVal) => sum + curVal);
console.log("Total Profits/Loses: ", netTotal);
my second attempt actually works to find the sum which i have called netTotal
let netTotal = finances.map(function(v) { return v[1] }) // second value of each
.reduce(function(a,b) { return a + b }); // sum
console.log('Total:', netTotal)
so just the the last 3 bullet points technically
In the netTotal calculation, we don't need the extra trip through the array to get the integer value. Just pull it out with [1] as you go:
let netTotal = finances.reduce((a,b) => a + b[1]);
Getting the differences between months is simple if you handle the special case of the first month not having a prior month to compare to.
let diffs = finances.map((el, index) => {
// on the first one, assume the prior value is 0
let priorValue = index === 0 ? 0 : finances[index-1][1]; // edit
return el[1] - priorValue;
})
let totalDiffs = diffs.reduce((a,b) => a + b); // simple sum
let averageDiffs = totalDiffs / finances.length
Max and min are a little extra work because the calculation should track the months also. Here's a way to do it where the max/min thing being tracked is the nested array itself.
let maxArr = ['', -Number.MAX_VALUE] // everything is bigger than this
let minArr = ['', Number.MAX_VALUE] // everything is smaller than this
for (let i=0; i<finances.length; i++) {
let arr = finances[i];
if (arr[1] > maxArr[1]) maxArr = arr;
if (arr[1] < minArr[1]) minArr = arr;
}
I have been stuck at this problem for a couple of days and couldn't figure it out.
Below is the data I have:
body = {
columns: {
links: [
"link1 ,\nlink2\n",
"link3 ,\nlink4\n"
],
timestamps: [
"[2:00-4:30, 5:53-7:23],\n[15:15-16:16, 45:43-47:32, 1:02:30-1:03:45]",
"[1:00-3:20, 8:50-9:20],\n[18:50-19:30, 42:00-46:28, 1:05:31-1:08:00]"
]
}
};
The timestamps for link1 would be 2:00-4:30 and 5:53-7:23, timestamps for link2 would be 15:15-16:16, 45:43-47:32, 1:02:30-1:03:45, however, they are in one string, same case with "link1, link2\n"
What I intended to do is as follow
For example:
body["columns"]["timestamps"][0] // "[2:00-4:30, 5:00-7:00],\n[15:15-16:16, 45:43-47:32, 1:02:30-1:03:45]"
timestamp_group = body["columns"][timestamps][0].match(/(?<=\[)(.*?)(?=\])/g)[0] // "[2:00-4:30, 5:00-7:00]"
//by using timestamp_group.split('-')
timestamp_group[] // ["2:00-4:30", "5:00-7:00"]
//further split into ["2:00", "4:30"]
//then using hmsToSecondsOnly() to make it into [120,270]
function hmsToSecondsOnly(str) {
var p = str.split(':'),
s = 0, m = 1;
while (p.length > 0) {
s += m * parseInt(p.pop(), 10);
m *= 60;
}
return s;
}
It would be something like this in the end:
[[120,270],[300, 420]],[[,],[,],[,]]
Then I can map links to time stamps.
body["columns"]["links"][0][0] to body["columns"]["timestamps"][0][0]
and calculate their durations.
I want to avoid using too much nested for loops, is there any better(elegant) way to approach this? Thanks.
I am not sure how to loop them properly. if using forEach, how should i start(nested or recursive?) Here are the attempts I tried in console.
enter image description here
enter image description here
I am developing using fullcalendar.js, and in the week view, when a week is from 2 different months (for example 27 july - 2 august) the fullcalendar week view shows the two months text. I am searching everywhere but there is no solution for this. Maybe stackoverflow users can help me.
That's what I have:
And that's what I need:
I see the date format but is MMMM YYYY, and it returns two months or one automatically and it seems impossible to change this.
In Calendar.defaults (aprox. line 8300 in non-minimized code) object I can notice this:
titleRangeSeparator: ' \u2014 ', // emphasized dash
monthYearFormat: 'MMMM YYYY', // required for en. other languages rely on datepicker computable option
As I explained, monthYearFormat seems to only be one month, but in a specific moment it merges with titleRangeSeparator to become two months.
Do you know how this is solvable?
Thank you.
EDIT
I found the functions that make this complex string, but is used by month and day views that I don't want to change (I need only to week view). The code is the next. How can I modify this code to solve it?
// Date Range Formatting
// -------------------------------------------------------------------------------------------------
// TODO: make it work with timezone offset
// Using a formatting string meant for a single date, generate a range string, like
// "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
// If the dates are the same as far as the format string is concerned, just return a single
// rendering of one date, without any separator.
function formatRange(date1, date2, formatStr, separator, isRTL) {
var localeData;
date1 = fc.moment.parseZone(date1);
date2 = fc.moment.parseZone(date2);
localeData = (date1.localeData || date1.lang).call(date1); // works with moment-pre-2.8
// Expand localized format strings, like "LL" -> "MMMM D YYYY"
formatStr = localeData.longDateFormat(formatStr) || formatStr;
// BTW, this is not important for `formatDate` because it is impossible to put custom tokens
// or non-zero areas in Moment's localized format strings.
separator = separator || ' - ';
return formatRangeWithChunks(
date1,
date2,
getFormatStringChunks(formatStr),
separator,
isRTL
);
}
fc.formatRange = formatRange; // expose
function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
var chunkStr; // the rendering of the chunk
var leftI;
var leftStr = '';
var rightI;
var rightStr = '';
var middleI;
var middleStr1 = '';
var middleStr2 = '';
var middleStr = '';
// Start at the leftmost side of the formatting string and continue until you hit a token
// that is not the same between dates.
for (leftI=0; leftI<chunks.length; leftI++) {
chunkStr = formatSimilarChunk(date1, date2, chunks[leftI]);
if (chunkStr === false) {
break;
}
leftStr += chunkStr;
}
// Similarly, start at the rightmost side of the formatting string and move left
for (rightI=chunks.length-1; rightI>leftI; rightI--) {
chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]);
if (chunkStr === false) {
break;
}
rightStr = chunkStr + rightStr;
}
// The area in the middle is different for both of the dates.
// Collect them distinctly so we can jam them together later.
for (middleI=leftI; middleI<=rightI; middleI++) {
middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
}
if (middleStr1 || middleStr2) {
if (isRTL) {
middleStr = middleStr2 + separator + middleStr1;
}
else {
middleStr = middleStr1 + separator + middleStr2;
}
}
return leftStr + middleStr + rightStr;
}
This isn't directly supported unfortunately, but there is still a better way than modifying the FC source (that get's messy with patches and stuff).
There are several render hooks available that we can use to fix the formatting after the fact. viewRender doesn't work because it's called before the title changes. So we can use eventAfterAllRender instead.
eventAfterAllRender:function(){
if(view.name!=="agendaWeek")
return;
var $title = $("#calendar").find(".fc-toolbar h2"); //Make sure this is the right selector
var text = $title.text();
text = text.match(/.*? /)+text.match(/[0-9]+/);
$title.text(text); //replace text
}
JSFiddle - titleFormat hack
Not the most elegant thing in the world, but it should work better than modifying the source. Let me know if there are any issues.
Edit:
Also, if you're having problems with it flashing the wrong dateformat before the correct one, use css the make the title invisible. Then add a class to the element in eventAfterAllRender that makes it visible again.
I have an array of objects that have a keys called timestamp and motion. motion contains a value and timestamp contains a unix timestamp. I want to iterate over a number of the objects and find what "time of day" period they correspond to, I then want to total up the motion values for that given time of day and save the entire thing in an array of arrays. I want the duration to be changeable.
Let's say these are my objects;
{
timestamp: 1397160634,
motion: 2,
id: '534771d8c311731e21c75c9f'
},
{
timestamp: 1397160634,
motion: 3,
id: '534771d8c311731e21c75c9f'
}
Now I create my results array
var sampleDuration = 60; // Min
var minutesInDay = 1440;
var samplesPerDay = minutesInDay/sampleDuration;
var finalResultItem = []
for (var i = 0; i < samplesPerDay; i++) {
var IndividualresultArray = []
IndividualresultArray.push(60*i);
IndividualresultArray.push(0);
finalResultItem.push(IndividualresultArray);
}
I now have an array of arrays with each subarray's first item being a number (corresponding to a minute stamp) and the second value being zero.
I would now like to loop through all my objects and increment the second value (motion) based on the time of day range that is in the timestamp
_forEach(objects, function (object) {
{
// grab the timestamp
// figure out which minute range it coresponds to
// increment the array value that corresponds to the minute stamp
// rinse and repeat
}
this is where I go blank, I need the end result to look something like this
[[30, 5],[60, 20],[90, 5],[120, 0] .........]
or it could even look like this
[[000002400, 5],[000003000, 20],[000003600, 5],[000004200, 0] .........]
where the first value is a timestamp that ignores the year, month, and day, and only considers the time of day.
I have considered using moment.js in some capacity but I'm not sure how. Any help with this problem would be great.
I created a jsFiddle for you. The motion increment logic should look like (I'm using jQuery here but you get the point)
// Loop through and increment motion
$.each(objs, function (idx, obj) {
var date = new Date(obj.timestamp * 1000); // Convert to milliseconds
var minutesInDay = date.getUTCHours() * 60 + date.getUTCMinutes(); // Remove UTC for local time!
var minuteRange = Math.floor(minutesInDay / sampleDuration);
finalResultItem[minuteRange][1] += obj.motion;
});
EDIT: Removed some discussion after your edit. I also used more generic logic based on sampleDuration.
This should do it:
_forEach(objects, function (object) {
var date = new Date(objec.timestamp*1000);
var minuteOfDay = date.getUTCHours()*60+date.getUTCMinutes();
finalResultItem[minuteOfDay][1] += object.motion;
})
For a variable sample rate, employ a secondOfDay and divide that by your sampleDuration, then floor it to get your array index.
I am adding entries to a schema every hour in order to track growth over the course of days while maintaining a current score for the current day. Now I would like to be able to pull the most recent record for each day for the past week. The results would be 6 records at or around midnight for 6 days previous and the 7th being the latest for the current day.
Here is my schema:
var schema = new Schema({
aid: { type: Number }
, name: { type: String }
, score: { type: Number }
, createdAt: { type: Date, default: Date.now() }
})
Edit
I've tried using this static, but it pulls the exact same record 7 times
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
for (var i = 1; i <= 7; i++) {
this.where('name', new RegExp(name, 'i'))
.where('createdAt')
.gte(now - (i * oneday))
.desc('createdAt')
.findOne(function(err,doc){
docs.push(doc)
})
}
}
If I were using SQL I would do a subquery selecting MAXDATE and join it to my main query in order to retrieve the results I want. Anyway to do this here?
Kristina Chodorow gives a detailed recipe for this exact task in her book MongoDB: The Definitive Guide:
Suppose we have a site that keeps track of stock prices. Every few
minutes from 10 a.m. to 4 p.m., it gets the latest price for a stock,
which it stores in MongoDB. Now, as part of a reporting application,
we want to find the closing price for the past 30 days. This can be
easily accomplished using group.
I'm not familiar with Mongoose, however I've tried to adapt her example to your case below. Note I changed the createdAt default property from a value to a function and added an extra field datestamp to your schema:
var oneday = 24 * 60 * 60;
var schema = new Schema({
aid: { type: Number }
, name: { type: String }
, score: { type: Number }
// default: is a function and called every time; not a one-time value!
, createdAt: { type: Date, default: Date.now }
// For grouping by day; documents created on same day should have same value
, datestamp: { type: Number
, default: function () { return Math.floor(Date.now() / oneday); }
}
});
schema.statics.getLastWeek = function(name, fn) {
var oneweekago = Date.now() - (7 * oneday);
ret = this.collection.group({
// Group by this key. One document per unique datestamp is returned.
key: "datestamp"
// Seed document for each group in result array.
, initial: { "createdAt": 0 }
// Update seed document if more recent document found.
, reduce: function(doc, prev) {
if (doc.createdAt > prev.createdAt) {
prev.createdAt = doc.createdAt;
prev.score = doc.score;
// Add other fields, if desired:
prev.name = doc.name;
}
// Process only documents created within past seven days
, condition: { "createdAt" : {"$gt": oneweekago} }
}});
return ret.retval;
// Note ret, the result of group() has other useful fields like:
// total "count" of documents,
// number of unique "keys",
// and "ok" is false if a problem occurred during group()
);
A solution is to use group() to groups records by day. It's fancy, slow and can be blocking (meaning nothing else can run at the same time), but if your record set isn't too huge it's pretty powerful.
Group: http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group
As for mongoose, I'm not sure if it supports group() directly, but you can use the node-mongodb-native implementation, by doing something like this (pseudo-code, mostly):
schema.statics.getLastWeek = function(name, cb) {
var keys = {} // can't remember what this is for
var condition = {} // maybe restrict to last 7 days
var initial = {day1:[],day2:[],day3:[],day4:[],day5:[],day6:[],day7:[]}
var reduce = function(obj, prev) {
// prev is basically the same as initial (except with whatever is added)
var day = obj.date.slice(0,-10) // figure out day, however it works
prev["day" + day].push(obj) // create grouped arrays
// you could also do something here to sort by _id
// which is probably also going to get you the latest for that day
// and use it to replace the last item in the prev["day" + 1] array if
// it's > that the previous _id, which could simplify things later
}
this.collection.group(keys, condition, initial, reduce, function(err, results) {
// console.log(results)
var days = results // it may be a property, can't remember
var lastDays = {}
days.forEach(function(day) {
// sort each day array and grab last element
lastDays[day] = days[day].sort(function(a, b) {
return a.date - b.date // check sort syntax, you may need a diff sort function if it's a string
}).slice(-1) // i think that will give you the last one
})
cb(lastDays) // your stuff
})
}
Some more comparisons between groups and map reduce from my blog:
http://j-query.blogspot.com/2011/06/mongodb-performance-group-vs-find-vs.html
There are no docs about the group command in the native driver, so you'll have to peer through the source code here:
https://github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/collection.js
Also for sort, check check https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort for exact syntax
Edit: Better Idea!!!
Just have a special collection called "lastRequestOfDay" and make the _id the day.
Overwrite the value with each new request. It will be super easy to query and fast to write and will always have the last value written each day!
Add another property to the schema named dateAdded or something.
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
for (var i = 0; i < 7; i++) {
this.where('name', new RegExp(name, 'i'))
.where('createdAt')
.lt(now - (i * oneday))
.gte(now - ((i + 1) * oneday))
.desc('createdAt')
.findOne(function(err,doc){
// might not always find one
docs.push(doc)
})
}
return fn(null, docs)
}
Try something like this:
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
, that = this
function getDay(day){
that.where('name', new RegExp(name, 'i'))
.where('createdAt')
.gte(now - (day * oneday))
.desc('createdAt')
.findOne(function(err,doc){
docs.push(doc)
})
}
for (var i = 1; i <= 7; i++) {
getDay(i);
}
}
Nobody seems to be trying for "close to midnight". :) The issue I saw with the original code was that it checked for a time greater than or equal to x days ago... which will always return the most recent time. I'm confused as to why DeaDEnD's solution returns the same record 7 times, though. Also, you never called fn, but that's not really the biggest of your concerns, is it?
Try adding on .lt(now - (now % oneday) - (i - 1) * oneday) (assuming 0-indexed; if it's 1-indexed, change that to say i - 2)