Given two arrays arr1 and arr2 that both have the same items, but are sorted differently, how to make a list of least number of item-move operations required to make arr1 match arr2?
The function / algorithm to do this should accept my two arrays as the only arguments, and return an array like this:
[
[1,5],
[3,0],
[7,2]
]
The above array would be interpreted as "Move item at index 1 to index 5, then move item at index 3 to index 0, and finally move item at index 7 to index 2."
By an item-move operation I mean the following:
function arrayMove(array, from, to) {
return array.splice(to, 0, array.splice(from, 1)[0]);
}
When an item is moved from index a to index b, items after index a "slide down" so the item that had index a + 1 now has index a, and when the item is added back at index b, the items that had an index >= b will slide up, so that the item that had index b would now have index b + 1.
Feel free to provide your algorithm in JS or pseudocode, any help appreciated.
This strikes me as related to the edit distance problem. Perhaps you could exploit the Wagner-Fischer algorithm.
Something like this perhaps?
Javascript
// swap two elements in an array by their indexes a and b and
// return an array of the swapped coordinates.
function swap(arr, a, b) {
// assign the value at index a to temp
var temp = arr[a];
// assign the value at index b to index a
arr[a] = arr[b];
// assign the value of temp to the value at index b
arr[b] = temp;
// coordinates of move
return [a, b];
}
// return an array of moved coordinates
function minMoves(arr1, arr2) {
// take a shallow copy of arr2 so that the original is not modified
arr2 = arr2.slice();
// apply a function against an accumulator (moves) for each value of
// the array (arr1) (from left-to-right)
return arr1.reduce(function (moves, item, index) {
// if the values of each array at the index are not the same
if (item !== arr2[index]) {
// swap the current indexed element of arr2 with the value of
// the correct element as indexed in arr1. Add the moved
// coordinates to the beginning of the accumulator
moves.unshift(swap(arr2, index, arr2.lastIndexOf(item)));
}
// return the accumulater for the next iteration
return moves;
}, []);
}
var before = [1, 5, 6, 3, 2, 4, 7, 8, 9, 0],
test = before.slice(),
after = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
moves = minMoves(before, after);
console.log('moves: ' + JSON.stringify(moves));
moves.forEach(function(move) {
swap(test, move[0], move[1]);
});
console.log('Should be ordered nicely: ' + JSON.stringify(test));
Output
moves: [[3,5],[2,5],[1,4]]
Should be ordered nicely: [1,2,3,4,5,6,7,8,9,0]
On jsFiddle
This is what I would do, it is not based on any research of algorithms that have been proven optimal.
And here is the code using your arrayMove method instead of swap
Javascript
function arrayMove(array, from, to) {
return array.splice(to, 0, array.splice(from, 1)[0]);
}
// return an array of moved coordinates
function minMoves(arr1, arr2) {
// take a shallow copy of arr2 so that the original is not modified
arr2 = arr2.slice();
// apply a function against an accumulator (moves) for each value of
// the array (arr1) (from left-to-right)
return arr1.reduce(function (moves, item, index) {
var last;
// if the values of each array at the index are not the same
if (item !== arr2[index]) {
// swap the current indexed element of arr2 with the value of
// the correct element as indexed in arr1. Add the moved
// coordinates to the beginning of the accumulator
last = arr2.lastIndexOf(item);
arrayMove(arr2, last, index);
moves.unshift([index, last]);
}
// return the accumulater for the next iteration
return moves;
}, []);
}
var before = [1, 5, 6, 3, 2, 4, 7, 8, 9, 0],
test = before.slice(),
after = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
moves = minMoves(before, after);
console.log('moves: ' + JSON.stringify(moves));
moves.forEach(function(move) {
arrayMove(test, move[0], move[1]);
});
console.log('Should be ordered nicely: ' + JSON.stringify(test));
Output
moves: [[3,4],[2,5],[1,4]]
Should be ordered nicely: [1,2,3,4,5,6,7,8,9,0]
On jsFiddle
Finally a jsPerf to compare the two methods.
Related
Given an array of integers, where the values should be sorted in the following order:
if we have an array
[1, -1, -3, 9, -2, -5, 4, 8,]
we must rearrange it this way: largest number, smallest number, 2nd largest number, 2nd smallest number, ...
[9, -5, 8, -3, 4, -2, 1, -1 ]
I get the first largest and smallest numbers, but can't figure out how to make it dynamic for all values in the array.
I know that I must take two variables, say firstSmallest and firstLargest and point them to the first and last index of the array respectively, run a loop, which I do already in the code below, and store value into new array by incrementing firstSmallest and decrementing firstLargest, but couldn't implement into code.
let unsortedArr = [1, 5, 8 , 7, 6, -1, -5, 4, 9, 5]
let output = [];
function meanderArray(unsorted){
let sorted = unsorted.sort((a, b) => a-b);
let firstSmallest = sorted[0];
let firstLargest = sorted[unsorted.length-1];
for(let i = 0; i <= sorted.length; i++){
//I should increment firstSmallest and decrement firstLargest numbers and store in output
}
return output;
}
meanderArray(unsortedArr);
console.log(output);
You could take a toggle object which takes the property of either the first item or last from an array and iterate until no more items are available.
function meanderArray([...array]) {
const
result = [],
toggle = { shift: 'pop', pop: 'shift' };
let fn = 'shift';
array.sort((a, b) => a - b);
while (array.length) result.push(array[fn = toggle[fn]]());
return result;
}
console.log(...meanderArray([1, 5, 8, 7, 6, -1, -5, 4, 9, 5]));
You can sort an array by descending, then logic is the following: take first from start and first from end, then second from start-second from end, etc.
let unsortedArr = [1, 5, 8 , 7, 6, -1, -5, 4, 9, 5]
let output = [];
function meanderArray(unsorted){
let sorted = unsorted.sort((a, b) => b-a);
let output = []
for(let i = 0; i < sorted.length/2; i++){
output.push(sorted[i])
if(i !== sorted.length - 1 - i){
output.push(sorted[sorted.length - 1 - i])
}
}
return output;
}
let result = meanderArray(unsortedArr);
console.log(result);
You can sort, then loop and extract the last number with pop() and extract the first number with shift().
let unsortedArr = [1, -1, -3, 9, -2, -5, 4, 8,]
let output = [];
function meanderArray(unsorted){
let sorted = unsorted.sort((a, b) => a - b);
for(let i = 0; i < unsortedArr.length + 2; i++){
output.push(sorted.pop());
output.push(sorted.shift());
}
console.log(output);
return output;
}
meanderArray(unsortedArr);
Fastest Meandering Array method among all solutions mentioned above.
According to the JSBench.me, this solution is the fastest and for your reference i have attached a screenshot below.
I got a different approach, but i found that was very close to one of above answers from elvira.genkel.
In my solution for Meandering Array, First I sorted the given array and then i tried to find the middle of the array. After that i divided sorted array in to two arrays, which are indices from 0 to middle index and other one is from middle index to full length of sorted array.
We need to make sure that first half of array's length is greater than the second array. Other wise when applying for() loop as next step newly created array will contains some undefined values. For avoiding this issue i have incremented first array length by one.
So, always it should be firstArr.length > secondArr.length.
And planned to create new array with values in meandering order. As next step I created for() loop and try to push values from beginning of the first array and from end of the second array. Make sure that dynamically created index of second array will receive only zero or positive index. Other wise you can find undefined values inside newly created Meandering Array.
Hope this solution will be helpful for everyone, who loves to do high performance coding :)
Your comments and suggestions are welcome.
const unsorted = [1, 5, 8, 7, 6, -1, -5, 4, 9, 5];
const sorted = unsorted.sort((a,b)=>a-b).reverse();
const half = Math.round(Math.floor(sorted.length/2)) + 1;
const leftArr = sorted.slice(0, half);
const rightArr = sorted.slice(half, sorted.length);
const newArr = [];
for(let i=0; i<leftArr.length; i++) {
newArr.push(leftArr[i]);
if (rightArr.length-1-i >= 0) {
newArr.push(rightArr[rightArr.length-1-i]);
}
}
I'm a newbie to all of this and trying to improve myself by solving problems and challenges.
I came across a problem whereby I have an unordered array which contains 8 integers.
eg [2,3,1,4,6,5,8,7]
I need to sort it [1,2,3,4,5,6,7,8] and reorder the array so that the array starts with the end value and then the first value and so on eg [8,1,7,2,6,3,5,4,]
I worked out I could use map() to iterate across the array and then use push() with pop() and shift() however it leaves the last 2 numbers behind in the original array and I'm not sure why. I got around this by using a concat and a reverse but I still don't understand why pop and shift don't bring across all the elements.
Code below that doesn't pull all the elements:
const reorder = (array) => {
let store = []
array.sort((a, b) => a - b).map((item, i) => {
if (array) {
store.push(array.pop())
store.push(array.shift())
}
})
return store
}
reorder([2, 3, 1, 4, 6, 5, 8, 7]) // returns [8,1,7,2,6,3]
Code that works but I have to add a concat and a reverse:
const reorder = (array) => {
let store = []
array.sort((a, b) => a - b).map((item, i) => {
if (array) {
store.push(array.pop())
store.push(array.shift())
}
})
return store.concat(array.reverse())
}
reorder([2, 3, 1, 4, 6, 5, 8, 7]) //returns [8,1,7,2,6,3,5,4]
Thanks for any help
I would just bisect the array, sort them in opposite orders and then add each element from each array to a new array
Given that you want to then take the sorted bisected arrays and produce another single array, I'd then use Array.prototype.reduce:
const alternatingSort = function (array) {
array = array.sort();
const midpoint = Math.round(array.length / 2)
let arr1 = array.slice(0, midpoint);
let arr2 = array.slice(midpoint);
arr2 = arr2.sort(function (a, b) { return b - a });
return arr1.reduce(function (retVal, item, index) {
arr2[index] && retVal.push(arr2[index]);
retVal.push(item);
return retVal;
}, []);
}
console.log(alternatingSort([2, 3, 1, 4, 6, 5, 8, 7]));
console.log(alternatingSort([2, 3, 1, 4, 6, 5, 8])); // with odd number
As I've seen nobody explained why the original OP solution doesn't work, Here is why:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/
Map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values (including undefined).
It is not called for missing elements of the array; that is:
1.Indexes that have never been set;
2.which have been deleted; or
3.which have never been assigned a value.
So what is happening in our code is that:
On the first iteration,
[(2), 3, 1, 4, 6, 5, 8, 7]
Map picks the first element(2) in the array, and delete the first and last characters in the array, so the array becomes
[3,(1), 4, 6, 5, 8]
Now, as map will not consider deleted elements, the second element(1) in the current array is called, also the first and last element in also removed:
[1, 4,(6), 5]
Now, map is trying to find the third element(6), and delete the first and last element:
[4,6]
Now, map is trying to find the fourth element, which is out of bound, so the map function will terminate.
So, you are strongly advised not to use Array.prototype.shift or Array.prototype.pop in Array.prototype.map.
You can do it following way:
const reorder = (array) => {
array.sort((a, b) => a - b);
const result = [];
const length = array.length;
for (let i = 0; i < length; i++) {
if (i % 2 === 0) {
result.push(array.pop());
} else {
result.push(array.shift());
}
}
return result;
}
const result = reorder([2, 3, 1, 4, 6, 5, 7]);
console.log(result);
Notice that I've intentionally made the array length to be an odd number. Some of the solutions here will break if the length is an odd number.
Personally I would sort, split in half and then just insert in. Not very fancy, but gets the job done.
function strangeWeave (arr) {
var sorted = arr.slice().sort()
var result = sorted.splice(0,Math.floor(sorted.length/2))
for (let i=0;sorted.length;i+=2) {
result.splice(i,0,sorted.pop())
}
return result
}
console.log(strangeWeave([1,2]))
console.log(strangeWeave([1,2,3]))
console.log(strangeWeave([1,2,3,4,5,6,7,8]))
console.log(strangeWeave([1,2,3,4,5,6,7,8,9]))
There is a much easier solution to sort two different arrays, one normal and one in reverse, then connect them together. Here is the code for that:
var myArray = [1, 3, 2, 4, 5, 7, 6, 8];
function getCopy(arr) {
var x = [];
for(var i = 0; i < arr.length; i++)
x.push(arr[i]);
return x;
}
function sortMyWay(arr) {
var sortedArr = [],
leftSide = getCopy(arr).sort()
.splice(0, Math.ceil(arr.length / 2)),
rightSide = getCopy(arr).sort().reverse()
.splice(0, Math.floor(arr.length / 2));
for(var i = 0; i < arr.length; i++)
i % 2
? sortedArr.push(leftSide[Math.floor(i / 2)])
: sortedArr.push(rightSide[Math.floor(i / 2)]);
console.log(sortedArr);
return sortedArr;
}
var sortedArr = sortMyWay(myArray);
Hope it helped!
Happy coding :)
I need to create some sort of pattern of skips that I will set onto an array with the ability to choose which item of the array will be the first item of this "pattern" of skips and then return the items chosen by the pattern.
For example, I need to jump 2 2 1 2 in this arr = [1,2,3,4,5,6,7,8,9] so if its the second item (2) it will return [2,4,6,7,9] does anybody knows a way in JS to do this??
Try it
var original = [1,2,3,4,5,6,7,8,9];
var pattern = [2,2,1,2];
console.log(cutArray(original, pattern));
function cutArray(originalArray, jumpPatternArray){
for(var i = 0; jumpPatternArray.length > i; i++)
originalArray.splice(i, jumpPatternArray[i] - 1);
return originalArray;
}
Arrays are usually zero-indexed so saying the start index is two corresponds to the second element in the array is a bit odd, but something like this should get you started.
function skip (start, pattern, arr)
{
let idx = 0,
res = [ ];
pattern.unshift (start == 0 ? start : start - 1); // A bit odd but to accomodate the 1st index in the array being called the second element. You can just make this pattern.unshift (start); if you want to do it normally :)
for (let i of pattern) {
idx += i;
res.push (arr [idx]);
}
return res;
}
will give:
=> skip (2, [ 2, 2, 1, 2 ], [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]);
[ 2, 4, 6, 7, 9 ]
I have multiple arrays of indexes, representing an order:
[[0,1,2], [2,0,1], [1,2,0], [0,1,2], ...]
I need to construct a new array of indexes with a length equal to the total number of indexes in the input while ordering the values using the position indicated in each array in the input.
This would be the output of the input above:
[0, 1, 2, 5, 3, 4, 7, 8, 6, 9, 10, 11, ...]
The total length is 12, so the list of indexes will contain 0-11
The first array of the input is 0, 1, 2 so the output starts with 0, 1, 2
The second array of the input is 2, 0, 1 and the next 3 indexes in the new list are 3-5. ordering these using the second array of the input results in 5,3,4
And so on...
You could use Array#reduce, Array#forEach and the length of the actual array for the count. Then push the sum of all length until now and the value of inner array to the result set.
var array = [[0, 1, 2], [2, 0, 1], [1, 2, 0], [0, 1, 2]],
result = [];
array.reduce(function (r, a) {
a.forEach(function (b) {
result.push(r + b);
});
return r + a.length;
}, 0);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm confused by your example, but it sounds like you want to flatten your array by one level. You can do so like this:
var arrays = [[0,1,2], [2,0,1], [1,2,0], [0,1,2]];
var result = [].concat.apply([], arrays);
console.log(result);
If you're using a library like Underscore, there are built-in methods to do that, like the flatten method.
The input and output examples are confusing, but I think what you want it this:
var array = [...] // your array
var result = Array.prototype.concat.apply([], array);
I believe that I interpreted your question based on the expected output, and edited the question accordingly.
You'll need to loop over the sub-arrays in the input and get the next set of indexes for the output, then add those to the output using the order of the sub-array.
This seems to do the trick:
var input = [[0, 1, 2], [2, 0, 1], [1, 2, 0], [0, 1, 2]];
var output = [];
// add values to the output for each of the inputs
$.each(input, function (index, value) {
// get the next and last values to add
var startIndex = output.length;
var lastIndex = (startIndex + value.length) - 1;
// make an array of sequential values from startIndex to lastIndex
var arrayToAdd = [];
for (var i = startIndex; i <= lastIndex; i++) {
arrayToAdd.push(i);
}
// add the values from arrayToAdd in the order of the indexes in the input
$.each(value, function (innerindex, innerindexvalue) {
output.push(arrayToAdd[innerindexvalue]);
});
});
console.log(output);
I have an array of objects. I want to move a selected object to the last position in the array. How do I do this in javascript or jquery?
Here is some code I have:
var sortedProductRow = this.product_row;
for (var s in sortedProductRow) {
if (sortedProductRow[s]["parent_product_type"] != "")
// Move this object to last position in the array
}
I'm looping through this with a for loop, and I want the output to be ordered so that all objects that does not have a "parent_product_type" value comes first, then those with a value.
to move an element (of which you know the index) to the end of an array, do this:
array.push(array.splice(index, 1)[0]);
If you don't have the index, and only the element, then do this:
array.push(array.splice(array.indexOf(element), 1)[0]);
Example:
var arr = [1, 2, 6, 3, 4, 5];
arr.push(arr.splice(arr.indexOf(6), 1)[0]);
console.log(arr); // [1, 2, 3, 4, 5, 6]
NOTE:
this only works with Arrays (created with the [ ... ] syntax or
Array()) not with Objects (created with the { ... } syntax or
Object())
Moving the first element of an array to the end of the same array
var a = [5,1,2,3,4];
a.push(a.shift());
console.log(a); // [1,2,3,4,5]
or this way
var a = [5,1,2,3,4];
var b = a.shift();
a[a.length] = b;
console.log(a); // [1,2,3,4,5]
Moving any element of an array to any position in the same array
// move element '5' (index = 2) to the end (index = 4)
var a = [1, 2, 5, 4, 3];
a.splice(4,0,a.splice(2,1)[0]);
console.log(a); // [1, 2, 4, 3, 5]
or it could be converted to a prototype as well, like this where x represents the current position of element while y represents the new position in array
var a = [1, 2, 5, 4, 3];
Array.prototype.move = function(x, y){
this.splice(y, 0, this.splice(x, 1)[0]);
return this;
};
a.move(2,4);
console.log(a); // ["1", "2", "4", "3", "5"]
Answer to the #jkalandarov comment
function moveToTheEnd(arr, word){
arr.map((elem, index) => {
if(elem.toLowerCase() === word.toLowerCase()){
arr.splice(index, 1);
arr.push(elem);
}
})
return arr;
}
console.log(moveToTheEnd(["Banana", "Orange", "Apple", "Mango", "Lemon"],"Orange"));
This is more clean, without using the array index of [0]
const colors = ['white', 'black', 'red', 'blue', 'green'];
// will push the blue to the end of the array
colors.push(colors.splice(colors.indexOf('blue'), 1).pop());
console.debug(colors);
// ["white", "black", "red", "green", "blue"]
Using an anonymous function you can pass in the array and the value to filter by.
let concatToEnd = function (arr, val) {
return arr.filter(function(x) {
return x !== val; // filter items not equal to value
}).concat(arr.filter(function(x) { // concatonate to filtered array
return x === val; // filter items equal to value
})
);
}
// invoke
concatToEnd(array, 'parent_product_type');
You could probably shorten this further:
let concatToEnd = (arr,val) => arr.filter(x => x !== val).concat(arr.filter(x => x === val))
This function filters the items which do not equal the value passed in, then concatenates the result (to the end of the filtered array) of another filter function which filters out the items which do equal the value you've passed in.
This function essentially separates the array into 2 filtered parts and then concatenates them back together
This hasn't been tested for your use-case, but I've used something similar to move all numbers of an array to the end of the index.
Immutable way:
const changedArr = [...prevArr.filter(a => a !== element), element]
Move any element to last position - for lodash users:
const array = ['A', 'B', 'C', 'D'] // output: A, B, C, D
// finds index of value 'B' and removes it
_.pull(array , 'B') // output: A, C, D
// adds value of 'B' to last position
_.concat(array , 'B') // output: A, C, D, B
You can move any number of items by splicing them, then spreading them into a push.
const numberOfItems = 3;
let items = [1,2,3,4,5,6,7,8,9];
items.push(...items.splice(0, itemsToMove))
Moving all items equal so something to the end can also be done by concat.
const myArray = [1, 0, 3, 5, 0, 'a', 3, 's']
const moveZerosToEnd = (arr) => {
return arr.filter(item => item !== 0).concat(arr.filter(item => item === 0))
}
console.log(myArray) // [1, 0, 3, 5, 0, 'a', 3, 's']
console.log(moveZerosToEnd(myArray)) // [1, 3, 5, 'a', 3, 's', 0, 0]