I have a function where I am sending in an object, wholesaler. There is an array of objects, wholesalers which contains multiple wholesaler objects.
I need the order number of the wholesaler that just came in to be increased by 1 and then find the wholesaler object that has a current ORDER of the new value and subtract 1.
$scope.wholesalers = [];
$scope.downWholesaler = function (wholesaler) {
if (wholesaler.id > 0) {
var orderNumber = wholesaler.ORDER;
var orderBelowNumber = orderNumber + 1;
angular.forEach($scope.wholesalers, function (myWholesaler) {
if (myWholesaler.ORDER === orderNumber) {
// raise current order +1
var add = orderNumber + 1
myWholesaler.ORDER = add;
}
//setting it
if (myWholesaler.ORDER === orderBelowNumber) {
//lower one with order below number -1
var subtract = orderNumber - 1;
myWholesaler.ORDER = subtract;
}
});
console.log($scope.wholesalers);
}
};
Above I am working on doing this. I go through $scope.wholesalers and increase the current objects ORDER number and then reduce the one next to it (ORDER +1) down one.
But the ORDER is going down and the next object is not changing at all. What am I doing wrong and how do I fix it?
edit: didn't have lastest code
Fix:
$scope.downWholesaler = function (wholesaler) {
if (wholesaler.id > 0) {
var add = parseInt(wholesaler.ORDER) + 1
angular.forEach($scope.wholesalers, function (myWholesaler) {
if (myWholesaler.ORDER == add) {
myWholesaler.ORDER = parseInt(myWholesaler.ORDER) - 1;
}
});
wholesaler.ORDER = add;
console.log($scope.wholesalers);
}
};
Related
I am using a dataset that contains around 65k data. I am mapping over the dataset multiple times to massage the dataset. After obtaining the dataset in the required format, I am using map to do some computations with the price of the current item. But, whenever I return the current object, it contains the computation data of the previous object.
Whenever I log the data, it always shows the current object and the computations based on the current object. But, the returned object contains a previous object's data. Here is the route:
const {priceBands} = require('../utils/profitComputations');
let profitArray = [];
//calculating the price bands
profitArray = _.map(nfoArray, item=>{
console.log(item.cmp);
//returns the current market price; getting the correct value here
let priceBandVar = priceBands(Number(item.cmp));
console.log(priceBandVar);
//applying some algorithms; getting the correct value here
return {item: item.cmp, profitBand: priceBandVar};
//Here I find a mismatch between the parameter and the calculations
});
Here is the priceBands function in my 'utils/profitComputations':
const _ = require('lodash');
const priceBandInterval = {'-4':0, '-3':0, '-2':0, '-1':0, '0floor':0,'0ceil':0,'1':0, '2':0, '3':0, '4':0};
let priceBands = {};
module.exports = {
priceBands: function(price){
let factor = 0;
if(price>=10000){
factor = 100;
}else if (price>=1000 && price<10000){
factor = 50;
}else if (price>=500 && price<1000){
factor = 25;
}else if (price>=100 && price<500){
factor = 10;
}else if(price>=25 && price<100){
factor = 2;
}else{
factor = 0.5;
}
let priceCeil, priceFloor;
if((price%factor) == 0){
priceCeil = price + factor;
priceFloor = price - factor;
} else {
const remainder = price%factor;
priceCeil = price - remainder + factor;
priceFloor = price - remainder;
}
_.map(Object.keys(priceBandInterval), item=>{
if(parseInt(item)>0){
priceBands[item] = (parseInt(item)*factor) + priceCeil;
} else if (parseInt(item)<0){
priceBands[item] = (parseInt(item)*factor) + priceFloor;
} else {
priceBands['0floor'] = priceFloor;
priceBands['0ceil'] = priceCeil;
}
});
return priceBands;
}
}
I would appreciate if someone can share some valuable insights on what I am missing.
You must clone the variable priceBandVar because javaScript variables are called by reference. The following code is your answer:
profitArray = _.map(nfoArray, item => {
console.log(item.cmp);
//returns the current market price; getting the correct value here
let priceBandVar = priceBands(Number(item.cmp));
console.log(priceBandVar);
//applying some algorithms; getting the correct value here
return {
item: item.cmp,
profitBand: clone(priceBandVar)
};
//Here I find a mismatch between the parameter and the calculations
});
function clone(o) {
var ret = {};
Object.keys(o).forEach(function(val) {
ret[val] = o[val];
});
return ret;
}
I'm trying to get whatever number is the most frequently occuring number in an array, so for an array containing 1,2,10,5,1 the result should be 1. The code I wrote returns me the frequency for each number, so 1 occurs twice, 2 occurs once, 10 occurs once etc. Any suggestions how I can fix my result?
function mode(arr) {
var uniqNum = {};
var numCounter = function(num, counter) {
if(!uniqNum.hasOwnProperty(num)) {
uniqNum[num] = 1;
} else {
uniqNum[num] ++;
}
};
arr.forEach(numCounter);
return uniqNum;
}
I've kept your code unchanged and added some extra statements. Here is the demo: http://codepen.io/PiotrBerebecki/pen/rrdxRo
function mode(arr) {
var uniqNum = {};
var numCounter = function(num, counter) {
if(!uniqNum.hasOwnProperty(num)) {
uniqNum[num] = 1;
} else {
uniqNum[num] ++;
}
};
arr.forEach(numCounter);
return Object.keys(uniqNum)
.sort((a,b) => uniqNum[b] - uniqNum[a]) // sort by frequency
.filter((val,ind,array) => uniqNum[array[0]] == uniqNum[val]) // leave only most frequent
.map(val => Number(val)); // convert text to number
}
console.log( JSON.stringify(mode([3,3,2,4,4])) ) // [3,4]
console.log( JSON.stringify(mode([2,4,3,3])) ) // [3]
I think it could be done only with a little modification to your forEach loop and the assistance of another auxiliary data structure:
function mode(arr) {
var freq = [], uniqNum = {}, i;
arr.forEach(function (num) {
uniqNum[num] = i = (uniqNum[num] || 0) + 1;
freq[i] = (freq[i] || []).concat(num);
});
return freq[freq.length - 1];
}
console.log(mode([1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6, 6, 7, 1, 6]));
With only one iteration over all the elements of the array we can gather enough information to print out the result:
uniqNum is the set you created to gather info about the element's frequency.
freq will be an array which last element will contain an array with the elements of higher frequency.
Fiddle. Hope it helps.
First we want to make an array where we count the number of occurrences of a certain value up to that point.
Then we use the reduce function to return an array of values read from the original array for the indexes whose values have the current max appearances. We redefine max and empty the final output array of modes (if new max is established) as we go along. We want this to be a collection in case there is a tie for maximum appearances.
Additional advantage of the below is that it doesn't require sort which is more expensive o(nlog n) and keeps the time complexity down to just linear. I also wanted to keep the functions used down to only two (map and reduce) as it is all that is need in this case.
edit: fixed a major bug uniqNum[e] += 1 instead of uniqNum[e] + 1 which went unnoticed as my initial case array was still returning expected result. Also made the syntax more concise in favor of more comments.
var arr = [1,2,10,5,1,5,2,2,5,3,3];
//global max to keep track of which value has most appearances.
var max = -1;
var uniqNum = {};
var modeArray = arr.map(function(e) {
//create array that counts appearances of the value up to that point starting from beginning of the input arr array.
if(!uniqNum.hasOwnProperty(e)) {
uniqNum[e] = 1;
return 1;
} else {
return uniqNum[e] += 1;
}
//reduce the above appearance count array into an array that only contains values of the modes
}).reduce(function (modes, e1, i) {
//if max gets beaten then redefine the mode array to only include the new max appearance value.
if(e1 > max){
//redefining max
max = e1;
//returning only the new max element
return [arr[i]];
//if its a tie we still want to include the current value but we don't want to empty the array.
}else if(e1 == max){
//append onto the modes array the co-max value
return[...modes, arr[i]];
}
return modes;
},[]);
alert(modeArray);
Here is a test you can run of my solution against #acontell. In my browser (Chrome with V8) my solution was around three-four times faster for arrays with large number of repeating values and even bigger advantage with distributions with lower number of repeating values. #acontell 's is a cleaner looking solution for sure, but definitely not faster in execution.
var arr = [];
for(var i=0; i < 100000; i++){
arr.push(Math.floor(Math.random() * (100 - 1)) + 1);
}
console.time("test");
test();
function test(){
var max = -1;
var uniqNum = {};
var modeArray = arr.map(function(e) {
//create array that counts appearances of the value up to that point starting from beginning of the input arr array.
if(!uniqNum.hasOwnProperty(e)) {
uniqNum[e] = 1;
return 1;
} else {
return uniqNum[e] += 1;
}
//reduce the above appearance count array into an array that only contains values of the modes
}).reduce(function (modes, e1, i) {
//if max gets beaten then redefine the mode array to only include the new max appearance value.
if(e1 > max){
//redefining max
max = e1;
//returning only the new max element
return [arr[i]];
//if its a tie we still want to include the current value but we don't want to empty the array.
}else if(e1 == max){
//append onto the modes array the co-max value
modes.push(arr[i])
return modes;
}
return modes;
},[]);
}
console.timeEnd("test");
console.time("test1");
test1();
function test1 () {
var freq = [],
uniqNum = {},
i;
arr.forEach(function(num) {
uniqNum[num] = i = (uniqNum[num] || 0) + 1;
freq[i] = (freq[i] || []).concat(num);
});
return freq[freq.length - 1];
}
console.timeEnd("test1");
I've tried as an exercise to solve this with native js functions.
var arr = [1,2,10,5,1];
// groupBy number
var x = arr.reduce(
function(ac, cur){
ac[cur]?(ac[cur] = ac[cur] + 1):ac[cur] = 1;
return ac;
}, {}
);
// sort in order of frequencies
var res = Object.keys(x).sort(
function(a,b){ return x[a] < x[b]}
);
res[0] has the most frequent element
I wonder what's the most efficient way to write a function that takes two arrays of random integers and terminates when a pair of matching Integers is found.
In the following example there are two arrays, one larger then the other.
The larger array contains the potential dividents, the smaller array the divisors.
I'd like the function to calculate until it has found a pair of divident and divisor that are divisible without remainder.
EXAMPLE:
var listDivident = _.shuffle(_.range(1, 101));
var listDivisor = _.shufle(_.range(1, 11));
randomMatch = function (listDivident, listDivisor) {
/* until ((listDivident % listDivisor !== "undefined")
&& (listDivident % listDivisor === 0) {
...
}
*/
return {
matchDivident: matchDivident,
matchDivisor: matchDivisor
};
};
Is there something like an "until" function or construct in JS?
Thanks for your help!
Vin
Unless your arrays are very large, you can simply generate all valid pairs first and then pick a random one:
var pairs = [];
_.each(listDivident, function(a) {
_.each(listDivisor, function(b) {
if(!(a % b))
pairs.push([a, b])
})
});
result = _.shuffle(pairs)[0];
What you call "until" can be achieved by looping until you find what you need:
var listDivident = _.shuffle(_.range(1, 101));
var listDivisor = _.shufle(_.range(1, 11));
randomMatch = function (listDivident, listDivisor) {
var i = 0,
found = false,
dividentLength = listDivident.length,
divisorLength = listDivisor.length,
matchDivident = null,
matchDivisor = null;
while (!found && i < dividentLength && i < divisorLength) {
if (listDivident[i] % listDivisor[i] === 0) {
matchDivident = listDivident[i];
matchDivisor = listDivisor[i];
found = true;
} else {
i++;
}
}
return {
matchDivident: matchDivident,
matchDivisor: matchDivisor
};
};
EDIT: After that, just call the randomMatch() with the whole arrays:
alert(randomMatch(listDivident, listDivisor));
I'm trying to pick a random film from an object containing film objects. I need to be able to call the function repeatedly getting distinct results until every film has been used.
I have this function, but it doesn't work because the outer function returns with nothing even if the inner function calls itself because the result is not unique.
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
T.J. Crowder already gave a great answer, however I wanted to show an alternative way of solving the problem using OO.
You could create an object that wraps over an array and makes sure that a random unused item is returned everytime. The version I created is cyclic, which means that it infinitely loops over the collection, but if you want to stop the cycle, you can just track how many movies were chosen and stop once you reached the total number of movies.
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
Then you can simply do something like:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
Note that the advantage of my implementation if you are cycling through items is that the same item will never be returned twice in a row, even at the beginning of a new cycle.
Update: You've said you can modify the film objects, so that simplifies things:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
This has the advantage that it doesn't matter if you call it with the same movies object or not.
Note the safety valve. Basically, as the number of watched films approaches the total number of films, our odds of picking a candidate at random get smaller. So if we've failed to do that after looping for twice as many iterations as there are films, we give up and just pick the first, if any.
Original (which doesn't modify film objects)
If you can't modify the film objects, you do still need the watchedFilms array, but it's fairly simple:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
Note that like your code, this assumes getRandomFilm is called with the same movies object each time.
Background
I'm attempting to check the existence of a value in array A in a second array, B. Each value is an observable number. Each observable number is contained in an observable array. The comparison always returns -1, which is known to be incorrect (insofar as values in A and B overlap). Therefore, there's something wrong with my logic or syntax, but I have not been able to figure out where.
JSBin (full project): http://jsbin.com/fehoq/190/edit
JS
//set up my two arrays that will be compared
this.scores = ko.observableArray();
//lowest is given values from another method that splices from scores
this.lowest = ko.observableArray();
//computes and returns mean of array less values in lowest
this.mean = (function(scores,i) {
var m = 0;
var count = 0;
ko.utils.arrayForEach(_this.scores(), function(score) {
if (!isNaN(parseFloat(score()))) {
//check values
console.log(score());
// always returns -1
console.log(_this.lowest.indexOf(score()));
//this returns an error, 'not a function'
console.log(_this.lowest()[i]());
//this returns undefined
console.log(_this.lowest()[i]);
//only do math if score() isn't in lowest
// again, always returns -1, so not a good check
if (_this.lowest.indexOf(score())<0) {
m += parseFloat(score());
count += 1;
}
}
});
// rest of the math
if (count) {
m = m / count;
return m.toFixed(2);
} else {
return 'N/A';
}
});
Update
#Major Byte noted that mean() is calculated before anything gets pushed to lowest, hence why I get undefined. If this is true, then what might be the best way to ensure that mean() will update based on changes to lowest?
You really just could use a computed for the mean
this.mean = ko.computed(
function() {
var sum = 0;
var count = 0;
var n = 0;
for(n;n < _this.scores().length;n++)
{
var score = _this.scores()[n];
if (_this.lowest.indexOf(score)<0) {
sum += parseFloat(score());
count++;
}
}
if (count > 0) {
sum = sum / count;
return sum.toFixed(2);
} else {
return 'N/A';
}
});
this will trigger when you add to lower(), scores() and change scores().
obligatory jsfiddle.
Update:
Forgot to mention that I change something crucial as well. From you original code:
this.dropLowestScores = function() {
ko.utils.arrayForEach(_this.students(), function(student){
var comparator = function(a,b){
if(a()<b()){
return 1;
} else if(a() > b()){
return -1;
} else {
return 0;
}
};
var tmp = student.scores().sort(comparator).slice(0);
student.lowest = ko.observableArray(tmp.splice((tmp.length-2),tmp.length-1));
});
};
apart from moving the comparator outside of dropLowestScores function, I changed the line:
student.lowest = ko.observableArray(tmp.splice((tmp.length-2),tmp.length-1));
to
student.lowest(tmp.splice((tmp.length-2),tmp.length-1));
student.lowest is an observable array, no need to define it as an observableArray again, in fact that actually breaks the computed mean. (The correction for Drop Lowest Scores as per my previous comment is left out here).