Find all the combinations for each element in mutliple arrays - javascript

I have the following object
var mapping = {
0: ['A','B','C'],
1: ['D','E','F'],
2: ['G', 'H', 'I'],
---
---
9: ['X','Z']
}
There is a function that references the above object and finds all the possible combinations. For example, we have
function combination(value){}
combination([1,0]); // DA, DB, DC, EA, EB, EC, FA, FB, FC
So in the above invocation the keys at "1" and "0" from the map object will be referenced and all the possible combinations for those two will be returned.
I think the best way to solve this would be to use a recursion, but i just can't seem to wrap my head around this

Not just recursion you can use multiple for loops to achieve this
for (var i = 0 ; i < mapping[index1].length ; i++)
{
for (var j = 0 ; j < mapping[index2].length ; j++)
{
resultArray.push(mapping[i][j]);
}
}
here resultArray is the output array, index1, index2 are the indexes passed

Close enough:
var mapping = {
0: ['A', 'B', 'C'],
1: ['D', 'E', 'F'],
2: ['G', 'H', 'I'],
9: ['X', 'Z']
}
function combine(array, index1, index2) {
var result = array[index1].map(function (el1) {
return array[index2].map(function (el2) {
return [el1, el2].join('');
})
});
return [].concat.apply([], result);
}
function combineAll(array, indices) {
var result = [];
for (var i = 0; i < indices.length - 1; i++) {
var index = indices[i];
var nextIndex = indices[i + 1];
result = result.concat(combine(array, index, nextIndex));
}
return result;
}
var combinations = combineAll(mapping, [1, 0, 9]);
console.log(combinations);
document.write(JSON.stringify(combinations, null, 2));

To find all possible combination we have to iterate over mapping object as well as array corresponding to given key, here i create two functions combination which will iterate over mapping object keys and map which will iterate of array corresponding to each given key:
var mapping = {
0: ['A','B','C'],
1: ['D','E','F'],
2: ['G', 'H', 'I'],
3: ['J', 'K', 'L'],
4: ['M', 'N', 'O']
}
var resultArray = [];
function combination(x){
for(var i=0;i<x.length;i++){
for(var j=i+1;j<x.length;j++){
map(x[i],x[j])
}
}
}
function map(index1,index2){
for (var i in mapping[index1])
{
if(mapping[index1].hasOwnProperty(i)){
for (var j in mapping[index2])
{
if(mapping[index2].hasOwnProperty(j)){
resultArray.push(mapping[index1][i]+''+mapping[index2][j]);
}
}
}
}
}
combination([0,2,1,4])
console.log(resultArray);

You can use recursion in order to solve this problem.
Simply iterate through the indices, and try to add every corresponding item recursively.
I have implemented a working JS function for you:
function combination(o) { // just a wrapper for convenient usage
var current = [];
function step() { // recursive function
if (current.length === o.indices.length) { // combination is ready
o.callback(current); // callback
return; // and leave recursion
}
// current.length is a current position (0, 1, 2, 3 etc.) for which we find a value
// o.indices[o.current.length] is an index of mapping for current position (0, 2, 2, 1 in a demo below)
// o.mapping[o.indices[o.current.length]] is simply a row
o.mapping[o.indices[current.length]].forEach(function(x) {
current.push(x);
step();
current.pop();
});
}
step(); // start recursion
}
You can use it this way:
combination({
mapping: { // your mapping
0: ['A', 'B', 'C'],
1: ['D', 'E', 'F'],
2: ['G', 'H', 'I']
},
indices: [0, 2, 2, 1], // indices may repeat
callback: function(x) { // callback will be called every time we find a combination
document.body.innerHTML += x + "<br/>"; // x is an array
}
});
Here is the working JSFiddle demo.

Following is a solution using recursion.
It does a DFS (Depth First Traversal ) to find all the paths of length corresponding to the input array.
For every dive to the next level, the array gets shortened using slice(1). The original array is not modified and the pattern repeats. When there are no more elements in the array, the path is printed.
I have added a count to see how many combinations are generated. The count should be a product of the lengths of all arrays in the initial input array.
That is for snippet with input array [2,0,3,7] , the product is 3 * 3 * 4 * 3 = 108, i.e 108 combinations.
Essentially picture a tree with the first level nodes with all mapping[2] elements, each of those have mapping[0] children making level 2 of the tree, each level 2 then has mapping[3] elements as children at level 3 and finally mapping[7] would make up the leaf nodes of the tree. This can go to any level based on your input array and your mapping configuration.
The code below does a recursive traversal of the above tree, each time going from root to each leaf node and prints out that path.
var mapping = {
0: ['0A','0B','0C'],
1: ['1A','1B','1C'],
2: ['2A','2B','2C'],
3: ['3A','3B','3C', '3D'],
4: ['4A','4B','4C'],
5: ['5A','5B','5C'],
6: ['6A','6B','6C'],
7: ['7A','7B','7C']
}
var count = 0;
function traverse(arr, comb) {
if(!arr.length) {
console.log(++count + " : " + comb+"\n");
return;
}
for(var j =0; j < mapping[arr[0]].length; j++)
traverse(arr.slice(1), comb + " " + mapping[arr[0]][j]);
}
traverse([2,0,3,7],"");

Related

Joining two ordered arrays, randomly mingle items, but keep original order

To make one array by randomly scattering items of two ordered arrays, while in the new array A is still before B, p before q, etc.
How to do it? I can use underscorejs or jquery.
[A,B,C,D]
[p,q,r,s]
==>
[p,q,A,r,B,C,D,s], or [A,B,p,C,q,r,s,D] or [A,p,q,B,r,C,s,D] or...
The following is not OK.
[B,p,A,...] or [p,A,r,...]
Your question contains the answer how to do it.
Take a random array.
Move the first element from it to the resulting array.
Repeat until all elements are moved.
One of possible solutions:
var a = ['A', 'B', 'C', 'D'], b = ['p', 'q', 'r', 's'], c = [];
while (a.length||b.length) {
var r = Math.random()+.5|0;
c.push([a, b][r].shift()||[a, b][+!r].shift());
}
console.log(JSON.stringify(c))
Vanilla js in the end.
var cap = ['A','B','C','D'];
var small = ['p','q','r','s'];
var arr = [];
var length = cap.length + small.length;
for(var i=0; i<length; i++) {
var rand = Math.random()-0.5;
var cond = rand > 0;
var pick;
if ( cap.length == 0 ) { pick = small; }
else if ( small.length == 0 ) { pick = cap; }
else { pick = cond ? cap : small; }
arr.push( pick.shift() );
}
With underscore, you can make use of sample for the general case (i.e. any number of arrays):
function mix(...arrays) {
var res = [];
while (_.some(arrays, a => a.length)) {
res.push(_.sample(_.filter(arrays, a => a.length)).shift());
}
return res;
}
console.log(mix(['A', 'B', 'C', 'D'], ['p', 'q', 'r', 's'], [1, 2, 3, 4, 5]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>

Why is my Javascript for loop not looping?

I'm trying to write a for loop that splits an array (parameter: arr) into sub-arrays of a given size (parameter: size), but it seems to be exiting the for loop early/not actually looping back in.
This code should return [['a', 'b'] ['c', 'd']], but right now is only returning [['a', 'b']].
I've tried researching but I can't pinpoint what in my code is stopping the loop from going back through the array.
function chunkArrayInGroups(arr, size) {
var newArr = [
[]
];
for (var i = 0; i < arr.length; i++) {
newArr[0].push(arr.shift(arr.slice(i, size)));
}
return newArr;
}
//calling the function:
console.log(chunkArrayInGroups(['a', 'b', 'c', 'd'], 2));
Please help me figure this out.
You could use a while loop and check the position against the length of the array.
function chunkArrayInGroups(array, size) {
var result = [],
i = 0;
while (i < array.length) {
result.push(array.slice(i, i += size));
}
return result;
}
console.log(chunkArrayInGroups(['a', 'b', 'c', 'd'], 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }
In the for loop increment i with size. On each iteration slice an array of length size (between i and i + size), and push it to newArr:
function chunkArrayInGroups(arr, size) {
var newArr = [];
for (var i = 0; i < arr.length; i += size) {
newArr.push(arr.slice(i, i + size));
}
return newArr;
}
//calling the function:
console.log(chunkArrayInGroups(['a', 'b', 'c', 'd'], 2));
You're modifying the array while you're looping over it, because arr.shift() removes the first element from the array and returns that. You're pushing that removed element onto newArr[0], not the slice.
So on the first iteration of the loop, arr is:
['a', 'b', 'c', 'd']
You push 'a' onto the result array, remove it from arr, and increment i.
On the next iteration, arr is
['b', 'c', 'd']
You push 'b' onto the result array, remove it from arr, and increment i.
On the next iteration, arr is
['c', 'd']
i is now 2, which is not less than arr.length, so the loop ends.
There's no reason to use arr.shift(), just use arr.slice() to get the chunks. You need to iterate by the chunk size, not 1. And the second argument to slice() is the end position (non-inclusive), not the size, so you need to add i + size.
function chunkArrayInGroups(arr, size) {
var newArr = [
[]
];
for (var i = 0; i < arr.length; i += size) {
newArr[0].push(arr.slice(i, i+size));
}
return newArr;
}
//calling the function:
console.log(chunkArrayInGroups(['a', 'b', 'c', 'd'], 2));

How to push elements to a 3D array (Javascript)

I have an object
1:a
2:b
3:c
4:d
5:e
6:f
7:g
8:h
9:i
I want to make a 3D array, like
[
[ [a], [b], [c] ],
[ [d], [e], [f] ],
[ [g], [h], [i] ]
]
Is it possible to write a for-loop to push first three letters to an 3D array, than second three letters and third at the end (like in example)?
You can first sort array of Object.keys() and then use reduce() to create new array by each 3 keys.
var obj = {1: 'a',2: 'b',3: 'c',4: 'd',5: 'e',6: 'f',7: 'g',8: 'h',9: 'i',}
var result = Object.keys(obj).sort((a, b) => a - b).reduce(function(r, e, i) {
if(i % 3 == 0) r.push([]);
r[r.length - 1].push([obj[e]])
return r;
}, [])
console.log(result)
As per #Andreas comment Does JavaScript Guarantee Object Property Order?.
properties order in objects is not guaranteed. So, if you need to rely on keyVal order a new step must be done: sorting on Object.keys.
A solution can be based looping on Object.keys and modulus operator in order to group by col or division for a row ordering:
var obj = {1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i'};
var resultByCol = [];
var resultByRow = [];
var objKeys = Object.keys(obj).sort(function(a, b) {
return a - b;
});
for (var idx = 0; idx < objKeys.length; idx++) {
var newIdx = idx % 3;
if (resultByCol[newIdx] === undefined) {
resultByCol.push([]);
}
resultByCol[newIdx].push([obj[objKeys[idx]]]);
newIdx = Math.floor(idx / 3);
if (resultByRow[newIdx] === undefined) {
resultByRow.push([]);
}
resultByRow[newIdx].push([obj[objKeys[idx]]]);
}
console.log('Grouped by Col: [');
for (var i = 0; i < resultByCol.length; i++) {
console.log('\t' + JSON.stringify(resultByCol[i]) + (((i + 1) == resultByCol.length) ? '' : ','));
}
console.log(']');
console.log('Grouped by Row: [');
for (i = 0; i < resultByRow.length; i++) {
console.log('\t' + JSON.stringify(resultByRow[i]) + (((i + 1) == resultByRow.length) ? '' : ','));
}
console.log(']');
This can be achieved very quickly and efficiently using keys and a simple for loop to keep your code expedient.
#gaetanoM's solution will work just fine, but for a more performant function you can do the following:
var object = {1:'a', 2:'b', 3:'c', 4:'d', 5:'e', 6:'f', 7:'g', 8:'h', 9:'i'};
var keys = Object.keys(object);
var result = [];
for ( var i=, j=keys.length; i<j; i++){
result[i] = object[keys[i]];
}
Yes. There are a number of ways to do it. Probably the cleanest looking would be to use the reduce() function:
const obj = { 1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i' };
const width = 3; // how many in each sub array.
const result = Object.keys(obj).map(key => obj[key]) // ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
.reduce((result, v) => {
if (result[result.length - 1].length === width) {
result.push([v]); // add new row
} else {
result[result.length - 1].push(v); // add to last row
}
return result;
}, [[]]);
console.log(result);
This is a little more verbose then it needs to be, but more understandable.
First, I loop through and map() the object to a normal array.
Then, in the reduce() function, I start it with the first level of nested arrays. Then on each loop, I check the length of the last array in result. If it's full, I add a new array for the next row. Otherwise, I just add the value to the last row.
A more compact version would be:
const obj = { 1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i' };
const width = 3;
const result = Object.keys(obj).reduce((r, k) =>
r.concat(
r[r.length - 1].length === width
? [[obj[k]]]
: [r.pop().concat(obj[k])]
), [[]]);
console.log(result);
This example makes more use of .concat() so everything is technically on one line, though obviously it's a good bit harder to read and understand.

Transforming arrays / objects: how can I create dynamically named objects within a for loop?

Trying to manipulate this:
input = [
[ ['a','b'], ['c','d'], ['e','f'] ],
[ ['g','h'], ['i','j'], ]
]
to
output = [
{a: 'b', c: 'd', e: 'f'},
{g: 'h', i: 'j'},
]
Here's what I have so far:
function transform(array) {
result = [];
for (var i=0; i<array.length; i++){
for (var j=0; j<array[i].length; j++){
// How can I create an object here?
object.array[i][j][0] = array[i][j][1];
}
}
return result;
}
I'm trying to solve this as a coding challenge so I don't necessarily want the answer but I'm unsure of how to proceed. Since the number of arrays that have a pair of strings inside is not uniform (for instance, first set of arrays within the input array has 3 sets, and the second set has 2 sets, I reckon I need to dynamically create objects within each loop that I can add to the results array at the end. How can I do this?
I don't think I'm supposed to use any sort of fancier / higher order functions. The goal is to build up my familiarity with the fundamentals.
You can use reduce to process the outer and inner arrays, e.g.
var input = [
[['a','b'], ['c','d'],['e','f'] ],
[['g','h'], ['i','j'],]
];
// For each outer array
var result = input.reduce(function(acc, a){
// Create an object from the inner arrays
acc.push(a.reduce(function(acc, a) {
acc[a[0]] = a[1];
return acc;
},{}));
return acc;
}, []);
console.log('With reduce\n');
console.log(result);
// Same algorithm using for loops:
var result2 = [];
// For each outer array
for (var i=0, iLen=input.length; i<iLen; i++) {
var acc = {};
var a = input[i];
// Loop over the inner arrays to build an object,
// then push into result array
for (var j=0, jLen=a.length; j<jLen; j++) {
var b = a[j]
acc[b[0]] = b[1];
}
result2.push(acc);
}
console.log('\nWith loops')
console.log(result2);
Input.reduce((memo,num) => {
memo[num[0]]=num[1];
return memo;
},{})
You can use nested for..of loops to iterate each inner array, create object, set property to element at index 0, value to element at index 1, push object to array at completion of nested for..of loop
let input = [
[
['a', 'b'],
['c', 'd'],
['e', 'f']
],
[
['g', 'h'],
['i', 'j']
]
];
let output = [];
for (let arr of input) {
let o = {};
for (let [key, value] of arr) {
o[key] = value;
}
output.push(o);
}
console.log(output);

Move an array element from one array position to another

I'm having a hard time figuring out how to move an element of an array. For example, given the following:
var array = [ 'a', 'b', 'c', 'd', 'e'];
How can I write a function to move the element 'd' to the left of 'b' ?
Or 'a' to the right of 'c'?
After moving the elements, the indexes of the rest of the elements should be updated. The resulting array would be:
array = ['a', 'd', 'b', 'c', 'e']
This seems like it should be pretty simple, but I can't wrap my head around it.
If you'd like a version on npm, array-move is the closest to this answer, although it's not the same implementation. See its usage section for more details. The previous version of this answer (that modified Array.prototype.move) can be found on npm at array.prototype.move.
I had fairly good success with this function:
function array_move(arr, old_index, new_index) {
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr; // for testing
};
// returns [2, 1, 3]
console.log(array_move([1, 2, 3], 0, 1));
Note that the last return is simply for testing purposes: splice performs operations on the array in-place, so a return is not necessary. By extension, this move is an in-place operation. If you want to avoid that and return a copy, use slice.
Stepping through the code:
If new_index is greater than the length of the array, we want (I presume) to pad the array properly with new undefineds. This little snippet handles this by pushing undefined on the array until we have the proper length.
Then, in arr.splice(old_index, 1)[0], we splice out the old element. splice returns the element that was spliced out, but it's in an array. In our above example, this was [1]. So we take the first index of that array to get the raw 1 there.
Then we use splice to insert this element in the new_index's place. Since we padded the array above if new_index > arr.length, it will probably appear in the right place, unless they've done something strange like pass in a negative number.
A fancier version to account for negative indices:
function array_move(arr, old_index, new_index) {
while (old_index < 0) {
old_index += arr.length;
}
while (new_index < 0) {
new_index += arr.length;
}
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr; // for testing purposes
};
// returns [1, 3, 2]
console.log(array_move([1, 2, 3], -1, -2));
Which should account for things like array_move([1, 2, 3], -1, -2) properly (move the last element to the second to last place). Result for that should be [1, 3, 2].
Either way, in your original question, you would do array_move(arr, 0, 2) for a after c. For d before b, you would do array_move(arr, 3, 1).
I like this way. It's concise and it works.
function arraymove(arr, fromIndex, toIndex) {
var element = arr[fromIndex];
arr.splice(fromIndex, 1);
arr.splice(toIndex, 0, element);
}
Note: always remember to check your array bounds.
Run Snippet in jsFiddle
Here's a one liner I found on JSPerf....
Array.prototype.move = function(from, to) {
this.splice(to, 0, this.splice(from, 1)[0]);
};
which is awesome to read, but if you want performance (in small data sets) try...
Array.prototype.move2 = function(pos1, pos2) {
// local variables
var i, tmp;
// cast input parameters to integers
pos1 = parseInt(pos1, 10);
pos2 = parseInt(pos2, 10);
// if positions are different and inside array
if (pos1 !== pos2 && 0 <= pos1 && pos1 <= this.length && 0 <= pos2 && pos2 <= this.length) {
// save element from position 1
tmp = this[pos1];
// move element down and shift other elements up
if (pos1 < pos2) {
for (i = pos1; i < pos2; i++) {
this[i] = this[i + 1];
}
}
// move element up and shift other elements down
else {
for (i = pos1; i > pos2; i--) {
this[i] = this[i - 1];
}
}
// put element from position 1 to destination
this[pos2] = tmp;
}
}
I can't take any credit, it should all go to Richard Scarrott. It beats the splice based method for smaller data sets in this performance test. It is however significantly slower on larger data sets as Darwayne points out.
The splice() method adds/removes items to/from an array, and returns the removed item(s).
Note: This method changes the original array. /w3schools/
Array.prototype.move = function(from,to){
this.splice(to,0,this.splice(from,1)[0]);
return this;
};
var arr = [ 'a', 'b', 'c', 'd', 'e'];
arr.move(3,1);//["a", "d", "b", "c", "e"]
var arr = [ 'a', 'b', 'c', 'd', 'e'];
arr.move(0,2);//["b", "c", "a", "d", "e"]
as the function is chainable this works too:
alert(arr.move(0,2).join(','));
demo here
My 2c. Easy to read, it works, it's fast, it doesn't create new arrays.
function move(array, from, to) {
if( to === from ) return array;
var target = array[from];
var increment = to < from ? -1 : 1;
for(var k = from; k != to; k += increment){
array[k] = array[k + increment];
}
array[to] = target;
return array;
}
Here is my one liner ES6 solution with an optional parameter on.
if (typeof Array.prototype.move === "undefined") {
Array.prototype.move = function(from, to, on = 1) {
this.splice(to, 0, ...this.splice(from, on))
}
}
Adaptation of the first solution proposed by digiguru
The parameter on is the number of element starting from from you want to move.
Here is a chainable variation of this:
if (typeof Array.prototype.move === "undefined") {
Array.prototype.move = function(from, to, on = 1) {
return this.splice(to, 0, ...this.splice(from, on)), this
}
}
[3, 4, 5, 1, 2].move(3, 0, 2) // => [1, 2, 3, 4, 5]
If you'd like to avoid prototype pollution, here's a stand-alone function:
function move(array, from, to, on = 1) {
return array.splice(to, 0, ...array.splice(from, on)), array
}
move([3, 4, 5, 1, 2], 3, 0, 2) // => [1, 2, 3, 4, 5]
And finally, here's a pure function that doesn't mutate the original array:
function moved(array, from, to, on = 1) {
return array = array.slice(), array.splice(to, 0, ...array.splice(from, on)), array
}
This should cover basically every variation seen in every other answer.
Got this idea from #Reid of pushing something in the place of the item that is supposed to be moved to keep the array size constant. That does simplify calculations. Also, pushing an empty object has the added benefits of being able to search for it uniquely later on. This works because two objects are not equal until they are referring to the same object.
({}) == ({}); // false
So here's the function which takes in the source array, and the source, destination indexes. You could add it to the Array.prototype if needed.
function moveObjectAtIndex(array, sourceIndex, destIndex) {
var placeholder = {};
// remove the object from its initial position and
// plant the placeholder object in its place to
// keep the array length constant
var objectToMove = array.splice(sourceIndex, 1, placeholder)[0];
// place the object in the desired position
array.splice(destIndex, 0, objectToMove);
// take out the temporary object
array.splice(array.indexOf(placeholder), 1);
}
This is based on #Reid's solution. Except:
I'm not changing the Array prototype.
Moving an item out of bounds to the right does not create undefined items, it just moves the item to the right-most position.
Function:
function move(array, oldIndex, newIndex) {
if (newIndex >= array.length) {
newIndex = array.length - 1;
}
array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]);
return array;
}
Unit tests:
describe('ArrayHelper', function () {
it('Move right', function () {
let array = [1, 2, 3];
arrayHelper.move(array, 0, 1);
assert.equal(array[0], 2);
assert.equal(array[1], 1);
assert.equal(array[2], 3);
})
it('Move left', function () {
let array = [1, 2, 3];
arrayHelper.move(array, 1, 0);
assert.equal(array[0], 2);
assert.equal(array[1], 1);
assert.equal(array[2], 3);
});
it('Move out of bounds to the left', function () {
let array = [1, 2, 3];
arrayHelper.move(array, 1, -2);
assert.equal(array[0], 2);
assert.equal(array[1], 1);
assert.equal(array[2], 3);
});
it('Move out of bounds to the right', function () {
let array = [1, 2, 3];
arrayHelper.move(array, 1, 4);
assert.equal(array[0], 1);
assert.equal(array[1], 3);
assert.equal(array[2], 2);
});
});
You can implement some basic calculus and create a universal function for moving array elements from one position to the other.
For JavaScript it looks like this:
function magicFunction (targetArray, indexFrom, indexTo) {
targetElement = targetArray[indexFrom];
magicIncrement = (indexTo - indexFrom) / Math.abs (indexTo - indexFrom);
for (Element = indexFrom; Element != indexTo; Element += magicIncrement){
targetArray[Element] = targetArray[Element + magicIncrement];
}
targetArray[indexTo] = targetElement;
}
Check out "moving array elements" at "Gloommatter" for detailed explanation.
https://web.archive.org/web/20121105042534/http://www.gloommatter.com:80/DDesign/programming/moving-any-array-elements-universal-function.html
I've implemented an immutable ECMAScript 6 solution based off of #Merc's answer over here:
const moveItemInArrayFromIndexToIndex = (array, fromIndex, toIndex) => {
if (fromIndex === toIndex) return array;
const newArray = [...array];
const target = newArray[fromIndex];
const inc = toIndex < fromIndex ? -1 : 1;
for (let i = fromIndex; i !== toIndex; i += inc) {
newArray[i] = newArray[i + inc];
}
newArray[toIndex] = target;
return newArray;
};
The variable names can be shortened, just used long ones so that the code can explain itself.
One approach would be to create a new array with the pieces in the order you want, using the slice method.
Example
var arr = [ 'a', 'b', 'c', 'd', 'e'];
var arr2 = arr.slice(0,1).concat( ['d'] ).concat( arr.slice(2,4) ).concat( arr.slice(4) );
arr.slice(0,1) gives you ['a']
arr.slice(2,4) gives you ['b', 'c']
arr.slice(4) gives you ['e']
Another pure JS variant using ES6 array spread operator with no mutation
const reorder = (array, sourceIndex, destinationIndex) => {
const smallerIndex = Math.min(sourceIndex, destinationIndex);
const largerIndex = Math.max(sourceIndex, destinationIndex);
return [
...array.slice(0, smallerIndex),
...(sourceIndex < destinationIndex
? array.slice(smallerIndex + 1, largerIndex + 1)
: []),
array[sourceIndex],
...(sourceIndex > destinationIndex
? array.slice(smallerIndex, largerIndex)
: []),
...array.slice(largerIndex + 1),
];
}
// returns ['a', 'c', 'd', 'e', 'b', 'f']
console.log(reorder(['a', 'b', 'c', 'd', 'e', 'f'], 1, 4))
The splice method of Array might help: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice
Just keep in mind it might be relatively expensive since it has to actively re-index the array.
I needed an immutable move method (one that didn't change the original array), so I adapted #Reid's accepted answer to simply use Object.assign to create a copy of the array before doing the splice.
Array.prototype.immutableMove = function (old_index, new_index) {
var copy = Object.assign([], this);
if (new_index >= copy.length) {
var k = new_index - copy.length;
while ((k--) + 1) {
copy.push(undefined);
}
}
copy.splice(new_index, 0, copy.splice(old_index, 1)[0]);
return copy;
};
Here is a jsfiddle showing it in action.
Array.prototype.moveUp = function (value, by) {
var index = this.indexOf(value),
newPos = index - (by || 1);
if (index === -1)
throw new Error("Element not found in array");
if (newPos < 0)
newPos = 0;
this.splice(index, 1);
this.splice(newPos, 0, value);
};
Array.prototype.moveDown = function (value, by) {
var index = this.indexOf(value),
newPos = index + (by || 1);
if (index === -1)
throw new Error("Element not found in array");
if (newPos >= this.length)
newPos = this.length;
this.splice(index, 1);
this.splice(newPos, 0, value);
};
var arr = ['banana', 'curyWurst', 'pc', 'remembaHaruMembaru'];
alert('withiout changes= '+arr[0]+' ||| '+arr[1]+' ||| '+arr[2]+' ||| '+arr[3]);
arr.moveDown(arr[2]);
alert('third word moved down= '+arr[0] + ' ||| ' + arr[1] + ' ||| ' + arr[2] + ' ||| ' + arr[3]);
arr.moveUp(arr[2]);
alert('third word moved up= '+arr[0] + ' ||| ' + arr[1] + ' ||| ' + arr[2] + ' ||| ' + arr[3]);
http://plnkr.co/edit/JaiAaO7FQcdPGPY6G337?p=preview
Here's one way to do it in an immutable way. It handles negative numbers as well as an added bonus. This is reduces number of possible bugs at the cost of performance compared to editing the original array.
const numbers = [1, 2, 3];
const moveElement = (array, from, to) => {
const copy = [...array];
const valueToMove = copy.splice(from, 1)[0];
copy.splice(to, 0, valueToMove);
return copy;
};
console.log(moveElement(numbers, 0, 2))
// > [2, 3, 1]
console.log(moveElement(numbers, -1, -3))
// > [3, 1, 2]
One approach would be to use splice() to remove the item from the array and then by using the splice() method once again, insert the removed item into the target index.
const array = ['a', 'b', 'c', 'd', 'e']
const newArray = moveItem(array, 3, 1) // move element from index 3 to index 1
function moveItem(arr, fromIndex, toIndex){
let itemRemoved = arr.splice(fromIndex, 1) // assign the removed item as an array
arr.splice(toIndex, 0, itemRemoved[0]) // insert itemRemoved into the target index
return arr
}
console.log(newArray)
In 2022, this typescript utility will work along with a unit test.
export const arrayMove = <T>(arr: T[], fromIndex: number, toIndex: number) => {
const newArr = [...arr];
newArr.splice(toIndex, 0, newArr.splice(fromIndex, 1)[0]);
return newArr;
};
const testArray = ['1', '2', '3', '4'];
describe('arrayMove', () => {
it('should move array item to toIndex', () => {
expect(arrayMove(testArray, 2, 0)).toEqual(['3', '1', '2', '4']);
expect(arrayMove(testArray, 3, 1)).toEqual(['1', '4', '2', '3']);
expect(arrayMove(testArray, 1, 2)).toEqual(['1', '3', '2', '4']);
expect(arrayMove(testArray, 0, 2)).toEqual(['2', '3', '1', '4']);
});
});
I love immutable, functional one liners :) ...
const swapIndex = (array, from, to) => (
from < to
? [...array.slice(0, from), ...array.slice(from + 1, to + 1), array[from], ...array.slice(to + 1)]
: [...array.slice(0, to), array[from], ...array.slice(to, from), ...array.slice(from + 1)]
);
Find and move an element from "n"th position to 0th position.
Eg: Find and move 'd' to 0th position:
let arr = [ 'a', 'b', 'c', 'd', 'e'];
arr = [...arr.filter(item => item === 'd'), ...arr.filter(item => item !== 'd')];
It is stated in many places (adding custom functions into Array.prototype) playing with the Array prototype could be a bad idea, anyway I combined the best from various posts, I came with this, using modern Javascript:
Object.defineProperty(Array.prototype, 'immutableMove', {
enumerable: false,
value: function (old_index, new_index) {
var copy = Object.assign([], this)
if (new_index >= copy.length) {
var k = new_index - copy.length;
while ((k--) + 1) { copy.push(undefined); }
}
copy.splice(new_index, 0, copy.splice(old_index, 1)[0]);
return copy
}
});
//how to use it
myArray=[0, 1, 2, 3, 4];
myArray=myArray.immutableMove(2, 4);
console.log(myArray);
//result: 0, 1, 3, 4, 2
Hope can be useful to anyone
This version isn't ideal for all purposes, and not everyone likes comma expressions, but here's a one-liner that's a pure expression, creating a fresh copy:
const move = (from, to, ...a) => (a.splice(to, 0, ...a.splice(from, 1)), a)
A slightly performance-improved version returns the input array if no move is needed, it's still OK for immutable use, as the array won't change, and it's still a pure expression:
const move = (from, to, ...a) =>
from === to
? a
: (a.splice(to, 0, ...a.splice(from, 1)), a)
The invocation of either is
const shuffled = move(fromIndex, toIndex, ...list)
i.e. it relies on spreading to generate a fresh copy. Using a fixed arity 3 move would jeopardize either the single expression property, or the non-destructive nature, or the performance benefit of splice. Again, it's more of an example that meets some criteria than a suggestion for production use.
const move = (from, to, ...a) =>from === to ? a : (a.splice(to, 0, ...a.splice(from, 1)), a);
const moved = move(0, 2, ...['a', 'b', 'c']);
console.log(moved)
I thought this was a swap problem but it's not. Here's my one-liner solution:
const move = (arr, from, to) => arr.map((item, i) => i === to ? arr[from] : (i >= Math.min(from, to) && i <= Math.max(from, to) ? arr[i + Math.sign(to - from)] : item));
Here's a small test:
let test = ['a', 'b', 'c', 'd', 'e'];
console.log(move(test, 0, 2)); // [ 'b', 'c', 'a', 'd', 'e' ]
console.log(move(test, 1, 3)); // [ 'a', 'c', 'd', 'b', 'e' ]
console.log(move(test, 2, 4)); // [ 'a', 'b', 'd', 'e', 'c' ]
console.log(move(test, 2, 0)); // [ 'c', 'a', 'b', 'd', 'e' ]
console.log(move(test, 3, 1)); // [ 'a', 'd', 'b', 'c', 'e' ]
console.log(move(test, 4, 2)); // [ 'a', 'b', 'e', 'c', 'd' ]
console.log(move(test, 4, 0)); // [ 'e', 'a', 'b', 'c', 'd' ]
This is a really simple method using splice
Array.prototype.moveToStart = function(index) {
this.splice(0, 0, this.splice(index, 1)[0]);
return this;
};
I ended up combining two of these to work a little better when moving both small and large distances. I get fairly consistent results, but this could probably be tweaked a little bit by someone smarter than me to work differently for different sizes, etc.
Using some of the other methods when moving objects small distances was significantly faster (x10) than using splice. This might change depending on the array lengths though, but it is true for large arrays.
function ArrayMove(array, from, to) {
if ( Math.abs(from - to) > 60) {
array.splice(to, 0, array.splice(from, 1)[0]);
} else {
// works better when we are not moving things very far
var target = array[from];
var inc = (to - from) / Math.abs(to - from);
var current = from;
for (; current != to; current += inc) {
array[current] = array[current + inc];
}
array[to] = target;
}
}
https://web.archive.org/web/20181026015711/https://jsperf.com/arraymove-many-sizes
TypeScript Version
Copied from #Merc's answer. I like that one best because it is not creating new arrays and modifies the array in place. All I did was update to ES6 and add the types.
export function moveItemInArray<T>(workArray: T[], fromIndex: number, toIndex: number): T[] {
if (toIndex === fromIndex) {
return workArray;
}
const target = workArray[fromIndex];
const increment = toIndex < fromIndex ? -1 : 1;
for (let k = fromIndex; k !== toIndex; k += increment) {
workArray[k] = workArray[k + increment];
}
workArray[toIndex] = target;
return workArray;
}
Array.move.js
Summary
Moves elements within an array, returning an array containing the moved elements.
Syntax
array.move(index, howMany, toIndex);
Parameters
index: Index at which to move elements. If negative, index will start from the end.
howMany: Number of elements to move from index.
toIndex: Index of the array at which to place the moved elements. If negative, toIndex will start from the end.
Usage
array = ["a", "b", "c", "d", "e", "f", "g"];
array.move(3, 2, 1); // returns ["d","e"]
array; // returns ["a", "d", "e", "b", "c", "f", "g"]
Polyfill
Array.prototype.move || Object.defineProperty(Array.prototype, "move", {
value: function (index, howMany, toIndex) {
var
array = this,
index = parseInt(index) || 0,
index = index < 0 ? array.length + index : index,
toIndex = parseInt(toIndex) || 0,
toIndex = toIndex < 0 ? array.length + toIndex : toIndex,
toIndex = toIndex <= index ? toIndex : toIndex <= index + howMany ? index : toIndex - howMany,
moved;
array.splice.apply(array, [toIndex, 0].concat(moved = array.splice(index, howMany)));
return moved;
}
});
I used the nice answer of #Reid, but struggled with moving an element from the end of an array one step further - to the beginning (like in a loop).
E.g. ['a', 'b', 'c'] should become ['c', 'a', 'b'] by calling .move(2,3)
I achieved this by changing the case for new_index >= this.length.
Array.prototype.move = function (old_index, new_index) {
console.log(old_index + " " + new_index);
while (old_index < 0) {
old_index += this.length;
}
while (new_index < 0) {
new_index += this.length;
}
if (new_index >= this.length) {
new_index = new_index % this.length;
}
this.splice(new_index, 0, this.splice(old_index, 1)[0]);
return this; // for testing purposes
};
As an addition to Reid's excellent answer (and because I cannot comment);
You can use modulo to make both negative indices and too large indices "roll over":
function array_move(arr, old_index, new_index) {
new_index =((new_index % arr.length) + arr.length) % arr.length;
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr; // for testing
}
// returns [2, 1, 3]
console.log(array_move([1, 2, 3], 0, 1));

Categories

Resources