Problems with immutable array - javascript

Recently i started working with D3.js to plot a sunburn graph. The data is provided in JSON. For some design stuff i wanted to swap some items (called childrens in D3 doc).
I know in JS arrays are objects...so something like this:
var buffer = myarray[2];
is just a reference. Therefore a buffer for swapping has no effect (?).
Thus i invented a second array (childrens_final) in my code which adopt the items while the swapping process. Its just iterating through every item, a second iteration is looking for an item with the same name to set items with same name in a row. Therefore the swap.
var childrens = response_data.data.data['children'];
var childrens_final = []
for (var child = 0; child < childrens.length; child++) {
var category = childrens[child];
var found = false;
var i = child+1
while (!(found) && (i < childrens.length)) {
if (childrens[i]['name'] == category['name']) {
var childrens = swapArrayElements(childrens, child+1, i);
var one = childrens[child];
var two = childrens[child+1]
found = true;
}
i++;
}
if (found) {
childrens_final.push(one);
childrens_final.push(two);
child++;
}
else {
childrens_final.push(childrens[child])
}
}
response.data.data['children'] = childrens_final;
return response.data.data;
The function swapArrayElements() is just using splice:
function swapArrayElements(list, x, y) {
if (list.length ==1) return list;
list.splice(x, 1, list.splice(y,1, list[x])[0]);
return list;
}
The problem is that there is still no effect from the swap in the graph. But when logging the childrens_final. There is something like that in the console:
Array [ Object, Object, Object, Object, Object, Object, Object, Object, Object ]
The objects are in the right order! But in the array there is still the old order.
Thats actually so basic, but i dont see a solution.
Btw...the code is working under AngularJS.

Found the problem. It's a D3.js problem. D3 is sorting the data itself. You have to set explicitly:
d3.layout.partition.sort(null)
Otherwise every pre sorting process has no effect.

Related

How to compare the values of a particular key from both arrays using inArray in jQuery

I have two array with objects inside in it. I want to compare the values of a particular key from both arrays and do something with that. I have tried using inArray but couldn't succeed. Below is my code.
function initialize() {
geometry = 'http://yyyy.cartodb.com/api/v2/sql?q=SELECT name,ST_AsGeojson(the_geom) from msa_usa';
$.getJSON(geometry,
function(data) {
var place_names = [];
place_names.push({
name: "Abilene",
average: 8.65
});
for (i = 0; i < place_names.length; i++) {
if ($.inArray(data.rows[i].name, place_names[i].name) > -1) {
geom.push((data.rows[i].st_asgeojson));
average_value.push(place_names[i].average);
} else
(console.log("else"));
//console.log(place_names[i].name);
}
})
}
console.log(average_value.length);
console.log(geom.length);
I'm not sure why you're trying to use $.inArray() at all. It appears you're just trying to compare two properties on two objects (which happen to be in arrays, but you're already indexing to a particular object). If that's the case, then you can just directly compare the two properties:
if (data.rows[i].name === place_names[i].name)
But, in your code, you appear to have just created place_names so it will only have one value in it, not a whole bunch of values in it. So, now that confuses me as to what you're really trying to do here.
For more help, please describe in words what you're really trying to accomplish. Are you just trying to see if one particular .name property is in the data.rows array of objects?
If so, that would be a different piece of code like this:
function findPropInArray(array, propName, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][propName] === value) {
return i;
}
}
return -1;
}
Then, in your code you could use something like this:
if (findPropInArray(data.rows, "name", "Abilene") !== -1) {
// "Abilene" was as a name property in data.rows
}

Knockout JS: Sorting and splicing an observable array of observable numbers

Background
To learn Knockout JS, I'm (slowly) building a gradebook in the browser. My lastest issue involves dropping the lowest scores in an observable array. I have a student model, which has an observable array called scores. This array consists of observable scores, which are plain numbers.
My methodology for removing the lowest grades is as follows. First, I sort each array of scores, high-low, then, for now, splice the end of the array, such that the two lowest numbers are stored into a new array called low. The low variable will be used later when I calculate the mean.
The Problem(s)
First, my current dropLowestGrades method outright removes data from the dom, which I do not desire. Second, myObservableArray.sort() does not appear to do any sorting! I'm not sure where to go here. Relevant script follows.
JSBin: http://jsbin.com/fehoq/141/edit
JS
var StudentsViewModel = (function () {
function StudentsViewModel() {
var _this = this;
...
this.dropLowestScores = function() {
var low = [];
ko.utils.arrayForEach(_this.students(), function(student){
console.log(student.fullName());
student.scores().sort();
/*
student.scores().sort(function(a, b) {
return a() == b() ? 0 : (a() > b() ? 1 : -1);
});
*/
low = student.scores.splice((student.scores().length-2),student.scores().length);
console.log('low: ' + low);
return low;
});
};
HTML
I currently just bind the function to a button. For simplicity's sake, I'm hard-coding the drop to be two scores. I will later allow the user to pass in a value. Note that the button is named "Sort" right now because I was originally going to have a sort feature, then build my dropLowestScores method on top it.
<button data-bind="click: dropLowestScores">Sort</button>
Update
I have significantly updated my method after insights from the answers. The script below still suffers from cutting the values in my scores array, which I do not wish to change.
this.dropLowestScores = function() {
ko.utils.arrayForEach(_this.students(), function(student){
console.log(student.fullName());
var tmp = student.scores().sort();
console.log(tmp);
student.lowest = tmp.splice((tmp.length-2),tmp.length);
console.log('student lowest: ' + student.lowest);
});
};
Update 2
I changed my StudentModel such that it now has property lowest, which keeps track of the lowest scores when user drops grades.
var StudentModel = (function () {
function StudentModel(fullName) {
var _this = this;
this.fullName = ko.observable(fullName);
this.scores = ko.observableArray();
this.lowest = [];
...
You need to remember that functions like sort() return a sorted list of the array, they don't actually transform the array itself.
var studentScores = student.scores().sort();
or something like -
var sortedStudentScores(function () {
return student.scores().sort();
});
And if you are sorting on a property of the score you need to use something like -
var sortFunction = // Google a JavaScript sort function
var studentScores = student.scores().sort(sortFunction);
and if you are trying to remove items splicing is correct (it transforms the array itself) otherwise you need to use something like a computed to just not add the lowest in.
Updated
var sortedStudentScores(function () {
// Set a local var equal to the sorted scores
var theseScores = student.scores().sort().splice(0);
theseScores.splice(student.scores().length-2),student.scores().length);
});

How to keep Javascript array sorted, without sorting it

I have a Node.js application where I have to very often do following things:
- check if particular array already contains certain element
- if element does exist, update it
- if element do not exist, push it to the array and then sort it using underscore _.sortBy
For checking if the element already exists in the array, I use this binary search function:
http://oli.me.uk/2013/06/08/searching-javascript-arrays-with-a-binary-search/
In this way, when the size of the array grows, the sorting becomes slower and slower.
I assume that the array size might grow to max 20 000 items per user. And eventually there will be thousands of users. The array is sorted by a key, which is quite a short string. It can be converted into integer if needed.
So, I would require a better way to keep the array sorted,
in stead of sorting it every time new element is pushed onto it.
So, my question is, how should/could I edit the binary search algorithm I use, to enable me to
get the array index where the new element should be placed, if it doesn't already exist in the array?
Or what other possibilities there would be to achieve this. Of course, I could use some kind of loop that would start from the beginning and go through the array until it would find the place for the new element.
All the data is stored in MongoDB.
In other words, I would like to keep the array sorted without sorting it every time a new element is pushed.
It's easy to modify this binaryIndexOf function to return an index of the next element when no matches found:
function binaryFind(searchElement) {
'use strict';
var minIndex = 0;
var maxIndex = this.length - 1;
var currentIndex;
var currentElement;
while (minIndex <= maxIndex) {
currentIndex = (minIndex + maxIndex) / 2 | 0; // Binary hack. Faster than Math.floor
currentElement = this[currentIndex];
if (currentElement < searchElement) {
minIndex = currentIndex + 1;
}
else if (currentElement > searchElement) {
maxIndex = currentIndex - 1;
}
else {
return { // Modification
found: true,
index: currentIndex
};
}
}
return { // Modification
found: false,
index: currentElement < searchElement ? currentIndex + 1 : currentIndex
};
}
So, now it returns objects like:
{found: false, index: 4}
where index is an index of the found element, or the next one.
So, now insertion of a new element will look like:
var res = binaryFind.call(arr, element);
if (!res.found) arr.splice(res.index, 0, element);
Now you may add binaryFind to Array.prototype along with some helper for adding new elements:
Array.prototype.binaryFind = binaryFind;
Array.prototype.addSorted = function(element) {
var res = this.binaryFind(element);
if (!res.found) this.splice(res.index, 0, element);
}
If your array is already sorted and you want to insert an element, to keep it sorted you need to insert it at a specific place in the array. Luckily arrays have a method that can do that:
Array.prototype.splice
So, once you get the index you need to insert at (you should get by a simple modification to your binary search), you can do:
myArr.splice(myIndex,0,myObj);
// myArr your sorted array
// myIndex the index of the first item larger than the one you want to insert
// myObj the item you want to insert
EDIT: The author of your binary search code has the same idea:
So if you wanted to insert a value and wanted to know where you should
put it, you could run the function and use the returned number to
splice the value into the array.
Source
I know this is an answer to an old question, but the following is very simple using javascripts array.splice().
function inOrder(arr, item) {
/* Insert item into arr keeping low to high order */
let ix = 0;
while (ix < arr.length) {
//console.log('ix',ix);
if (item < arr[ix]) { break; }
ix++;
}
//console.log(' insert:', item, 'at:',ix);
arr.splice(ix,0,item);
return arr
}
The order can be changed to high to low by inverting the test

JavaScript array sort(function) to sort table rows- not sorting

I am trying to sort a dynamically constructed table on the client side. So far I have done my research to discover JavaScript's sort() method will take a callback. Here is what I have so far:
function retrvCatalog(e){
var merch = document.getElementById('merch');
var tRows = merch.rows;
var tBody = merch.tBodies;
var rowArr = [];
for (x in tRows){
rowArr[x] = tRows[x];
}
rowArr.sort(function(a, b){
if (a.cells.textContent < b.cells.textContent){
return -1;
}
if(a.cells.textContent > b.cells.textContent){
return 1;
}
return 0;
});
}
Stepping through it in Firebug, it appears to not change the order of the rows. Can someone please help me figure out what I am missing?
FINAL ALGORITHM
function retrvCatalog(e){
var fltr = e.id;
var merch = document.getElementById('merch');
var tblHead = merch.tHead;
merch.deleteTHead();
var tRows = merch.rows;
var rowArr = [];
for (var i=0; i<tRows.length; i++){
rowArr[i] = tRows[i];
}
rowArr = rowArr.sort(function(a, b){
if (fltr > 3){
a = parseFloat(a.cells[fltr].innerHTML);
b = parseFloat(b.cells[fltr].innerHTML);
}
else{
a = a.cells[fltr].innerHTML;
b = b.cells[fltr].innerHTML;
}
if (a>b){
return 1;
}
if(a<b){
return -1;
}
return 0;
});
while(merch.hasChildNodes()) {
merch.removeChild(merch.firstChild);
}
merch.appendChild(tblHead);
for (i=0;i<rowArr.length;i++){
merch.appendChild(rowArr[i]);
}
}
The final two columns in the row are numbers, so that is why the method to sort is slightly variable.
Several problems in your code.
First, you didn't declare the x variable.
for(var x...
Second, don't use for-in to iterate an array like collection. Use for.
for (var x = 0, len = tRows.length; x < len; x++){
rowArr[x] = tRows[x];
}
Third, there is no textContent property of a cells collection.
This is easy to test by logging its value. This should have been the first thing you tried.
console.log(a.cells.textContent); // undefined
You need to decide which cell you want, and ask for it by index.
console.log(a.cells[0].textContent);
Finally, you should be aware that this technique will not show the result of the sorting in the DOM. You're only sorting the Array. You'll need to append the new ordering to the DOM.
Maybe you knew this, but you didn't show it in your code.
I don't know the relationship of the rows to the tBodies, so I can't give an example. But if all the rows are in one tbody, just loop the Array, and tBody[0].appendChild(rowArr[i])
I'm not sure if I'm missing something, but I'm pretty sure you can't use textContent on the cells array. You need to index cells so you know which column to actually sort on. If your rows have 4 columns each (or even if there's only 1), you still need to tell the sort function which column to sort on.
So in your sort function, if you wanted to sort by the second column, you'd want something like:
rowArr.sort(function (a, b) {
if (a.cells[1].textContent < b.cells[1].textContent) {
return -1;
} else if (a.cells[1].textContent > b.cells[1].textContent) {
return 1;
}
return 0;
});
And I'm not sure what's in your cells, but you may want to use .innerHTML, not .textContent.
rowArr.sort(function(a,b) {
a = parseFloat(a.cells.textContent);
b = parseFloat(b.cells.textContent);
return (a-b);
};
"don't use for-in to iterate an array like collection." - user1673729
tRows is not an array, it's an HTML collection. That is why I used "for in" – nodirtyrockstar
An HTML Collection is an array like collection. Do not use for-in.

jQuery/Javascript index into collection/map by object property?

I have the following Javascript defining an array of countries and their states...
var countryStateMap = [{"CountryCode":"CA","Name":"Canada","States":[{"StateCode":"S!","CountryCode":"CA","Name":"State 1"},{"StateCode":"S2","CountryCode":"CA","Name":"State 2"}]},"CountryCode":"US","Name":"United States","States":[{"StateCode":"S1","CountryCode":"US","Name":"State 1"}]}];
Based on what country the user selects, I need to refresh a select box's options for states from the selected Country object. I know I can index into the country collection with an int index like so...
countryStateMap[0].States
I need a way to get the Country by CountryCode property though. I know the following doesn't work but what I would like to do is something like this...
countryStateMap[CountryCode='CA'].States
Can this be achieved without completely rebuilding my collection's structure or iterating over the set each time to find the one I want?
UPDATE:
I accepted mVChr's answer because it worked and was the simplest solution even though it required a second map.
The solution we actually ended up going with was just using the country select box's index to index into the collection. This worked because our country dropdown was also being populated from our data structure. Here is how we indexed in...
countryStateMap[$('#country').attr("selectedIndex")]
If you need to do it any other way, use any of the below solutions.
One thing you could do is cache a map so you only have to do the iteration once:
var csmMap = {};
for (var i = 0, cl = countryStateMap.length; i < cl; i++) {
csmMap[countryStateMap[i].CountryCode] = i;
}
Then if countryCode = 'CA' you can find its states like:
countryStateMap[csmMap[countryCode]].States
countryStateMap.get = function(cc) {
if (countryStateMap.get._cache[cc] === void 0) {
for (var i = 0, ii = countryStateMap.length; i < ii; i++) {
if (countryStateMap[i].CountryCode === cc) {
countryStateMap.get._cache[cc] = countryStateMap[i];
break;
}
}
}
return countryStateMap.get._cache[cc];
}
countryStateMap.get._cache = {};
Now you can just call .get("CA") like so
countryStateMap.get("CA").States
If you prefer syntatic sugar you may be interested in underscore which has utility methods to make this kind of code easier to write
countryStateMap.get = _.memoize(function(cc) {
return _.filter(countryStateMap, function(val) {
val.CountryCode = cc;
})[0];
});
_.memoize , _.filter
love your local jQuery:
small little function for you:
getByCountryCode = function(code){var res={};$.each(countryStateMap, function(i,o){if(o.CountryCode==code)res=o;return false;});return res}
so do this then:
getByCountryCode("CA").States
and it returns:
[Object, Object]

Categories

Resources