【问题标题】:Adding Element in middle of an array在数组中间添加元素
【发布时间】:2014-12-19 12:16:18
【问题描述】:

如何在数组中间添加一个元素?我曾尝试在 google 上搜索,但 找不到不循环的方法。任何人都可以通过提供代码 sn-p 来帮助我,但请不要建议使用循环,因为数组很重,性能是这里的瓶颈。

编辑

实际上我希望每个奇数索引元素都被复制到偶数索引。

例子

myarray[0] = "a";
myarray[1] = "b";
myarray[2] = "c";

预期答案

myarray[0] = "a";
myarray[1] = "a";
myarray[2] = "b";
myarray[3] = "b";
myarray[4] = "c";
myarray[5] = "c";

【问题讨论】:

  • 数组不是快速插入的正确数据类型。
  • 但是你知道在中间插入元素的方法吗?
  • 不,没有。 going 在结果中的项目数上至少是线性的。没有办法解决这个问题 - 除非您创建一个实际上只将项目请求代理到原始列表的集合。我强烈怀疑您的示例不能代表您的实际需求,这意味着它仍然很难为您提供帮助。

标签: c# arrays winforms


【解决方案1】:

如何在数组中间添加一个元素?

您不能在数组中添加元素任何地方。创建数组后,它的大小是固定的 - 您可以修改元素,但不能添加或删除它们。

您可以改用List<T>确实允许插入 - 但需要复制插入点之后出现的元素。

您可能会改用LinkedList<T>,但是您需要循环以获取到正确的节点。

您可能需要考虑改用某种树形结构,这样可能更容易找到正确的位置然后插入数据。

或者,考虑重新设计 - 我们没有太多上下文,但您可能需要考虑收集所有数据开始,忽略排序,然后将其排序为正确的顺序。

编辑:对于您的示例,我不会使用任何类型的插入 - 我会从旧列表创建一个新列表,例如:

// Or use Enumerable.Repeat instead of creating an array for each element.
var newList = oldList.SelectMany(x => new[] { x, x }).ToList();

或者不使用 LINQ:

var copies = 2; // Or whatever
var newList = new List<string>(oldList.Count * copies);
foreach (var item in oldList)
{
    for (int i = 0; i < copies; i++)
    {
        newList.Add(item);
    }
}

从根本上说,您不会比 O(N) 便宜,其中 N 是结果列表中的项目数...

【讨论】:

  • 请看我对问题的扩展
  • 你对 lambda 的回答是正确的,但不能使用它,因为它会在调试时阻止编码,你能提出其他方法吗
  • @newbee:我不知道您所说的“在调试时阻止编码”是什么意思。你需要比这更具体很多。如果您的意思是您不能使用“编辑并继续”,我建议您改掉在调试期间编写大量代码的习惯。更喜欢进行更改后易于重新运行的单元测试。
  • 如果你在 .net 框架平台上使用 lambda 表达式,它会在调试时阻止更改(这是我的习惯)
  • @newbee:那就改掉那个习惯吧。这不是一个富有成效的 - 这只是一个例子。如果您在调试时更改代码,您怎么能确信您的更改不会影响已经运行的代码?对于几乎所有场景,单元测试是一种好得多的方法,IMO。
【解决方案2】:

你可以通过使用“Buffer.BlockCopy”来做到这一点,但你必须为它创建一个单独的数组副本

【讨论】:

  • 请看我对问题的扩展
【解决方案3】:

您不能在数组中添加元素。但是如果你使用 List 那么方法看起来像:

public void Operation(List<string> list)
{
    if (list == null || list.Count == 0) return;

    int count = list.Count;

    for (int i = 0; i <= count; i++)
        if (i % 2 == 0)
            list.Insert(i + 1, list[i]);
} 

【讨论】:

  • 没有循环是否可以使用列表,因为我的性能因循环而下降
  • @newbee 你可以优化战利品,但它只能通过循环来完成。我没有找到这样的 linq 扩展。检查Zip 扩展,这将帮助您找到它。
  • 您在预取list.Count 时进行了错误的优化,因为这意味着例如{"1", "2", "3", "4", "5", "6"} 将变为 {"1", "1", "2", "2", "3", "3", "4", "4", "5", "6"},最后两个不会加倍。您需要将i &lt;= count 更改为i != list.Counti &lt; list.Count
  • @Jon Hanna 感谢您的建议,但我的回答只是问题的逻辑.. 我认为
  • &lt;=!= 不一样,您可以在这里使用&lt;!=,两者都可以,所以我建议两者。您的错误是您存储 count = list.Count 但随后 list.Count 更改,因此记忆值不正确。您也可以使用count = list.Count * 2 来修复错误,同时仍然在记忆。但是,您的效率缺陷是您在运行它的过程中复制了 count * (count - 1) / 2 元素,这使得它比需要的慢得多。
【解决方案4】:

解决评论:

没有循环是否可以使用列表,因为我的性能因循环而下降

虽然不可能完全避免循环并保持相同的结构(但请参见下文),但我们可以大幅改进多个插入。

每次插入时,列表后面的所有元素都必须复制到内部结构中。因此,对于一个 n 元素列表,将有 n-1 复制操作,总共复制了 n * (n - 1) / 2 个元素(因为复制的元素数量每次都会下降。这意味着所花费的时间大约为 O(n 2)—二次时间。这是不好的。

我们可以做得更好,首先使列表大小增加一倍,然后将元素复制到它们的新位置。由于我们只需要复制 n * 2 副本,因此我们现在在 O(n)(线性时间)内完成此操作。

要将列表的大小加倍,我们将其添加到自身。这既方便(我们不需要创建另一个集合,或重复调用Add()),又利用了List&lt;T&gt;.AddRange() 优化处理添加到自身的列表这一事实:

list.AddRange(list);

现在,假设列表包含{"1", "2", "3"}。它现在包含{"1", "2", "3", "1", "2", "3"},我们想要{"1", "1", "2", "2", "3", "3"}

所以我们从原始集合的最后一个元素 (list[2]) 开始并将其复制到最后两个位置,然后在列表中向后移动。因为我们在向前复制的同时向后移动,所以我们最终不会复制到尚未复制的位置(如果我们从头开始,我们只会在所有地方复制相同的"1"

public static void DoupleUp(List<string> list)
{
  if(list == null || list.Count == 0)
      return;
  list.AddRange(list);
  for(int idx = list.Count / 2 - 1; idx != -1; --idx)
  {
    T el = list[idx];
    list[idx * 2] = el;
    list[idx * 2 + 1] = el;
  }
}

现在剩下的就是意识到没有什么特别与字符串相关的,然后泛化该方法,以便我们可以在所有列表中使用它:

public static void DoupleUp<T>(List<T> list)
{
  if(list == null || list.Count == 0)
      return;
  list.AddRange(list);
  for(int idx = list.Count / 2 - 1; idx != -1; --idx)
  {
    T el = list[idx];
    list[idx * 2] = el;
    list[idx * 2 + 1] = el;
  }
}

在修复了 Ankush 回答中的错误后,我进行了一次测试运行,从 Enumerable.Range(0, 10).Select(i =&gt; i.ToString()).ToList()(数字 0 到 9 作为字符串)开始,然后在上面调用了他的 Operation 15 次,然后又回到原来的状态list 并调用了我的DoubleUp 15 次(15 并不多,但每次都会翻倍,所以最后的调用是将 163840 个元素的列表变成一个包含 327680 个元素的列表)。

在我的机器上,使用 Operation 执行此操作大约需要 12.8 秒,而使用 DoubleUp 执行此操作大约需要 0.01 秒。

但是,如果您只想遍历此列表一次,那么您最好动态创建列表:

public static IEnumerable<T> DoubleElements<T>(this IEnumerable<T> source)
{
  foreach(T item in source)
  {
    yield return item;
    yield return item;
  }  
}

那么您可以只使用foreach(string str in DoubleElements(myarray)),甚至不必更改使用数组。

你甚至可以概括一下:

public static IEnumerable<T> RepeatElements<T>(this IEnumerable<T> source, int count)
{
  foreach(T item in source)
    for(int i = 0; i < count; ++i)
      yield return item;
}

然后使用foreach(string str in RepeatElements(myarray, 2))

现在,如果你真的需要避免循环,你将不得不再次做一些非常不同的事情,它会带走一些可能的进一步使用:

public class RepeatList<T> : IList<T>
{
  private readonly IList<T> _backing;
  private readonly int _repeats;
  public RepeatList(IList<T> backing, int repeats)
  {
    if(backing == null)
      throw new ArgumentNullException("backing");
    if(repeats < 1)
      throw new ArgumentOutOfRangeException("repeats");
    _backing = backing;
    _repeats = repeats;
  }
  public RepeatList(int repeats)
    : this(new List<T>(), repeats)
  {
  }
  public T this[int index]
  {
    get { return _backing[index / _repeats]; }
    set { _backing[index / _repeats] = value; }
  }
  public int Count
  {
    get { return _backing.Count * _repeats; }
  }
  public bool IsReadOnly
  {
    get { return _backing.IsReadOnly; }
  }
  public int IndexOf(T item)
  {
    int idx = _backing.IndexOf(item);
    return idx >= 0 ? idx * _repeats : -1;
  }
  public void Insert(int index, T item)
  {
    _backing.Insert(index / _repeats, item);
  }
  public void RemoveAt(int index)
  {
    _backing.RemoveAt(index / _repeats);
  }
  public void Add(T item)
  {
    _backing.Add(item);
  }
  public void Clear()
  {
    _backing.Clear();
  }
  public bool Contains(T item)
  {
    return _backing.Contains(item);
  }
  public void CopyTo(T[] array, int arrayIndex)
  {
    if(array == null)
      throw new ArgumentNullException("array");
    if(arrayIndex < 0)
      throw new ArgumentOutOfRangeException("arrayIndex");
    if(array.Length - arrayIndex < _backing.Count * _repeats)
      throw new ArgumentException("array is too small to copy all elements starting from index " + arrayIndex);
    foreach(T item in this)
      array[arrayIndex++] = item;
  }
  public bool Remove(T item)
  {
    return _backing.Remove(item);
  }
  public IEnumerator<T> GetEnumerator()
  {
    foreach(T item in _backing)
      for(int i = 0; i != _repeats; ++i)
        yield return item;
  }
  IEnumerator IEnumerable.GetEnumerator()
  {
    return GetEnumerator();
  }
}

这将在恒定时间内创建这样一个列表:var rep = new RepeatList&lt;string&gt;(myArray, 2) 几乎立即返回。

它会像 myArray 一样发生变异(更改 myArray 的元素 2,然后更改 rep 的元素 4 和 5),如果与非只读后备列表一起使用,这将是双向的,包括对 @987654348 的调用@ 实际上添加了 2 个元素。您可以通过将其设为只读并让所有变异成员抛出 NotSupportedException 来最大限度地减少奇怪,但发生直写也很有用。

【讨论】:

    猜你喜欢
    • 2021-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-19
    • 2014-04-17
    • 1970-01-01
    • 2019-03-09
    • 2018-01-12
    相关资源
    最近更新 更多