Reordering an array in javascript - javascript

there are many questions/answers dealing with this topic. None match my specific case. Hopefully someone can help:
I have an array of indexes such as:
var indexes = [24, 48, 32, 7, 11];
And an array of objects that look similar to this:
var items = [{
name : "whatever",
selected : false,
loading : true,
progress : 55,
complete : false
},
{
name : "whatever 2",
selected : false,
loading : false,
progress : 100,
complete : true
}];
Each integer within the indexes array corresponds to the actual index of an object within the items array.
Lastly I have a variable which defines the new insert position within the items array:
var insertindex = ??
What I would like to do is to take all objects in the items array that have the indexes stored in the indexes array, remove them, then finally place them back, all next to each other at a specified index defined by the variable insertindex.
I have been trying to use splice() by copying the objects at each index to a temporary array, then removing them from the original array, then finally looping through this new temporary array and putting them back into the original items array at the new positions, but seems to be hitting a mental brick wall and cannot get it to work correctly.
To summarize, I simply want to take all objects from the items array that match an index defined in the indexes array, put them together and reinsert them at a predefined index, back into the items array.
To help with conceptual visualization. If you think of the app as a javascript file manager, allowing the reordering of multiple file selections which do not have to be adjacent. The indexes array defining the current selection and the items array defining the list of files. And finally the rearoderindex defines the new insert position that all selected files should move to.
EDIT: As was rightly suggested here is the code I am playing with right now:
function reorder(items, indexes, insertindex){
var offset = 0;
var itemscopy = items.slice(0); //make shallow copy of original array
var temparray = new Array(); // create temporary array to hold pulled out objects
//loop through selected indexes and copy each into temp array
for(var i=0, len=indexes.length; i<len; i++){
array[i] = itemscopy[self.cache.selecteditems[i]];
}
//remove all selected items from items array
for(var i=0, len=indexes.length; i<len; i++){
items.splice(indexes[i], 1);
}
//finally loop through new temp array and insert the items back into the items array at the specified index, increasing the index each iteration using the offset variable.
for(var i=0, len=temparray.length; i<len; i++){
items.splice((insertindex+offset), 0, array[i]);
offset++;
}
}
I'm aware this is pretty horrible and that looping three times should not be necessary. But I've been trying lots of different methods, some working when reordering in one direction, some in the other an mostly, not at all. I figured I would look to optimize the function later, once I have it working with accuracy.
I'm certain I must be doing something extremely stupid or completely overlooking something, but for the life of me I can't work out what right now.

If you don't care about order of indexes array, I'd suggest another short solution:
items.splice.apply(items, [insertIndex, 0].concat(indexes.sort(function(a, b) {
return a - b;
})).map(function(i, p) {
return p > 1 ? items.splice(i - p + 2, 1).pop() : i;
}));
DEMO: http://jsfiddle.net/T83fB/
To make it short I used Array.map() method, which is not supported by old IE browsers. However it is always easy to use a shim from MDN.

You can use the .splice() function to add elements to an array, as well as removing items from it. The general principle is:
Sort indexes into ascending numeric order
Iterate over indexes, removing the element at that index (adjusting for the number of removed items) and storing it in a removedItems array
Add the removedItems array back in at the required index
The code to do that would look something like this:
var removedItems = [];
// sort indexes
indexes.sort(function(a, b) {
if(a < b) return -1;
else if(b < a) return 1;
return 0;
});
for(var i = 0; i < indexes.length; i++) {
var index = indexes[i];
removedItems.push(items.splice(index - removedItems.length, 1));
}
var insertIndex = 1;
items.splice.apply(items, [insertIndex, 0].concat(removedItems));
Take a look at this jsFiddle demo.

Related

How to edit the highest 5 values of an array in javascript?

I have a single 1d array storing a series of scores. My end goal is to have the 5 highest scores with brackets around them ( e.g. (score) ) for me to then format and output onto the display. In the case where there are duplicate scores, the first occurrences would be bracketed, up to that 5 top values.
So for example:
[9,8,10,9,6,8,6,5,4,4,3,3,6] would become [(9),(8),(10),(9),6,(8),6,5,4,4,8,3,8]
What I've tried so far is this:
var topvals = scores.sort((a,b) => b-a).slice(0,5);
for(var j=0; j< scores.length; j++){
if(topvals.length==0){
break;
}else if(topvals.includes(scores[j])){
scores[j] = "(" + scores[j] + ")";
topvals.splice(topvals.indexOf(scores[j]),1);
}
}
With the idea that topvals is an array containing the top 5 values, and I then loop through scores looking for those values, removing them each time.
What this results in is the first 5 values of scores having brackets around them.
I'm happy to go a completely different route with this, or just fix what I've done so far. Thanks in advance.
sort with indexes attached. Use index positions to change to desired format. O(N log N) for the sort.
scores = [9,8,10,9,6,8,6,5,4,4,3,3,6]
scores.map((n,i)=>({n,i})) // each object as n: number, i: index
.sort((a,b)=>a.n-b.n).slice(-5) // sort, slice top 5
.forEach(({i})=>scores[i]=`(${scores[i]})`) // add parens by indexes
console.log(scores)
If you have very, very large data sets and need something closer to O(N), you'll want to implement a pivot selecting algorithm. Just sorting is simpler.
The call to sort() sorts scores in place, which means it changes the scores array. So that is why you need to clone it first, then sort, then slice. Also you probably want to eliminate duplicates from your scores. I linked a stack overflow answer that describes how to do that. Since filter does not filter scores in place, but rather returns a new array, you don't need to explicitly call slice(0) to clone scores.
var scores = [9,8,10,9,6,8,6,5,4,4,3,3,6];
// Function from linked SO answer.
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
// Filter unique scores into copy of array, then slice the top 5.
var topvals = scores.filter(onlyUnique).sort((a,b) => b-a).slice(0,5);
for (var j=0; j< scores.length; j++) {
if( topvals.length==0) {
break;
} else if(topvals.includes(scores[j])) {
scores[j] = "(" + scores[j] + ")";
topvals.splice(topvals.indexOf(scores[j]),1);
}
}
Get all unique values in a JavaScript array (remove duplicates)

Javascript Nested Loop Pushing to Array

I am relatively new to programming and am having some issues with a project I am working on.
msg.newCG2 = [];
for(i=0;i<msg.newCG.length;i++){
for(j=0;j<msg.campaignGroup.length;i++){
if(msg.campaignGroup[j].col10 === msg.newCG[j]){
msg.groupTotals = msg.groupTotals + msg.campaignGroup[j].col11;
}
msg.newCG2.push(msg.newCG[i], msg.groupTotals)
}
}
Basically, for each one of the "IDs" (integers) in msg.newCG, I want to look for each ID in msg.campaignGroup and sum up the totals for all listings with the same ID, from msg.campaignGroup.col11 - then push the ID and the totals to a new array - msg.newCG2.
When I run the code, the first item sent through processes, but grinds to a halt because of memory. I assume this is because of an error in my code.
Where did this code go wrong? I am sure that there are better ways to do this as a whole, but I am curious where I went wrong.
There is a typo in your second for loop and the push needs to happen inside the outer loop.
msg.newCG2 = [];
for(i=0;i<msg.newCG.length;i++){
for(j=0;j<msg.campaignGroup.length;j++){
if(msg.campaignGroup[j].col10 === msg.newCG[i]){
msg.groupTotals = msg.groupTotals + msg.campaignGroup[j].col11;
}
}
msg.newCG2.push(msg.newCG[i], msg.groupTotals)
}
How about:
msg.newCG2 = [];
for (i=0; i < msg.newCG.length; i++) {
var groupTotal = 0;
for (j=0; j < msg.campaignGroup.length; j++) {
if (msg.campaignGroup[j].col10 === msg.newCG[i]){
groupTotal = groupTotal + msg.campaignGroup[j].col11
}
}
msg.newCG2.push(groupTotal)
}
Rather than looping 1.2M times, it would be more efficient to use a single-pass over the 4000 campaign groups, grouping by id to create an array of totals for all ids -- I like using the reduce() function for this:
var cgMap = msg.campaignGroups.reduce(function(arr, grp) {
var grpid = grp.col10;
var count = grp.col11;
var total = arr[grpid] || 0;
arr[grpid] = total + count;
},
[]);
I know, the reduce(...) function is not the easiest to grok, but it takes the second arg (the empty array) and passes it, along with each campaign group object in turn, to that inline function. The result should be a simple array of group totals (from col11), indexed by the group id (from col10).
Now, it's just a matter of returning the totals for those 300 ids found in msg.newCG -- and this map() function does that for us:
var cgOut = msg.newCG.map(function(gid) {
return cgMap[gid]; // lookup the total by group id
}
);
I've made some assumptions here, like the group ids are not terribly large integers, and are rather closely spaced (not too sparse). From the original code, I was not able to determine the format of the data you are wanting to return in msg.newCG2. The final push() function would append 2 integers onto the array -- the output group id and the total for that group. Having pairs of group ids and totals interleaved in a flat array is not a very useful data structure. Perhaps you meant to place the total value into an array, indexed by the group id? If so, you could re-write that line as:
msg.newCG2[msg.newCG[i]] = msg.groupTotals;

Using Jquery Splice

I am trying to remove an item from an Array using Splice method.
arrayFinalChartData =[{"id":"rootDiv","Project":"My Project","parentid":"origin"},{"1":"2","id":"e21c586d-654f-4308-8636-103e19c4d0bb","parentid":"rootDiv"},{"3":"4","id":"deca843f-9a72-46d8-aa85-f5c3c1a1cd02","parentid":"e21c586d-654f-4308-8636-103e19c4d0bb"},{"5":"6","id":"b8d2598a-2384-407a-e2c2-8ae56c3e47a2","parentid":"deca843f-9a72-46d8-aa85-f5c3c1a1cd02"}];
ajax_delete_id = "e21c586d-654f-4308-8636-103e19c4d0bb,deca843f-9a72-46d8-aa85-f5c3c1a1cd02,b8d2598a-2384-407a-e2c2-8ae56c3e47a2";
$.each(arrayFinalChartData, function (idx, obj) {
var myObj = obj.id;
if (ajax_delete_id.indexOf(myObj) >= 0) {
var vararrayFinalChartDataOne = arrayFinalChartData.splice(idx, 1);
}
});
console.log(arrayFinalChartData);
Please check at : http://jsbin.com/deqix/3/edit
Note : It does not complete the "last leg " of the loop. That means if I have 4 items, then it successfully executes 3 items. Same goes for 6,7...items.
I need to "REMOVE" few items and "PRESERVE THE BALANCE" in an array.
You can use for loop instead of $.each function:
alert('length before delete ' + arrayFinalChartData.length);
for (var i = arrayFinalChartData.length - 1; i >= 0; i--) {
id = arrayFinalChartData[i].id;
if(ajax_delete_id.indexOf(id) > -1){
arrayFinalChartData.splice(i, 1);
}
};
alert('length after delete ' + arrayFinalChartData.length);
Demo.
Complete edit :
After researching a bit, and console.logging a lot, I finally found where the issue is coming from ! It's actually quite simple, but very sneaky !
Theoretical explanation :
You are calling the splice function with the variable "idx", but remember that the splice function remaps / reindexes your array ! So, each time you splice the array, its size decreases by one while you're still inside the $.each function. The splice messes up jQuery indexation of your array, because jQuery doesn't know that you're removing elements from it !
Iterative explanation :
$.each function starts, thinking your array has 4 elements, which is true, but only for a while. First loop, idx = 0, no splice. Second loop, idx = 1, splice, which means that your array has now 3 elements left in it, reindexed from 0 to 2. Third loop, idx = 2, splice, which means your array has now two elements left in it, but $.each continues ! Fourth loop, idx = 3, js crashes, because "arrayFinalChartData[3]" is undefined, since it was moved back each time the array got spliced.
To solve your problem, you need to use a for loop and to start analyzing the array from the end, not from the beginning, hence each time you splice it, your index will decrease as well. And if you want to preserve balance, just push the removed items into an array. Remember that you are analyzing the array from the end, so items pushed into the "removedItems" array will be in reverse order. Just like this :
var removedItems = new Array();
for (var i = arrayFinalChartData.length - 1; i >= 0; i--) {
var myObj = arrayFinalChartData[i].id;
if (ajax_delete_id.indexOf(myObj) >= 0) {
removedItems.push(arrayFinalChartData.splice(i, 1)[0]);
}
}
console.log(arrayFinalChartData);
console.log(removedItems);
And a working demo (inspect the page, observe the console and click "Run") :
http://jsfiddle.net/3mL6C/3/
I will not give credit to myself for this answer, thanks to another similar thread for giving me a hint.
Your problem here is that when the $.each is set up, it's expecting a certain length of object, which you are then changing. You need to loop in a way that respects the dynamic length of the object.
var i = 0;
while (i < arrayFinalChartData.length) {
var myObj = arrayFinalChartData[i].id;
if (ajax_delete_id.indexOf(myObj) >= 0) {
// current item is in the list, so remove it but KEEP THE SAME INDEX
arrayFinalChartData.splice(i, 1);
} else {
// item NOT in list, so MOVE TO NEXT INDEX
i++
}
}
console.log(arrayFinalChartData);
Demo

Why is the JS Array skipping certain entries when running the splice method within a loop?

I have an array:
var productIds = new Array("1","6","7","Product-Total","ccFirst","ccLast","email","ccExpMonth","ccExpYear","billingAddress","billingCity","billingState","billingZip");
I want to delete a value if it is not a number:
for(var i=0; i<productIds.length; i++){
if(isNaN(Number(productIds[i]))) {
productIds.splice(i,1);
}
}
It seems the splice method is affecting the positions of the values.
I found this solution(Looping through array and removing items, without breaking for loop) which is what I think I need, but I can't figure out how to implement their answers for my code.
How can I fix my problem?
btw, I posted a more detailed jsFiddle: http://jsfiddle.net/fte3m/2/
When you delete the entry at index i, you need to subtract 1 from i so the entry that was moved down is not skipped.
for(var i=0; i<productIds.length; i++){
if(isNaN(productIds[i])) {
productIds.splice(i--,1); // <-- Decrement i
}
}
As RobG points out in his comment, it's easier to simply process the array in the other direction:
for(var i=productIds.length - 1; i>=0; i--){
if(isNaN(productIds[i])) {
productIds.splice(i,1);
}
}
Alternatively, if you don't mind reassigning productIds to be a new array object and you are running JS 1.6 or later:
productIds = productIds.filter(function (id) {
return !isNaN(id);
});
(Note that in the above, I just use isNaN(value) rather than isNaN(Number(value)). Whenever Number(value) would return NaN, isNaN(value) will return true, and vice versa. Note also that neither approach will filter out a null id, since Number(null)==0 and isNaN(null)==false. If you want to exclude null entries from the result, you will need to test for that separately.)

Javascript - storing the index of all array values in a variable

Say I have an array in JS: var fruits = [apple,orange,banana]
I want to store the index of each fruit in variables such that at any point in time, if I add more stuff to the array, I will still know that the index of apple is X. So in this case, 0 is apple, but if I add something to the beginning of that away, the index of apple changes.
The more verbose way I can think of is to loop through the array
for (var i=0;i<fruits.length;i++) {
switch(fruits[i]) {
case:"apple"
var indexApple = i;
break;
//etc
}
}
Another way I can think of is use the value of the arrays as the variable name.
for (var i=0;i<fruits.length;i++) {
//psedo code
var 'index' + fruits[i] = i;
}
So in the end I'd have var indexApple = 0, indexOrange = 1, etc. THe key to the second method is to be able to create a dynamic variable by concatenating the string 'index' and the value of the array to create that variable. Not sure how to do that.
Note: Ideally I want the variables that store the index to be dynamically generated. Such that I only I can modify/add to the fruits array, and a new variable will be generated to store the index.
it seems like ensuring your the value of the index is legitimate will be difficult. i would include jquery and use the inArray method which returns the index of the item in the array.
function showIndexes() {
var appleIndex = $.inArray(fruits, "Apple"); //returns 0
var guavaIndex = $.inArray(fruits, "Guava"); //returns -1
fruits.unshift("Guava");
appleIndex = $.inArray(fruits, "Apple"); //returns 1
guavaIndex = $.inArray(fruits, "Guava"); //returns 0
}
The simplest solution is simply to build an Object which gives you nearly O(1) lookup time, and will scale with your array:
function LinkFruits(fruits) {
FruitLookup = {}
fruits.forEach((fruit,ind) => FruitLookup[fruit] = ind)
}
Now you can simply "lookup" your index from the FruitLookup table when needed like:
console.log("The index of apple is",FruitLookup.apple,"and for orange is",FruitLookup.orange)
Now if you modify your array you simply need to run LinkFruits(fruits).
Technical Note: If you want to fully automate this process you can look into Array.observe() which is now deprecated. Or overload the push and pop methods of this array to trigger the update before falling back to the default prototype methods.

Categories