Searching an array of objects using jquery - javascript

I have a log analysis script to populate a complex visualisation.
Picture an array (called, rather unoriginally, 'log') of Activity objects, each of which is in the form:
{
name:foo,
activities:[
{time:t, action:a},
{time:t, action:a},
{time:t, action:a},
...
]
}
There will be up to 75 activity objects in the array, each containing an array of 400-600 actions (one timeslot every 5 minutes since midnight the previous day).
Given a known activity name (foo above) and a time that will already exist in the activities array, I need to update the associated action.
Each name will be unique and each time is in ascending order in the array in exact 5 minutes increments.
As I have to do this a little over 1000 times every time the graph is updated (so an average of 1000 values to update and 1000*500*60 points to plot), performance is a fairly key issue...
Looping in jq is far more efficient than anything I could write so, at the moment, I have
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
$.grep($.grep(log, function(n, i)
{
return (n.name == n)
}
)[0].activities, function(n, i)
{
return (n.time == t)
}
)[0].action = "bar";
That seems to be working, but it's taken me so long and I've had so many arguments with myself that I'm not confident.
Am I missing a better way?

I wont give you a better loop method for your problem, as any loop you come up with will relatively be no better than the last.
If you truly want a solution that will enhance performance, you should think about rearranging your object entirely. If every name of each log and time of each activities array is unique, you can change your object setup to have those values as the key of each subobject.
Using this method, you'll just be doing a key look up, no loop needed.
New LOG Object
var log =
{
unique_name : {
"activities" : {
time_1 : action_1,
time_2 : action_2,
time_3 : action_3,
etc...
}
},
unique_name_2 : {
"activities" : {
etc...
}
}
}
Now with var u_name = "foo"; and var t = "some time"; you can simply do...
log[u_name][t] = "some action";
Hope this helps!

Seems like you want the first matched activity of the first matched log.
In that case, you should break the loop after the first match is found. You can do this with .some().
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
log.some(function(ob, i) {
if (ob.name == n) {
ob.activities.some(function(ob2, i) {
if (ob2.time == t) {
ob2.action = "bar";
return true;
}
});
return true;
}
});
Also, your n parameter was shadowing your n variable, so I changed the param to ob.
But for loops will generally be quite a bit faster than functional methods.
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
for (var i = 0; i < log.length; i++) {
var ob = log[i];
if (ob.name == n) {
for (var j = 0; j < ob.activities.length; j++) {
var ob2 = ob.activities[j];
if (ob2.time == t) {
ob2.action = "bar";
break;
}
}
break;
}
}
If you decide that you should keep the outer loop going if there's no match found on the inner loop, change the code to one of these:
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
log.some(function(ob, i) {
if (ob.name == n) {
return ob.activities.some(function(ob2, i) {
if (ob2.time == t) {
ob2.action = "bar";
return true;
}
});
}
});
n = "foo";
t = new Date(y,mm,d,h,m).toLocaleString() // matches a time stamp in the log
OUTER:
for (var i = 0; i < log.length; i++) {
var ob = log[i];
if (ob.name == n) {
for (var j = 0; j < ob.activities.length; j++) {
var ob2 = ob.activities[j];
if (ob2.time == t) {
ob2.action = "bar";
break OUTER;
}
}
}
}

Related

How can i solve this exercixe with String.prototype?

How can i create this to console log like this?
String.prototype.sheldonize = function () {
return `knock ${this}`
}
'Penny'.sheldonize(3)
I have this code at the moment, but I dont know how to repeat knock more times
Use the repeat method to establish a number of 'knocks' in the line and to establish how many times the line should repeat
String.prototype.sheldonize = function (repeats) {
const line = `${'knock '.repeat(repeats)}${this}, `.repeat(repeats)
return `${line.substring(0,line.length-2)}.`;
}
console.log('Penny'.sheldonize(3));
Using For Loop and repeat method
String.prototype.sheldonize = function(count) {
let ans = "";
for (let i = 0; i < count; i++) {
ans += "knock ";
}
ans = `${ans}${this}, `.repeat(count)
ans = ans.substring(0, ans.length - 2) + "."
return ans;
}
console.log('Penny'.sheldonize(3))
console.log('Penny'.sheldonize(2))
Create an array and on each iteration up to n - 1 add the string to it, finally joining it up and returning the string from the function.
// If you're adding to a prototype it's always best
// to double check to see if the method already exists
// no matter (in this case) how unlikely
if (!('sheldonize' in String.prototype)) {
String.prototype.sheldonize = function (n) {
// Create the array
const out = [];
// Create the string
const knock = 'knock '.repeat(n);
// Loop until `n - 1` has been reached
// pushing the string into the array
// on each iteration
for (let i = 0; i < n; i++) {
out.push(`${knock}${this}`);
}
// Finally return the joined array
return out.join(', ');
}
}
console.log('Penny'.sheldonize(3));
console.log('Penny'.sheldonize(2));
console.log('John'.sheldonize(4));
Additional documentation
repeat

Manipulate more javascript array based on another array

I've a strange thing to do but I don't know how to start
I start with this vars
var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330];
var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17];
var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1];
So to start all the 3 array have the same length and the very first operation is to see if there is a duplicate value in sky array, in this case the 0 is duplicated and only in this case is at the end, but all of time the sky array is sorted. So I've to remove all the duplicate (in this case 0) from sky and remove the corresponding items from base and sum the corresponding items on ite. So if there's duplicate on position 4,5 I've to manipulate this conditions. But let see the new 3 array:
var new_base = [1,2,3,5,7,9,14,19,28,40,56,114,232,330];
var new_sky = [0,3,4,5,6,7,8,9,10,11,12,14,16,17];
var new_ite = [139,38,13,15,6,4,6,3,2,1,2,1,1,1];
If you see the new_ite have 139 instead the 64,52,23, that is the sum of 64+52+23, because the first 3 items on sky are the same (0) so I remove two corresponding value from base and sky too and I sum the corresponding value into the new_ite array.
There's a fast way to do that? I thought a for loops but I stuck at the very first for (i = 0; i < sky.length; i++) lol, cuz I've no idea on how to manipulate those 3 array in that way
J
When removing elements from an array during a loop, the trick is to start at the end and move to the front. It makes many things easier.
for( var i = sky.length-1; i>=0; i--) {
if (sky[i] == prev) {
// Remove previous index from base, sky
// See http://stackoverflow.com/questions/5767325/how-to-remove-a-particular-element-from-an-array-in-javascript
base.splice(i+1, 1);
sky.splice(i+1, 1);
// Do sum, then remove
ite[i] += ite[i+1];
ite.splice(i+1, 1);
}
prev = sky[i];
}
I won't speak to whether this is the "fastest", but it does work, and it's "fast" in terms of requiring little programmer time to write and understand. (Which is often the most important kind of fast.)
I would suggest this solution where j is used as index for the new arrays, and i for the original arrays:
var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330];
var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17];
var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1];
var new_base = [], new_sky = [], new_ite = [];
var j = -1;
sky.forEach(function (sk, i) {
if (!i || sk !== sky[i-1]) {
new_ite[++j] = 0;
new_base[j] = base[i];
new_sky[j] = sk;
}
new_ite[j] += ite[i];
});
console.log('new_base = ' + new_base);
console.log('new_sky = ' + new_sky);
console.log('new_ite = ' + new_ite);
You can use Array#reduce to create new arrays from the originals according to the rules:
var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330];
var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17];
var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1];
var result = sky.reduce(function(r, n, i) {
var last = r.sky.length - 1;
if(n === r.sky[last]) {
r.ite[last] += ite[i];
} else {
r.base.push(base[i]);
r.sky.push(n);
r.ite.push(ite[i]);
}
return r;
}, { base: [], sky: [], ite: [] });
console.log('new base:', result.base.join(','));
console.log('new sky:', result.sky.join(','));
console.log('new ite:', result.ite.join(','));
atltag's answer is fastest. Please see:
https://repl.it/FBpo/5
Just with a single .reduce() in O(n) time you can do as follows; (I have used array destructuring at the assignment part. One might choose to use three .push()s though)
var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330],
sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17],
ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1],
results = sky.reduce((r,c,i) => c === r[1][r[1].length-1] ? (r[2][r[2].length-1] += ite[i],r)
: ([r[0][r[0].length],r[1][r[1].length],r[2][r[2].length]] = [base[i],c,ite[i]],r),[[],[],[]]);
console.log(JSON.stringify(results));

In angular updating one variable inexplicably updates another

I am using angular and plotly to plot either the raw data or a moving average. I have the moving average working but I am running into an issue with assigning variables. I retrieve an array of user objects which each have an x and y key with arrays associated with them.
$scope.init=function(){
$rootScope.page='companyResults';
$scope.isPlotlyDone = false;
$scope.moving = false;
var refresh = function () {
incidentService.dayWiseTripsByUser(...).then(function (plotArray){
$scope.unaffectedPlot = plotArray;
$scope.movingAveragePlot = allMoving(plotArray);
console.log($scope.unaffectedPlot[0].y);
console.log($scope.movingAveragePlot[0].y);
});
};
refresh();
}
Im that code block, I would expect that $scope.unaffectedPlot[0].y and $scope.movingAveragePlot[0].y would have different arrays since I ran the latter through the following set of functions. The curious thing is that both $scope variables are synced, so if I run the second through allMoving the unaffectedPlot variable also gets smoothed and neither get synced obviously if I don't call allMoving. What am I missing about Angular? What is a good way to have a moving average work with a toggle? My plan is to show one variable or the other depending on if a button is clicked.
var d3_numeric = function(x) {
return !isNaN(x);
}
var d3sum = function(array, f) {
var s = 0,
n = array.length,
a,
i = -1;
if (arguments.length === 1) {
// zero and null are equivalent
while (++i < n) if (d3_numeric(a = +array[i])) s += a;
} else {
while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
}
return s;
};
var movingWindowAvg = function (arr, step) {
return arr.map(function (_, idx) {
var wnd = arr.slice(idx - step, idx + step + 1);
var result = d3sum(wnd) / wnd.length; if (isNaN(result)) { result = _; }
return result;
});
};
var allMoving = function(pltArray) {
var movingArray = [];
pltArray.forEach(function(plot){
var oneMoving = plot;
oneMoving.y = movingWindowAvg(plot.y, 5);
movingArray.push(oneMoving);
});
return movingArray;
}
This actually isn't an angular issue. I had to test it some since I didn't see what was going on either.
When you wrote
oneMoving.y = blah
you were actually altering the contents of plot for each element and in turn altering the contents of plotArray unintentionally (since plot is an object)
So you are only creating a reference variable when you say 'var onMoving = plot' )
To outright solve your problem you can clone plot but that isn't so clean of a process
One easy yet dirty way is
JSON.parse(JSON.stringify(obj))
from this thread
I threw together a shotty example that captures what was going wrong for you
var array = [{one:1, two:2},{one:1, two:2},{one:1, two:2}],
copyArray = array,
newArr = doStuff(array)
function doStuff(a) {
var otherNewArr = []
a.forEach(function(ae) {
var aVar = ae
aVar.one = 5
otherNewArr.push(aVar)
})
return otherNewArr
}
console.log(copyArray,newArr)
And to fix it just replace
var aVar = ae
with
var aVar = JSON.parse(JSON.stringify(ae))

Count how many times a character happens in a string and store it in an array JavaScript

I've been trying to figure out how to count how many times a character happens in a string and store it in another variable that will hold the character and the number of times it occurs in the string.
For example:
var greeting = "Hello World";
[H] occurs [1] time.
[e] occurs [1] time.
[l] occurs [3] times.
[o] occurs [2] times.
[W] occurs [1] time.
[r] occurs [1] time.
[d] occurs [1] time.
I am a JS Beginner and I tried as much as I can following guides and tutorials but this exercise seems to be out of my league. I would appreciate some help as to how would you guys go on about solving this problem.
Thanks!
You basically want to create a mapped set of characters to it's count in the string. Storing this stuff in an array might be weird as you'd need 2 Dimentional arrays. Instead store it in an hash object.
var greeting = "Hello world!";
var hash = {};
for(var i = 0; i < greeting.length; i++){
if(hash[greeting[i]] === undefined){
hash[greeting[i]] = 1;
} else {
hash[greeting[i]] += 1;
}
}
// printing the stuff in hash.
for(var x in hash){
if(hash.hasOwnProperty(x)){
console.log(x, hash[x]);
}
}
Anyway if you need this stuff in array, you can put so:
var arr = [];
var i = 0;
for(var x in hash){
if(hash.hasOwnProperty(x)){
arr[i++] = [x, hash[x]];
}
}
for(var i = 0; i< arr.length; i++){
console.log(arr[i]);
}
But I wouldn't recommend it. You can see redundancy for yourself.
Try this:
var result = {};
Array.prototype.map.call('Hello world!', function(x) {
if (typeof result[x] == 'undefined') {
result[x] = 1;
} else {
result[x] += 1;
}
});
console.log(result);
var result = {};
Array.prototype.map.call('Hello world!', function(x) {
if (typeof result[x] == 'undefined') {
result[x] = 1;
} else {
result[x] += 1;
}
});
console.log(result);

JavaScript Associate Array access returns "literal" Array Prototype Code

Here is my code so far for my school project (using Murach's JavaScript and DOM Scripting by Ray Harris). The chapter is only about Arrays and does not cover Prototypes, but I wanted to try it out based on Internet tutorials and references:
/*
Operation
This application stores the last name, first name, and score for
one or more students and it calculates the average score for all of the scores
that have been entered. When the user clicks on the Clear button, this
application clears the score data from this application. When the user clicks
on the Sort button, this application sorts the data in alphabetical order by
last name.
Specifications
The program should use one or more arrays to store the data.
Assume that the user will enter valid data.
*/
var $ = function (id)
{
return document.getElementById(id);
}
/*
Array prototype object extension for averaging the contents
"Adding a method to the built-in Array object to extract the average
of any numerical values stored in the array is therefore a useful
addition to that object." http://javascript.about.com/library/blaravg.htm
*/
Array.prototype.average = function ()
{
var avg = 0;
var count = 0;
for (var i = 0; i<this.length; i++)
{
//never gets here:
alert(i + ": " + this[i]);
var e = +this[i];
if(!e && this[i] !== 0 && this[i] !== '0')
{
e--;
}
if (this[i] == e)
{
avg += e;
count++;
}
}
return avg / count;
}
var addScore = function ()
{
studentScores[$('last_name').value + ', ' + $('first_name').value] = $('score').value;
update();
}
var clearScore = function ()
{
for (var i in studentScores)
{
studentScores[i] = '';
}
update();
}
var sortScore = function ()
{
scores.sort();
update();
}
var update = function ()
{
var result = '';
for (var i in studentScores)
{
result += (i + ': ' + studentScores[i] + '\n');
}
$('scores').value = result;
$('average_score').value = studentScores.average().toFixed(1);
}
window.onload = function ()
{
//a variable is initialized inside a function without var, it will have a global scope:
studentScores = [];
$('add_button').onclick = addScore;
$('sort_button').onclick = sortScore;
$('clear_button').onclick = clearScore;
$('last_name').focus();
}
When the code enters the "update()" function (end of the "addScore()" function) and accesses the array,
it populates the "literal" code from the Prototype into the text area (and fails to find the average on the next line):
I don't have enough rep points to post the image, but here is my output (there are no errors in the Chrome JS Console):
lowe, doug: 82
average: function ()
{
var avg = 0;
var count = 0;
for (var i = 0; i<this.length; i++)
{
//never gets here:
alert(i + ": " + this[i]);
var e = +this[i];
if(!e && this[i] !== 0 && this[i] !== '0')
{
e--;
}
if (this[i] == e)
{
avg += e;
count++;
}
}
return avg / count;
}
Any help appreciated (best practice or algorithm suggestions welcome)
Change this:
studentScores = []
to this:
studentScores = {}
...so that you're using an Object instead of an Array.
Your for loop in average() is just iterating numeric indices instead of the non-numeric keys you created.
Create your average() method as a standalone function like the others, and pass studentScores to it to calculate the average, and then use for-in instead of for.
That's simple: Do not use for…in enumerations for looping Arrays! You do so in your clearScore and update functions.
for (var prop in obj) loops over all [enumerable] properties, including those that are inherited from Array.prototype (for Array objects at least). A for (var i=0; i<array.length; i++) loop will not have that problem.
You have to decide whether studentScores is intended to be an array (i.e., an integer is used to access the stored data) or an Object/Associative Array (a string is used to set/get an element).
If you want to use the student's name as the key, you should declare studentScores as an object, and your 'average' method would have to be added to the Object prototype (which I don't recommend).
With the current state of the code, you have stumbled on the fact that an Array is also an object, and can have arbitrary properties attached to it, like any other object. You have added properties by name, but in your average method, you are trying to access numerically based indices. But that's not where the data you're adding is stored.
> a = [];
[]
> a['foo'] = 'bar';
'bar'
> a.length
0
> a[3] = 0;
0
> a.length
4

Categories

Resources