将数组元素从一个数组位置移动到另一个数组位置

Mar*_*own 464 javascript arrays

我很难搞清楚如何移动数组元素.例如,给出以下内容:

var arr = [ 'a', 'b', 'c', 'd', 'e'];
Run Code Online (Sandbox Code Playgroud)

我怎么能写一个'd'以前移动的函数'b'

还是'a'之后'c'

移动后,应更新其余元素的索引.这意味着在第一个例子中,移动arr [0]将='a',arr [1] ='d'arr [2] ='b',arr [3] ='c',arr [4] = 'E'

这看起来应该很简单,但我无法绕过它.

Rei*_*eid 630

如果您想在npm上使用某个版本,则array-move最接近此答案,尽管它的实现并不相同.有关详细信息,请参阅其用法部分.可以在array.prototype.move的 npm上找到此答案的先前版本(修改后的Array.prototype.move).


我在这个功能上取得了相当不错的成功:

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)); 
Run Code Online (Sandbox Code Playgroud)

请注意,最后一个return仅用于测试目的:splice就地对阵列执行操作,因此不需要返回.通过扩展,这move是一个就地操作.如果您想避免这种情况并返回副本,请使用slice.

单步执行代码:

  1. 如果new_index大于数组的长度,我们希望(我推测)用新的undefineds 正确填充数组.这个小片段通过推动undefined阵列来处理这个问题,直到我们有适当的长度.
  2. 然后,在arr.splice(old_index, 1)[0],我们拼出旧元素.splice返回拼接出来的元素,但它在一个数组中.在上面的例子中,这是[1].所以我们采用该数组的第一个索引来获取原始数据1.
  3. 然后我们用splice这个元素插入new_index的位置.因为我们填充了上面的数组,如果new_index > arr.length它,它可能会出现在正确的位置,除非他们做了一些奇怪的事情,如传递负数.

一个代表负面指数的发烧友版本:

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));
Run Code Online (Sandbox Code Playgroud)

哪个应该考虑到array_move([1, 2, 3], -1, -2)正确的事情(将最后一个元素移动到倒数第二个位置).结果应该是[1, 3, 2].

无论哪种方式,在你原来的问题,你会怎么做array_move(arr, 0, 2)a之后c.对于d之前b,你会怎么做array_move(arr, 3, 1).

  • 这很完美!你的解释很清楚.感谢您花时间写这篇文章. (18认同)
  • 您不应该操纵对象和数组原型,它会在迭代元素时导致问题. (15认同)
  • @burakemre:我认为结论并没有那么清楚.大多数优秀的JS程序员(以及大多数流行的库)在使用for..in之类的东西时都会使用`.hasOwnProperty`检查,特别是像Prototype和MooTools等修改原型的库.无论如何,在这样一个相对有限的例子中,我并不觉得这是一个特别重要的问题,社区中对于原型修改是否是一个好主意存在很好的分歧.通常,迭代问题是最不重要的问题. (9认同)
  • 在步骤1中不需要循环,只需在`if`块中使用`this [new_index] = undefined;`即可.由于Javascript数组是稀疏的,这将扩展数组大小以包含`.splice`的new_index,但不需要创建任何干预元素. (3认同)
  • @Michael:好点 - 但是`this [new_index] = undefined`实际上会在*正确的索引之前在数​​组槽*中放置一个`undefined`.(例如,`[1,2,3] .move(0,10)`将在插槽10中具有"1"而在插槽9中将具有"未定义".)相反,如果稀疏性正常,我们可以执行`this [new_index ] = this.splice(old_index,1)[0]`没有其他拼接调用(将其改为if/else). (3认同)
  • 请不要将其添加到原型中.当TC39想要将它原生地添加到JavaScript时,由于人们这样做,他们将不得不使用不同的,尴尬的名称. (2认同)
  • 我已经更新了帖子,以便它不会修改数组原型。回想起来,我认识到修改原型在 2011 年充其量是摇摇欲坠的,现在我们是 2018 年,是时候更改帖子了。 (2认同)
  • 实际上我在这里看到一个问题:如果我调用 `array_move([0, 1, 2, 3, 4], 3, 1)`,那么索引为 `3` 的项目将移动到索引为 ` 的项目之前的位置1`,工作正常。然而,通过调用 `array_move([0, 1, 2, 3, 4], 1, 3)` 我期望相同的行为 - 索引为 `1` 的项目移动到索引为 `3` 的项目之前 - 所以预期结果是“[0, 2, 1, 3, 4]”,但事实并非如此。正确的代码应该是 `arr.splice(new_index &gt; old_index ? new_index - 1 : new_index, 0, arr.splice(old_index, 1)[0]);` (2认同)

dig*_*uru 252

这是我在JSPerf上发现的一个内容....

Array.prototype.move = function(from, to) {
    this.splice(to, 0, this.splice(from, 1)[0]);
};
Run Code Online (Sandbox Code Playgroud)

这是很棒的阅读,但如果你想要性能(在小数据集中)尝试...

 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;
    }
  }
Run Code Online (Sandbox Code Playgroud)

我不能相信,它应该全部归Richard Scarrott所有.它在此性能测试中击败了基于拼接的方法,用于较小的数据集.然而Darwayne指出,在较大的数据集上,它会明显变慢.

  • 这似乎是一个非常愚蠢的交易.小数据集的性能可以忽略不计,但大数据集的丢失是一个重大损失.您的净交换是负面的. (36认同)
  • 永远不要修改内置原型.http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/ (11认同)
  • @Reid这不是一个要求.IMO可以假设数组的长度没有被修改. (3认同)
  • 一行解决方案需要处理两种情况:`from &gt;= to ? this.splice(to, 0, this.splice(from, 1)[0]) : this.splice(to - 1, 0, this.splice(from, 1)[0]);` (3认同)
  • 在大型数据集上,性能更高的解决方案更慢.http://jsperf.com/array-prototype-move/8 (2认同)
  • 不要在原型中添加东西,伙计们 (2认同)

Ste*_*low 184

我喜欢这种方式.它有效,简洁而优雅.

function arraymove(arr, fromIndex, toIndex) {
    var element = arr[fromIndex];
    arr.splice(fromIndex, 1);
    arr.splice(toIndex, 0, element);
}
Run Code Online (Sandbox Code Playgroud)

注意:请记住检查数组边界.

这是一个测试的jsFiddle:https://jsfiddle.net/aq9Laaew/286055/

  • 我个人更喜欢3行代码.它更容易理解:获取元素的副本; 从阵列中删除它; 将其插入新位置.一个衬垫较短但不太清楚,以便其他人理解...... (34认同)
  • 由于Array.splice在新数组中返回已删除的值,因此您可以将其写为单行... arr.splice(index + 1,0,arr.splice(index,1)[0]); (24认同)
  • 简短的代码。但现在是 2019 年了!!,创建数组的克隆并返回它,而不是改变数组。这将使您的函数“arraymove”符合函数式编程标准 (5认同)
  • 现在是 2021 年。在某些情况下,由于内存/性能原因,复制是完全不合适的。纯函数应该是默认值,但它不应该成为教条。 (5认同)
  • 从&gt;到&gt;时,toIndex将不正确 (4认同)
  • 我从未想象过,对于某些人来说,2019 年之后,原地变异数组会变得过时。完全合法的答案,+1。 (4认同)
  • 2022 年在这里 - 最近不得不在 javascript 中处理大量数据集,我现在可以告诉你,克隆以进行简单的更改或使用数组函数根本不是一个好主意。我讨厌这种“拥有”让一切都一成不变的心态。Javascript 并不是被设计成不可变的,如果是的话,以这种方式编程会很有效。 (4认同)
  • @SamwellTarly 1979-2022:一种尺寸并不适合所有人;课程的马——OP 没有指定需要一个纯函数,或者他正在使用一个其大小使其实用的数据集。 (4认同)
  • 关心解释为什么不呢? (3认同)
  • 每个人都在谈论不改变数组,但没有人注意到如果“toIndex”大于“fromIndex”,则需要递减? (3认同)
  • 函数式编程在许多情况下都很棒,但并非所有情况都需要、也不必遵守函数式编程标准。如果你是一个纯函数战士,这在本地仍然有用。这取决于你的函数方法的细粒度。 (2认同)

小智 33

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"]
Run Code Online (Sandbox Code Playgroud)

由于该功能是可链接的,因此也适用:

alert(arr.move(0,2).join(','));
Run Code Online (Sandbox Code Playgroud)

在这里演示

  • 请参阅关于此的其他评论:修改诸如 Array 和 Object 之类的内置原型是一个坏主意。你会破坏东西。 (3认同)

Mer*_*erc 26

我的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;
}
Run Code Online (Sandbox Code Playgroud)

  • 真的我怎么想念那个?固定! (3认同)
  • 在函数的第一个字符串中,您应该返回“ array”,就像最后那样。 (2认同)

Anu*_*rag 16

从@Reid那里得到了这个想法,即在应该移动的项目的位置推送一些东西来保持数组大小不变.这确实简化了计算.此外,推送空对象还具有以后能够唯一地搜索它的好处.这是有效的,因为两个对象在引用同一对象之前不相等.

({}) == ({}); // false
Run Code Online (Sandbox Code Playgroud)

所以这里是接收源数组的函数,以及源,目标索引.如果需要,您可以将它添加到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);
}
Run Code Online (Sandbox Code Playgroud)


And*_*ena 15

这是基于@ Reid的解决方案.除了:

  • 我没有改变Array原型.
  • 将项目移出边界不会创建undefined项目,它只是将项目移动到最右侧的位置.

功能:

function move(array, oldIndex, newIndex) {
    if (newIndex >= array.length) {
        newIndex = array.length - 1;
    }
    array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]);
    return array;
}
Run Code Online (Sandbox Code Playgroud)

单元测试:

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);
    });
});
Run Code Online (Sandbox Code Playgroud)


Eli*_*dou 11

这是我的单线程ES6解决方案,带有可选参数on.

if (typeof Array.prototype.move === "undefined") {
  Array.prototype.move = function(from, to, on = 1) {
    this.splice(to, 0, ...this.splice(from, on))
  }
}
Run Code Online (Sandbox Code Playgroud)

适应第一个提出的解决方案 digiguru

参数on是从from您想要移动的元素数量.

  • 我无法理解最后一个纯函数。该语句是否以逗号分隔?它是如何工作的? (4认同)
  • 解决方案很好。但是,当您扩展原型时,不应使用箭头函数,因为在这种情况下,“this”不是数组实例,而是 Window 对象。 (2认同)
  • 这是我最喜欢的答案——现代、简洁、简单。我添加了一个可链接的变体,以与标准数组方法更加一致。有些人会对原型污染提出异议,所以我还添加了一个独立的功能。最后,一些用例需要纯函数,而不是就地操作,所以我也添加了这一点。 (2认同)
  • @batbrain9392 - 检查这个问题:/sf/ask/719917551/ (2认同)

Bar*_*yle 9

我已经ECMAScript 6根据@Merc这里的答案实现了一个不可变的解决方案:

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;
};
Run Code Online (Sandbox Code Playgroud)

变量名可以缩短,只使用长的,以便代码可以自我解释。


Jar*_*ike 7

一种方法是使用切片方法创建一个包含所需顺序的新数组.

var arr = [ 'a', 'b', 'c', 'd', 'e'];
var arr2 = arr.slice(0,1).concat( ['d'] ).concat( arr.slice(2,4) ).concat( arr.slice(4) );
Run Code Online (Sandbox Code Playgroud)
  • arr.slice(0,1)给你['a']
  • arr.slice(2,4)给你['b','c']
  • arr.slice(4)给你['e']


Gas*_*ass 7

一种方法是使用 usesplice()从数组中删除项目,然后splice()再次使用方法,将删除的项目插入到目标索引中。

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)
Run Code Online (Sandbox Code Playgroud)

您可以在这里找到splice() 的简短说明


kin*_*oli 7

2022 年,此打字稿实用程序将与单元测试一起使用。

export const arrayMove = <T>(arr: T[], fromIndex: number, toIndex: number) => {
  const newArr = [...arr];
  newArr.splice(toIndex, 0, newArr.splice(fromIndex, 1)[0]);
  return newArr;
};
Run Code Online (Sandbox Code Playgroud)

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']);
  });
});
Run Code Online (Sandbox Code Playgroud)


Ken*_*iro 6

splice方法Array可以帮助:https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice

请记住,它可能相对昂贵,因为它必须主动重新索引数组.


小智 6

您可以实现一些基本的微积分并创建一个通用函数,用于将数组元素从一个位置移动到另一个位置.

对于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; 

}
Run Code Online (Sandbox Code Playgroud)

查看"gloommatter"中的"移动数组元素"以获取详细说明.

http://www.gloommatter.com/DDesign/programming/moving-any-array-elements-universal-function.html

  • 这应该是正确的答案,因为它不分配任何新数组。谢谢! (2认同)

Jav*_*mae 5

我需要一个不可变的移动方法(一个没有改变原始数组的方法),所以我调整了@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;
};
Run Code Online (Sandbox Code Playgroud)

这是一个jsfiddle显示它在行动.


小智 5

另一个使用 ES6 数组扩展运算符的纯 JS 变体,没有突变

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))
      
 
Run Code Online (Sandbox Code Playgroud)


H D*_*Dog 5

TypeScript 版本

复制自 @Merc 的答案。我最喜欢这个,因为它不会创建新数组并就地修改数组。我所做的就是更新到 ES6 并添加类型。

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;
}
Run Code Online (Sandbox Code Playgroud)