I have demonstrated my problem in a fiddle: http://jsfiddle.net/sljux/zVg7R/3/
I have an array of objects. For simplicity, lets say each object has a name, and an array of values, all observable:
self.array = [
{
name: ko.observable("first"),
values: ko.observableArray([1, 2, 3])
},
{
name: ko.observable("second"),
values: ko.observableArray([2, 3, 4])
},
{
name: ko.observable("third"),
values: ko.observableArray([3, 4, 5])
}
];
From that array I need to filter out certain objects, and add an additional computed to each one, which computes the sum of all values. The filtering part isn't important to the problem, so it won't be done. The problem is that the observable needs to be a property of each object, and it needs to reference that object's values array.
My best try was:
self.newArray = ko.observableArray([]);
for(var i = 0; i < self.array.length; i++) {
var obj = {
name: ko.observable("new " + self.array[i].name()),
values: ko.observableArray(self.array[i].values())
};
obj.sum = ko.computed({
read: function() {
var res = 0;
for (var j = 0; j < obj.values().length; j++)
res += obj.values()[j];
return res;
},
owner: obj
});
self.newArray.push(obj);
}
The problem is that the reference to the values observable array is somehow lost. That is, on the first computing, each object gets the sum of his values, but in the end, each computed computes the sum of the last object in array.
I tried with, and without the owner part of the computed, the reference is still transferred. The bug is obviously visible in the fiddle, where I have set up three buttons that change each values array.
I also tried setting it up as a class:
function Obj(name, values) {
...
}
self.newArray.push(new Obj("first", [1, 2, 3]);
but the same thing happens.
Sort answer: use ko.utils.arrayForEach instead of the manual for loop:
ko.utils.arrayForEach(self.array, function(item) {
var obj = {
name: ko.observable("new " + item.name()),
values: ko.observableArray(item.values())
};
obj.sum = ko.computed({
read: function() {
var res = 0;
for (var j = 0; j < obj.values().length; j++)
res += obj.values()[j];
return res;
},
owner: obj
});
self.newArray.push(obj);
});
Demo JSFiddle.
Long answer: you've bitten by the fact that in JavaScript the variables are function scoped so your for loop it does not create three local obj variable but reuses the same one variable and this combined with how closures work you will end up with all your computed referencing the last value:
You can solve this with wrapping the for body in an immediately executed function:
for(var i = 0; i < self.array.length; i++) {
(function(){
var obj = {
name: ko.observable("new " + self.array[i].name()),
values: ko.observableArray(self.array[i].values())
};
obj.sum = ko.computed({
read: function() {
var res = 0;
for (var j = 0; j < obj.values().length; j++)
res += obj.values()[j];
return res;
},
owner: obj
});
self.newArray.push(obj);
})();
}
Demo JSFiddle.
Related
This question already has answers here:
Why does JavaScript map function return undefined?
(13 answers)
Closed last month.
So I have 24 "person" objects, which I created using "names" array, so that "dude" names are repeating;
Next I made a function that compares some random numbers to object id-s, and if they are equal,then I am trying to make new array of person names that go with that id.
(I'm sorry if this sounds too complicated, but I really don't understand why my map method doesn't work.)
Here 2 versions of my code. The first one works, and does what I want.
var names = ["jim", "jack", "aaron", "hugh", "jeff", "cameron", "allen", "charlie"];
var len = 3,
arr1 = [],
counter = 1;
for (var i = 0; i < len; i++) {
names.forEach(name => {
arr1.push({
id: counter,
dude: name
});
counter++;
});
}
console.log(arr1);
function checkName(nums) {
var namesarr = [];
for (var i = 0; i < arr1.length; i++) {
nums.forEach(function(num) {
if (num === arr1[i].id) {
namesarr.push(arr1[i].dude);
}
});
}
return (namesarr);
};
console.log(checkName([1, 3, 6]));
But in second version, my map function returns undefined, and I really don't understand why?!
var names = ["jim", "jack", "aaron", "hugh", "jeff", "cameron", "allen", "charlie"];
var len = 3,
arr1 = [],
counter = 1;
for (var i = 0; i < len; i++) {
names.forEach(name => {
arr1.push({
id: counter,
dude: name
});
counter++;
});
}
console.log(arr1);
function checkName(nums) {
var namesarr;
for (var i = 0; i < arr1.length; i++) {
namesarr = nums.map(function(num) {
if (num === arr1[i].id) {
return arr1[i].dude;
}
});
}
return (namesarr);
};
console.log(checkName([1, 3, 6]));
You're overwriting the namesarr variable on each iteration of the loop. I think you meant to add to it on each pass, rather than overwrite it. To do that, you can use the array .concat method.
Then, finally, make sure to filter all the undefined values from the result before you return it.
var names = ["jim", "jack", "aaron", "hugh", "jeff", "cameron", "allen", "charlie"];
var len = 3,
arr1 = [],
counter = 1;
for (var i = 0; i < len; i++) {
names.forEach(name => {
arr1.push({
id: counter,
dude: name
});
counter++;
});
}
console.log(arr1);
function checkName(nums) {
var namesarr = [];
for (var i = 0; i < arr1.length; i++) {
namesarr = namesarr.concat(nums.map(function(num) {
if(num === arr1[i].id) {
return arr1[i].dude;
}
}));
}
return (namesarr.filter(Boolean));
};
console.log(checkName([1,3,6]));
The result of map() is an array of all the returned values. If a function doesn't execute a return statement, it's equivalent to ending with return undefined.
In your second version, whenever the if condition fails, you don't execute return arr1[i].dude;, so it's returning undefined by default. It's as if you'd written:
namesarr = nums.map(function(num) {
if (num === arr1[i].id) {
return arr1[i].dude;
} else {
return undefined;
}
});
The other difference between your two versions of the code is that the second version reassigns namesarr each time through the for loop. So you're just printing the result of the last iteration.
The first version assigns it once before the loop, and adds to it only when the if condition succeeds, so you get the elements from all iterations.
Your map function doesn't return something for all the items in the array (because of the if) so some values in the result array will be undefined.
Plus, your map is inside a loop that loops over arr1, so for each iteration of that loop, the array namesarr get overridden. So the map will be as if it was applied only for the last element in arr1, thus if nums contain N elements then namesarr will have at least N - 1 undefined values in it (N - 1 if the last object of arr1 matches nums, N if not).
The problem is better solved using reduce instead of map:
function checkName(nums) {
return arr1.reduce(function(namesarr, obj) { // for each object obj in the array arr1
if(nums.indexOf(obj.id) !== -1) { // if the object's id is in the array nums
namesarr.push(obj.dude); // then add the object's dude to the array namesarr
}
return namesarr;
}, []); // the empty array to initialize namesarr
}
I need some dummy data that I can use. I usually, manually create my data in a variable like...
const jsonData = [
{
name: 'random',
age: '0',
}
];
I'm trying to create a function that gives me back an array with a list of objects in (like the above) the amount of objects in the array is based on the value I give it.
I came to the conclusion using the map function would be best, like so:
const myMap = new Map();
myMap.forEach((q, n) => {
});
I'm still learning. Honestly now sure how I'd go about creating this.
You can use a simple loop:
function genData(n) {
var results = [];
for (var i = 0; i < n; i++) {
results.push({name: 'random', age: 0});
}
return results;
}
If you want to randomize the property values, look into Math.random.
Here is a simple example that picks a random value from provided lists:
function genData(n, values) {
var results = [];
for (var i = 0; i < n; i++) {
var obj = {};
for (var prop in values) {
obj[prop] = values[prop][Math.floor(Math.random() * values[prop].length)];
results.push(obj);
}
}
return results;
}
console.log(genData(3, {name: ['foo', 'bar', 'baz'], age: [0, 10, 20, 30, 40]}));
I want to iterate over my 'areasarray' in the array 'areas' dataprovider array,
I have no idea how to loop over an array in an array, I've tried several tries with for-loops but none of it succeeded.
this is amCharts Maps framework.
var areasarray = {};
//get JSON File
$(function getData() {
var url = "../assets/document.json";
$.ajax({
url: url,
dataType: 'json',
success: function (data) {
console.log(data);
for (var i = 0; i < data.fact.length; i++) {
if (inverseCountryCodes[data.fact[i].dims.COUNTRY] != null) {
areasarray[i] = {
"id": inverseCountryCodes[data.fact[i].dims.COUNTRY],
"value": data.fact[i].Value,
"info": "Verkeersdoden per 100 000 inwoners: " + data.fact[i].Value
}
}
}
//console.log(areasarray);
//Map initialiseren
var map;
map = new AmCharts.AmMap();
map.colorSteps = 20;
var dataProvider =
{
mapVar: AmCharts.maps.worldLow
areas: [
{
id: "BE",
value: 10,
info: "Verkeersdoden ..."
}
]
};
console.log(dataProvider);
map.areasSettings = {
autoZoom: true,
selectedColor: "#338DAB"
};
map.dataProvider = dataProvider;
var valueLegend = new AmCharts.ValueLegend();
valueLegend.right = 10;
valueLegend.minValue = "little";
valueLegend.maxValue = "a lot!";
map.valueLegend = valueLegend;
map.addListener("clickMapObject", function (event) {
document.getElementById("info").innerHTML = '<p><b>' + event.mapObject.title + '</b></p><p>' + event.mapObject.info + '</p>';
});
map.mouseWheelZoomEnabled = true;
map.write("mapdiv");
}
});
});
If you want to iterate over areasarray which is actually an object and not an array you should look into using a for...in loop
For iterating over arrays within arrays, one approach would be to nest for loops
for(var i = 0; i < array1.length; i++) {
for(var j = 0; j < array2.length; j++) {
// do something
}
}
It's not clear to me what you mean by "array in an array" in this context and it would help if you provided more information about what exactly you are trying to accomplish
I would try a nested loop. Here is an example of creating an array of arrays and then looping through each.
var matrix = []
matrix[1] = []
matrix[1][1] = "foo"
matrix.forEach(function(column){
column.forEach(function(cell){
console.log(cell);
});
});
var areasarray = {}; means it's an object, not an array.
To iterate through each items in this object, try this.
var keys = Object.keys(areasarray);
keys.forEach(function(k) {
// you can access your item using
// k is the property key
console.log(areasarray[k]);
console.log(areasarray[k].id);
console.log(areasarray[k].value);
console.log(areasarray[k].info);
});
Not sure why you chose to create areasarray as an object.
If you wanted to, you could have defined it as:
var areasarray = [];
Then when adding to the array you use:
areasarray.push({
"id": inverseCountryCodes[data.fact[i].dims.COUNTRY],
"value": data.fact[i].Value,
"info": "Verkeersdoden per 100 000 inwoners: " + data.fact[i].Value
});
So later on, you can simply do:
for (var i = 0; i < areasarray.length; i++) {
console.log(areasarray[i]);
console.log(areasarray[i].id);
console.log(areasarray[i].value);
console.log(areasarray[i].info);
}
Note: in the above code, i is an index, where in the object block code, k is a key to the object.
Use nested loops.
Example:
var a1=["1","2","3","4","5","6","7"];
var a2=["a","b","c","d","e"];
for(var i=0;i<a1.length;i++) //loop1
{
console.log(a1[i]);
for(var j=0;j<a2.length;j++) //loop2
{
console.log(a2[j]);
}
}
Sample Output:
1st iteration of loop1:
1abcde
2nd iteration of loop1:
2abcde
and so on...
For every iteration of loop1,loop2 iterates 4 times(j<5).
Hoping I got your question right...This could be an answer.!
I have an array of objects like this one:
{'shapeId': 'shapeid1', 'latlong': '45.42342,-65.23424', 'number': 5}
I want the sum of the values of the attribute number when the value of the attribute shapeId is the same.
Is there a built in method for that? Thank you!
What I tried so far is this, using reduce() method, but the results are odd when the function is called many times.
function selectMarkersInPoly() {
allTotal = new Array();
alert(allMarkers.length)
for (var i=0; i < createdShapes.length; i++) {
for (var j=0; j < allMarkers.length; j++){
var latlong = allMarkers[j].getPosition();
if(google.maps.geometry.poly.containsLocation(latlong, createdShapes[i]) == true) {
allMarkers[j].setOptions({
icon : "http://labs.google.com/ridefinder/images/mm_20_white.png",
shapeId:createdShapes[i].id
});
}else{
allMarkers[j].setOptions({
icon : "http://labs.google.com/ridefinder/images/mm_20_red.png",
shapeId: 'shapeid0'
});
}
}
}
allTotal = allMarkers.reduce(function(res, obj) {
if (!(obj.shapeId in res)){
res.__array.push(res[obj.shapeId] = obj);
}else {
res[obj.shapeId].number += obj.number;
}
return res;
}, {__array:[]}).__array
.sort(function(a,b) { return b.shapeId - a.shapeId;
});
for (var j=0; j < allTotal.length; j++) {
alert(allTotal[j].shapeId + ': ' + allTotal[j].number)
}
}
There are 32600 numbers total in allMarkers.number among 3505 objects. The first time I call the function I get:
allTotal= [
Object { number=32598, shapeId='shapeid0'},
Object { number=2, shapeId='shapeid1'}
]
where shapeid0 is not selected. The second time I call the function without changing anything, I get:
allTotal= [
Object { number=65193, shapeId='shapeid0'},
Object { number=2, shapeId='shapeid1'}
]
And the third time:
allTotal= [
Object { number=97788, shapeId='shapeid0'},
Object { number=2, shapeId='shapeid1'}
]
Your code is a bit oddly formatted. However, the problem is that inside the expression res.__array.push(res[obj.shapeId] = obj); you assign the object itself to the res, not a clone. With res[obj.shapeId].number += obj.number; you then manipulate the original object, which is why the results grow in subsequent calls.
Better:
var numberSums = allMarkers.reduce(function(res, obj) {
var id = obj.shapeId;
if (id in res)
res[id] += obj.number;
else
res[id] = obj.number;
return res;
}, {});
Object.keys(numberSums).sort(function(a,b){return a-b;}).forEach(function(id) {
alert(id+": "+numberSums[id]);
});
I have a data dictionary like this:
var data = {
'text1': 1,
'text2': 2,
'text3': 3,
...
'text20': 20
];
I need to pick a random selection of those keys and then shuffle it's values. In the example, it should write something like this:
> console.log(choose(data, 5));
[ { key: 'text15', value: 8 },
{ key: 'text6', value: 3 },
{ key: 'text3', value: 15 },
{ key: 'text19', value: 6 },
{ key: 'text8', value: 19 } ]
For now I'm extracting the keys into another array and sorting by Math.random() but I'm stuck at swaping the values because no key should have the same value it initially had.
How would you swap key/values here?
Thanks
I put together a possible solution using underscore.js to simplify traversing the object and arrays in a cross browser manner:
var data = {
text1: 1,
text2: 2,
text3: 3,
text4: 4,
text5: 5,
text6: 6,
text7: 7,
text8: 8,
text9: 9,
text10: 10
};
function choose(data, num)
{
var keys = _.sortBy(
_.keys(data),
function(k)
{
return (Math.random() * 3) - 1;
}
),
results = [],
k1, k2;
if (num > keys.length) {
throw new Error('Impossible to retrieve more values than exist');
}
while (results.length < num) {
k1 = k2 || keys.pop();
k2 = keys.pop();
results.push({key:k1, value: data[k2]});
}
return results;
}
console.log(choose(data, 5));
This isn't necessarily an optimal approach but it seems to meet your requirements. I first grab all of the keys and sort them randomly. I then loop through the random keys creating a new object with one key and the following keys value. That way you'll always end up with a different value associated with each key. If you need it to work when the value of num passed in to the function == the number of keys in the data then you'll have to add a little more code - I'll leave that as an exercise for the reader :)
You can have a play with this code on jsfiddle:
http://jsfiddle.net/zVyQW/1/
You could do this:
collect names and corresponding values in two arrays names and values
shuffle both arrays independently of each other
take the first n items of both arrays and combine them
Here’s an example implementation:
Array.prototype.shuffle = function() {
for (var i=this.length-1, j, tmp; i>0; i--) {
j = Math.round(Math.random()*i);
tmp = this[i], this[i] = this[j], this[j] = tmp;
}
return this;
};
function choose(data, number) {
var names = [], values = [], pick = [];
for (var name in data) {
if (data.hasOwnProperty(name)) {
names.push(name);
values.push(data[name]);
}
}
names = names.shuffle(), values = values.shuffle();
for (var i=Math.min(number >>> 0, names.length-1); i>=0; i--) {
pick.push({key: names[i], value: values[i]});
}
return pick;
}
Been a while since this was answered, but I was working on shuffling and found the following to be by far the fastest implementation with an evenly random distribution.
It's fast because it only makes one call to Math.random on each iteration, all the rest is done by property access. It doesn't modify the array, just reassigns values.
function shuffle(a) {
var t, j, i=a.length, rand=Math.random;
// For each element in the array, swap it with a random
// element (which might be itself)
while (i--) {
k = rand()*(i+1)|0;
t = a[k];
a[k]=a[i];
a[i]=t;
}
return a;
}
It uses a combination of three functions (including the Array shuffle prototype method).
Here is the complete code:
var obj = {
"red":"RED",
"blue":"BLUE",
"green":"GREEN",
"yellow":"YELLOW",
"purple":"PURPLE"
};
Array.prototype.shuffle = function(){
for (var i = 0; i < this.length; i++){
var a = this[i];
var b = Math.floor(Math.random() * this.length);
this[i] = this[b];
this[b] = a;
}
}
obj = shuffleProperties(obj); // run shuffle
function shuffleProperties(obj) {
var new_obj = {};
var keys = getKeys(obj);
keys.shuffle();
for (var key in keys){
if (key == "shuffle") continue; // skip our prototype method
new_obj[keys[key]] = obj[keys[key]];
}
return new_obj;
}
function getKeys(obj){
var arr = new Array();
for (var key in obj)
arr.push(key);
return arr;
}
for(key in obj){
alert(key);
}
Check all post,
Best Regards.
Use an implementation of random that randomizes a discrete set of values, such as Math.rand seen here. For each index, randomize Math.rand(index, length-1) to get a list of random indexes, the location off all indices will change.