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

IT技术 javascript arrays
2021-01-21 22:25:33

我很难弄清楚如何移动数组元素。例如,给定以下内容:

var arr = [ 'a', 'b', 'c', 'd', 'e'];

我怎样才能写一个函数来移动'd'之前'b'

还是'a'之后'c'

移动后,应更新其余元素的索引。这意味着在移动后的第一个示例中,

arr = ['a', 'd', 'b', 'c', 'e']

这看起来应该很简单,但我无法理解它。

6个回答

如果你想要 npm 上的一个版本,array-move是最接近这个答案的,尽管它不是相同的实现。有关更多详细信息,请参阅其用法部分。可以在 npm 上的array.prototype.move上找到此答案的先前版本(修改后的 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)); 

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

逐步执行代码:

  1. 如果new_index大于数组的长度,我们希望(我假设)用 new undefineds正确填充数组这个小片段通过推动undefined数组直到我们有合适的长度来处理这个问题
  2. 然后,在 中arr.splice(old_index, 1)[0],我们拼接出旧元素。splice返回拼接出来的元素,但它在一个数组中。在我们上面的例子中,这是[1]. 因此,我们采用该数组的第一个索引来获取原始数据1
  3. 然后我们用来splice在new_index 的地方插入这个元素。因为我们在 if 上面填充了数组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));

这应该说明array_move([1, 2, 3], -1, -2)正确的事情(将最后一个元素移到倒数第二个位置)。结果应该是[1, 3, 2]

无论哪种方式,在您最初的问题中,您都会array_move(arr, 0, 2)aafterc对于d以前b,你会这样做array_move(arr, 3, 1)

不需要步骤 1 中的循环,您可以简单地this[new_index] = undefined;if使用由于 Javascript 数组是稀疏的,这将扩展数组大小以包含 new_index 以.splice使其工作,但无需创建任何中间元素。
2021-03-12 22:25:33
@burakemre:我认为这个结论并没有那么明确。大多数优秀的 JS 程序员(以及最流行的库)在使用.hasOwnPropertyfor..in 之类的东西进行迭代时都会使用检查,尤其是像 Prototype 和 MooTools 这样修改原型的库。无论如何,在这样一个相对有限的例子中,我并不觉得这是一个特别重要的问题,而且社区中对于原型修改是否是一个好主意存在很好的分歧。不过,通常情况下,迭代问题是最不关心的。
2021-03-19 22:25:33
这工作得很好!而且你的解释很清楚。感谢您花时间写这篇文章。
2021-03-20 22:25:33
您不应该操作 Object 和 Array 原型,它会在迭代元素时导致问题。
2021-03-23 22:25:33
请不要将此添加到原型中。当 TC39 想要将它本地添加到 JavaScript 时,他们将不得不使用不同的、尴尬的名称,因为人们这样做。
2021-04-01 22:25:33

我喜欢这种方式。它简洁且有效。

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

注意:永远记得检查你的数组边界。

在 jsFiddle 中运行代码段

我个人更喜欢 3 行代码。更容易理解:获取元素的副本;从数组中删除它;将其插入新位置。一个班轮较短,但对其他人来说不太清楚......
2021-03-11 22:25:33
现在是 2021 年。由于内存/性能原因,仍然存在复制完全不合适的情况。纯函数应该是默认的,但不应该是教条。
2021-03-11 22:25:33
由于 Array.splice 在新的 Array 中返回删除的值,您可以将其写为单行... arr.splice(index + 1, 0, arr.splice(index, 1)[0]);
2021-03-13 22:25:33
简短而简单的代码。但现在是 2019 年!!,创建数组的克隆并返回它而不是改变数组。这将使您的函数“arraymove”符合函数式编程标准
2021-03-13 22:25:33
我从未想过,对于某些人来说,在 2019 年之后,对数组进行原地变异会变得过时。完全合法的答案,+1。
2021-04-09 22:25:33

这是我在 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;
    }
  }

我不能相信任何功劳,这一切都应该归功于理查德·斯卡洛特在这个性能测试中,它在较小的数据集上击败了基于拼接的方法然而,正如 Darwayne 指出的那样,它在较大的数据集上要慢得多

您的高性能解决方案在大型数据集上速度较慢。jsperf.com/array-prototype-move/8
2021-03-12 22:25:33
这似乎是一个非常愚蠢的权衡。在小数据集上的性能是一个可以忽略不计的增益,但在大数据集上的损失是一个显着的损失。你的净交换是负的。
2021-03-19 22:25:33
一行解决方案需要处理两种情况: from >= to ? this.splice(to, 0, this.splice(from, 1)[0]) : this.splice(to - 1, 0, this.splice(from, 1)[0]);
2021-04-02 22:25:33
@Reid 这不是必需的。IMO 可以假设数组的长度没有被修改。
2021-04-06 22:25:33
请永远不要修改内置原型。nczonline.net/blog/2010/03/02/...
2021-04-09 22:25:33

splice() 方法向/从数组添加/删除项目,并返回删除的项目。

注意:此方法更改原始数组。/w3学校/

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"]

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

alert(arr.move(0,2).join(','));

演示在这里

请参阅有关此的其他评论:修改诸如 Array 和 Object 之类的内置原型是一个坏主意。你会破坏东西。
2021-03-19 22:25:33
有没有使用这个的库?挺整洁的!
2021-03-31 22:25:33

我的 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;
}
我最喜欢你简单灵活的解决方案。谢谢!
2021-03-10 22:25:33
在第一个函数字符串中,您应该返回array,就像最后所做的那样。
2021-03-11 22:25:33
真的,我怎么错过了?固定的!
2021-03-27 22:25:33