【问题标题】:Cropping the first element in a string array without copying all the elements to a new array裁剪字符串数组中的第一个元素而不将所有元素复制到新数组中
【发布时间】:2014-11-06 08:06:59
【问题描述】:

有一个字符串数组

string[] lines

我想删除第一个元素。所以如果数组有 5 个元素,我想以 4 个结尾。

有很多方法可以做到这一点:

  1. 创建一个新的字符串数组,其大小与原来的减一相同。然后简单地复制所有元素(第一个元素除外)。我不喜欢这样,因为,好吧,你必须创建一个全新的数组并且看起来重复了内存使用(?)

  2. 使用像 List 这样的集合来实现方法。但那将是同样的故事:我必须将我的字符串数组转换为列表,然后删除第一个元素,然后将列表转换为字符串数组(因为我需要一个字符串数组,而不是列表)。

  3. System.Array.Resize() 似乎能够“裁剪”数组,这正是我所需要的。但是,它会裁剪“最右边”的元素,而我需要裁剪第一个(最左边的)元素。


我的问题是:有没有更简单的方法来消除字符串数组中的第一个元素,最终得到一个大小比原始数组小一的数组?

【问题讨论】:

  • AFAIK 数组是不可变的。 System.Array.Resize() 通过使用选项 1 来实现。
  • 如果你真的需要这个,你可能想要一个 CircularBuffer。正如谷歌所证明的那样,周围有很多 C# 实现。
  • 不..你不能不复制..你能做的最好是(),创建一个自定义类,
  • @JamesBarrass 没错。因此,Resize<> 方法的参数是ref T[] array;如果保留现有实例,则不需要 ref (IsByRef)。

标签: c# .net arrays


【解决方案1】:

我不知道你想用“裁剪”版本做什么,但一个非常便宜的做法是将lines 顶部包装在ArraySegment<> 中,如下所示:

  var cropped = new ArraySegment<string>(lines, 1, 4);

自 .NET 4.5(从 2012 年开始)以来,ArraySegment&lt;&gt; 实现了一些接口,因此您可以执行以下操作:

  foreach (var l in cropped)
  {
      // ...
  }

(在ArraySegment&lt;&gt; 上使用显式接口实现,迭代变量l 是强类型)。

请记住,这只是原始数组实例的包装。如果该数组被修改,这将反映在ArraySegment&lt;&gt; 中。没有抄袭(甚至没有抄袭)。


如果您需要索引等,请将您的 ArraySegment&lt;&gt; 结构的值框到 IReadOnlyList&lt;&gt; 或 IList&lt;&gt;:

  IReadOnlyList<string> croppedB = new ArraySegment<string>(lines, 1, 4);

或:

  IList<string> croppedC = new ArraySegment<string>(lines, 1, 4);

【讨论】:

  • 这很好,在 .NET 4.5 之前该结构没有任何意义,因为它只是携带偏移量和长度,您必须自己进行偏移量计算。我想知道为什么他们没有创建一个构造函数,它需要一个 IList&lt;T&gt; 来允许嵌套它们。恕我直言,制作一个完全只读的变体会更有意义。
  • +1 表示 ArraySegment。你永远不会停止学习。最终可能会派上用场,谢谢。
【解决方案2】:

如果您真的喜欢整个“不要创建新数组”,您可以使用 startIndex 和长度变量,并且始终保持整个数组不变,并且仅通过这两个值将其中的一部分标记为可用。但是,如果您不喜欢大数组大小和最先进的性能,那么您的程序会看起来更简洁,如果您只是创建一个新数组,那么您的程序会更容易理解、创建和维护:

var arrayMinusFirstElement = lines.Skip(1).ToArray()

【讨论】:

  • 这不是“消除数组中的第一个元素”。
  • @HamidP 不,因为根据定义从数组中删除一个元素而不创建临时对象是不可能的。数组是固定的。您可以使用我描述的方式或使用我发布的代码(如果临时文件很好)。任何其他解决方案(包括您的)只是隐藏临时对象的创建。但是隐藏这不是问题的重点,OP询问了避免它。
  • 我的解决方案如何隐藏“创建临时对象”?
  • @HamidP 你有一个完整的字典作为临时存储。 OP在运算前有一个数组,运算后需要一个数组。
【解决方案3】:

此解决方案是模拟数组,您可以在末尾添加项目并从头开始裁剪项目。 无论您添加或裁剪了多少项目,您始终可以使用从零开始的索引器。 这是一个队列,您可以修改它的元素并通过它们的索引访问它们。

var lines = new DicAsArray<string>();
lines.Add("zero");
lines.Add("one");
lines.Add("two");
lines.Add("three");
lines.Add("four");
lines.Crop();
lines[2] = "!!!";

for (int i = 0; i < lines.Count; i++)
{
    Console.WriteLine(lines[i]);
}

其余的就像

public class DicAsArray<T>
{
    public DicAsArray()
    {
        _dic = new Dictionary<int, T>();
        _minIndex = 0;
        _maxIndex = -1;
    }

    public void Add(T item)
    {
        _dic[++_maxIndex] = item;
    }

    public T Crop()
    {
        _croppedCount++;
        var item = _dic[_minIndex];
        _dic.Remove(_minIndex++);
        return item;
    }

    public T this[int index]
    {
        get
        {
            var mappedIndex = _croppedCount + index;
            return _dic[mappedIndex];
        }
        set
        {
            var mappedIndex = _croppedCount + index;
            if (mappedIndex > _maxIndex || mappedIndex < _minIndex)
                throw new IndexOutOfRangeException();
            _dic[mappedIndex] = value;
        }
    }

    public int Count { get { return _dic.Count; } }

    private Dictionary<int, T> _dic;
    private int _maxIndex;
    private int _minIndex;
    private int _croppedCount;
}

【讨论】:

  • 不鼓励在 Stack Overflow 上发布没有附带说明的代码。例如,您可能会提到与Queue&lt;T&gt; 相比,此实现的效率非常低。还有,大概是_dic.Remove(_minIndex++)?
  • 我正在路上,请稍等。
  • 它不是Queue,Queue 没有索引器。 @格鲁
  • 但这真的意味着您必须使用Dictionary 来存储数组的一部分吗?你知道Queue&lt;T&gt;是如何在.NET中实现的(或者,好吧,在我能想到的任何库中)?
  • 是的,它的实现由一个数组和两个变量支持,用于维护头和尾索引。这不是存储一段数组,我实现了一个类似数组的结构,可以在尾部添加项目,可以从头部裁剪项目,无论你做了什么,它都会保持索引。您还可以通过它的索引访问和修改每个项目@Groo
【解决方案4】:

你说Array.Resize()裁剪最右边的元素,然后简单地先反转数组,然后再反转它。

Array.Reverse(Array.Resize(Array.Reverse(myArray)))

【讨论】:

    猜你喜欢
    • 2020-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-09
    • 1970-01-01
    • 2017-03-17
    • 1970-01-01
    相关资源
    最近更新 更多