我很难弄清楚如何移动数组的元素。例如,给定以下内容:
var array = [ 'a', 'b', 'c', 'd', 'e'];
如何编写函数将元素 'd'
移动到 'b'
的左侧?
还是 'c'
右侧的 'a'
?
移动元素后,应更新其余元素的索引。结果数组将是:
array = ['a', 'd', 'b', 'c', 'e']
这似乎应该很简单,但我无法理解它。
const changeValuePosition = (arr, init, target) => {[arr[init],arr[target]] = [arr[target],arr[init]]; return arr}
init
和 target
处的元素。
如果您想要 npm 上的版本,array-move 是最接近此答案的,尽管它不是相同的实现。有关更多详细信息,请参阅其用法部分。此答案的先前版本(修改后的 Array.prototype.move)可以在 npm 的 array.prototype.move 上找到。
我在这个功能上取得了相当大的成功:
函数 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]);返回 arr; // 用于检测 }; // 返回 [2, 1, 3] console.log(array_move([1, 2, 3], 0, 1));
请注意,最后一个 return
仅用于测试目的:splice
就地对数组执行操作,因此不需要返回。通过扩展,这个 move
是一个就地操作。如果您想避免这种情况并返回副本,请使用 slice
。
单步执行代码:
如果 new_index 大于数组的长度,我们希望(我假设)用新的未定义正确地填充数组。这个小片段通过在数组上推送 undefined 来处理这个问题,直到我们有适当的长度。然后,在 arr.splice(old_index, 1)[0] 中,我们拼接出旧元素。 splice 返回被拼接出来的元素,但它在一个数组中。在我们上面的例子中,这是 [1]。因此,我们采用该数组的第一个索引来获取原始 1。然后我们使用 splice 将这个元素插入到 new_index 的位置。因为如果 new_index > arr.length 我们填充了上面的数组,它可能会出现在正确的位置,除非他们做了一些奇怪的事情,比如传入一个负数。
一个更好的版本来解释负指数:
函数 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]);返回 arr; // 用于测试目的 }; // 返回 [1, 3, 2] console.log(array_move([1, 2, 3], -1, -2));
这应该正确考虑 array_move([1, 2, 3], -1, -2)
之类的事情(将最后一个元素移到倒数第二个位置)。结果应该是 [1, 3, 2]
。
无论哪种方式,在您最初的问题中,您都会在 c
之后为 a
执行 array_move(arr, 0, 2)
。对于 b
之前的 d
,您将执行 array_move(arr, 3, 1)
。
我喜欢这种方式。它简洁而且有效。
function arraymove(arr, fromIndex, toIndex) {
var element = arr[fromIndex];
arr.splice(fromIndex, 1);
arr.splice(toIndex, 0, element);
}
注意:永远记得检查你的数组边界。
这是我在 JSPerf 上找到的一个衬里......
Array.prototype.move = function(from, to) {
this.splice(to, 0, this.splice(from, 1)[0]);
};
读起来很棒,但是如果您想要性能(在小型数据集中),请尝试...
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;
}
}
我不能把功劳归于Richard Scarrott。在此 performance test 中,它击败了基于拼接的较小数据集的方法。然而,在较大的数据集 as Darwayne points out 上,它的速度要慢得多。
from >= to ? this.splice(to, 0, this.splice(from, 1)[0]) : this.splice(to - 1, 0, this.splice(from, 1)[0]);
splice() 方法在数组中添加/删除项目,并返回删除的项目。注意:此方法更改原始数组。 /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"]
因为函数是 chainable 这也有效:
alert(arr.move(0,2).join(','));
我的2c。易于阅读,有效,速度快,不会创建新数组。
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;
}
array
,就像它在结尾处所做的那样。
从@Reid 那里得到了这个想法,即在应该移动的项目的位置上推一些东西以保持数组大小不变。这确实简化了计算。此外,推送一个空对象还有一个额外的好处,那就是以后能够唯一地搜索它。这是有效的,因为两个对象在引用同一个对象之前是不相等的。
({}) == ({}); // false
所以这是接收源数组和源、目标索引的函数。如果需要,您可以将其添加到 Array.prototype 中。
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);
}
sourceIndex = 0
、destIndex = 1
destIndex
是源元素在数组中移动之前的索引。
这是我的一个线性 ES6 解决方案,带有一个可选参数 on
。
if (typeof Array.prototype.move === "undefined") {
Array.prototype.move = function(from, to, on = 1) {
this.splice(to, 0, ...this.splice(from, on))
}
}
改编digiguru
提出的第一个解决方案
参数 on
是要移动的从 from
开始的元素数。
这是一个可链接的变体:
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]
如果你想避免原型污染,这里有一个独立的函数:
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]
最后,这是一个不会改变原始数组的纯函数:
function moved(array, from, to, on = 1) {
return array = array.slice(), array.splice(to, 0, ...array.splice(from, on)), array
}
这应该基本上涵盖所有其他答案中看到的所有变化。
这是基于@Reid 的解决方案。除了:
我没有更改 Array 原型。
将项目向右移出边界不会创建未定义的项目,它只是将项目移动到最右边的位置。
功能:
function move(array, oldIndex, newIndex) {
if (newIndex >= array.length) {
newIndex = array.length - 1;
}
array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]);
return array;
}
单元测试:
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);
});
});
您可以实现一些基本的微积分并创建一个通用函数来将数组元素从一个位置移动到另一个位置。
对于 JavaScript,它看起来像这样:
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;
}
查看“Gloommatter”中的“移动数组元素”以获取详细说明。
我已经根据 @Merc
在此处的回答实施了一个不可变的 ECMAScript 6
解决方案:
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;
};
变量名可以缩短,只使用长的,这样代码就可以解释自己了。
fromIndex === toIndex
,为什么不立即返回 array
,如果不是这样,只创建 newArray
?不变性并不意味着每次函数调用都必须创建一个新副本,即使没有更改也是如此。只是询问 b/c 增加此函数长度(相对于基于拼接的单行)的动机是性能,并且 fromIndex
很可能经常等于 toIndex
,具体取决于使用情况。
一种方法是使用 slice 方法按您想要的顺序创建一个包含片段的新数组。
例子
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) 给你 ['a']
arr.slice(2,4) 给你 ['b', 'c']
arr.slice(4) 给你 ['e']
arr2
最终成为一个字符串,对吧? :) 它最终是 "adc,de"
。
Array
的 splice
方法可能会有所帮助:https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice
请记住,它可能相对昂贵,因为它必须主动重新索引数组。
另一个使用 ES6 数组扩展运算符的纯 JS 变体,没有突变
const reorder = (array, sourceIndex, destinationIndex) => { const smallIndex = Math.min(sourceIndex, destinationIndex); const largeIndex = Math.max(sourceIndex,destinationIndex); return [ ...array.slice(0, smallIndex), ...(sourceIndex
我需要一个不可变的移动方法(一个不改变原始数组的方法),所以我调整了@Reid 接受的答案,在拼接之前简单地使用 Object.assign 创建数组的副本。
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;
};
这是一个jsfiddle showing it in action。
这是以不可变的方式执行此操作的一种方法。它处理负数以及额外的奖励。与编辑原始数组相比,这以性能为代价减少了可能的错误数量。
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]
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
我喜欢不可变的、功能性的单衬:) ...
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)]
);
在很多地方(adding custom functions into Array.prototype)都说使用 Array 原型可能是个坏主意,无论如何我结合了各种帖子中最好的,我用现代 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
希望对任何人都有用
这个版本并不适合所有目的,也不是每个人都喜欢逗号表达式,但这里有一个纯表达式的单行,创建一个新鲜的副本:
const move = (from, to, ...a) => (a.splice(to, 0, ...a.splice(from, 1)), a)
如果不需要移动,稍微性能改进的版本返回输入数组,它仍然可以用于不可变使用,因为数组不会改变,它仍然是一个纯表达式:
const move = (from, to, ...a) =>
from === to
? a
: (a.splice(to, 0, ...a.splice(from, 1)), a)
任何一个的调用是
const shuffled = move(fromIndex, toIndex, ...list)
即它依赖于传播来生成一个新的副本。使用固定的 arity 3 move
将危及单个表达式属性、非破坏性性质或 splice
的性能优势。同样,它更像是一个满足某些标准的示例,而不是生产使用的建议。
const move = (from, to, ...a) =>from === to ? a : (a.splice(to, 0, ...a.splice(from, 1)), a);常量移动 = move(0, 2, ...['a', 'b', 'c']);控制台日志(移动)
我认为这是一个交换问题,但事实并非如此。这是我的单线解决方案:
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));
这是一个小测试:
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' ]
这是一个使用拼接的非常简单的方法
Array.prototype.moveToStart = function(index) {
this.splice(0, 0, this.splice(index, 1)[0]);
return this;
};
我最终将其中两个结合起来,以便在移动小距离和大距离时更好地工作。我得到了相当一致的结果,但这可能会被比我聪明的人稍微调整一下,以针对不同的尺寸等进行不同的工作。
在小距离移动物体时使用其他一些方法比使用拼接要快得多(x10)。虽然这可能会根据数组长度而改变,但对于大型数组来说确实如此。
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
一种方法是使用 splice()
从数组中移除项目,然后再次使用 splice()
方法,将移除的项目插入到目标索引。
const array = ['a', 'b', 'c', 'd', 'e'] const newArray = moveItem(array, 3, 1) // 将元素从索引 3 移动到索引 1 function moveItem(arr, fromIndex, toIndex){ let itemRemoved = arr.splice(fromIndex, 1) // 将移除的项目分配为数组 arr.splice(toIndex, 0, itemRemoved[0]) // 将 itemRemoved 插入目标索引 return arr } console .log(新数组)
Array.move.js
概括
移动数组中的元素,返回包含移动元素的数组。
句法
array.move(index, howMany, toIndex);
参数
index:移动元素的索引。如果为负,则索引将从末尾开始。
howMany:从索引移动的元素数。
toIndex:放置移动元素的数组的索引。如果为负,toIndex 将从末尾开始。
用法
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"]
填充物
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;
}
});
.move
看起来应该可以工作(我没有测试过),但您应该注意它不是任何标准的一部分。警告人们 polyfill/monkeypatched 函数可能会破坏一些假定所有可枚举都是他们的代码的人也很好。
我使用了不错的 answer of @Reid,但是在将一个元素从数组末尾移动一步 - 到开头(就像在 循环 中)时遇到了困难。例如 ['a', 'b', 'c'] 应该通过调用 .move(2,3) 变成 ['c', 'a', 'b']
我通过更改 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
};
作为 Reid's excellent answer 的补充(因为我无法评论);您可以使用模来使负索引和太大的索引都“翻转”:
函数 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]);返回 arr; // 用于测试 } // 返回 [2, 1, 3] console.log(array_move([1, 2, 3], 0, 1));
let ar = ['a', 'b', 'c', 'd'];
function change( old_array, old_index , new_index ){
return old_array.map(( item , index, array )=>{
if( index === old_index ) return array[ new_index ];
else if( index === new_index ) return array[ old_index ];
else return item;
});
}
let result = change( ar, 0, 1 );
console.log( result );
结果:
["b", "a", "c", "d"]
var ELEMS = ['a', 'b', 'c', 'd', 'e']; /* 源项目将被移除,它会被放置在目的地之后 */ function moveItemTo(sourceItem, destItem, elements) { var sourceIndex = elements.indexOf(sourceItem); var destIndex = elements.indexOf(destItem); if (sourceIndex >= -1 && destIndex > -1) { elements.splice(destIndex, 0, elements.splice(sourceIndex, 1)[0]); } 返回元素; } console.log('初始化:',ELEMS); var 结果 = moveItemTo('a', 'c', ELEMS); console.log('BeforeAfter:', 结果);
让 oldi, newi, arr; if(newi !== oldi) { 让 el = this.arr.splice(oldi, 1); if(newi > oldi && newi === (this.arr.length + 2)) { this.arr.push(""); } this.arr.splice(newi, 0, el); if(newi > oldi && newi === (this.arr.length + 2)) { this.arr.pop(); } }
不定期副业成功案例分享
.hasOwnProperty
检查,尤其是使用诸如 Prototype 和 MooTools 等修改原型的库时。无论如何,在这样一个相对有限的示例中,我并不觉得这是一个特别重要的问题,并且社区中对于原型修改是否是一个好主意存在很好的分歧。不过,通常情况下,迭代问题是最不值得关注的。if
块中使用this[new_index] = undefined;
。由于 Javascript 数组是稀疏的,这将扩展数组大小以包括 new_index 以使.splice
工作,但无需创建任何中间元素。