【问题标题】:Array slices in C#C#中的数组切片
【发布时间】:2010-09-29 05:18:01
【问题描述】:

你是怎么做到的?给定一个字节数组:

byte[] foo = new byte[4096];

如何将数组的前 x 个字节作为单独的数组获取? (具体来说,我需要它作为IEnumerable<byte>

这是为了与Sockets 一起工作。我认为最简单的方法是数组切片,类似于 Perls 语法:

@bar = @foo[0..40];

这会将前 41 个元素返回到 @bar 数组中。 C# 中有什么我只是缺少的东西,还是我应该做的其他事情?

LINQ 对我来说是一个选项 (.NET 3.5),如果有帮助的话。

【问题讨论】:

标签: c# arrays


【解决方案1】:

您可以使用ArraySegment<T>。它非常轻巧,因为它不复制数组:

string[] a = { "one", "two", "three", "four", "five" };
var segment = new ArraySegment<string>( a, 1, 2 );

【讨论】:

  • 不幸的是它不是 IEnumerable。
  • 有谁知道为什么它不是 IEnumerable?我不。看起来应该是这样。
  • @RonKlein,我知道它是一个包装器,它不会复制。你剩下的解释让我迷失了。
  • ArraySegment 是 IList 和 IEnumerable 从 .Net 4.5 开始。对于旧版本用户来说太糟糕了..
  • @Zyo 我的意思是 ArraySegment 从 .Net 4.5 开始实现 IEnumerable,而不是 IEnumerable 本身是新的。
【解决方案2】:

数组是可枚举的,所以您的foo 本身已经是IEnumerable&lt;byte&gt;。 只需使用Take() 之类的 LINQ 序列方法即可从中获取所需内容(不要忘记将Linq 命名空间与using System.Linq; 一起包含):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

如果您确实需要来自任何 IEnumerable&lt;byte&gt; 值的数组,则可以使用 ToArray() 方法。这似乎不是这里的情况。

【讨论】:

  • 如果我们要复制到另一个数组,只需使用 Array.Copy 静态方法。但是我认为其他答案已经正确解释了意图,不需要另一个数组,只需要前 41 个字节的 IEnumberable
  • 注意只有一维和交错数组是可枚举的,多维数组不是。
  • 注意使用 Array.Copy 比使用 LINQ 的 Take 或 Skip 方法快很多。
  • @Abel 这实际上是非常不正确的。多维数组可枚举的,但它们的枚举方式如下:[2,3] =&gt; [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]。锯齿状数组也是可枚举的,但不是在枚举时返回值,而是返回其内部数组。像这样:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
  • @Aidiakapi “非常不正确”? ;)。但你部分正确,我应该写“multidim 数组不实现IEnumerable&lt;T&gt;”,这样我的陈述就会更清楚。另请参阅:stackoverflow.com/questions/721882/…
【解决方案3】:

您可以使用数组CopyTo() 方法。

或者使用 LINQ,您可以使用 Skip()Take()...

byte[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
var subset = arr.Skip(2).Take(2);

【讨论】:

  • +1 是个好主意,但我需要使用返回的数组作为另一个函数的输入,这使得 CopyTo 需要一个临时变量。我会等待其他答案。
  • 我还不熟悉 LINQ,也许这进一步证明我真的应该这样做。
  • 这种方法至少比 Array.Copy 慢 50 倍。这在很多情况下都不是问题,但是在循环中进行数组切片时,性能下降非常明显。
  • 我正在打一个电话,所以性能对我来说不是问题。这对可读性很有帮助...谢谢。
  • 感谢Skip()。只是Take() 不会给你一个任意的切片。此外,我一直在寻找 LINQ 解决方案(切片 IEnumerable,但我知道数组的结果会更容易找到)。
【解决方案4】:

从 C# 8.0/.Net Core 3.0 开始

将支持数组切片,并添加新类型 IndexRange

Range Struct docs
Index Struct docs

Index i1 = 3;  // number 3 from beginning
Index i2 = ^4; // number 4 from end
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Console.WriteLine($"{a[i1]}, {a[i2]}"); // "3, 6"

var slice = a[i1..i2]; // { 3, 4, 5 }

以上代码示例取自 C# 8.0 blog

注意^ 前缀表示从数组的end 开始计数。如图docs example

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

RangeIndex 也可以在切片数组之外工作,例如使用循环

Range range = 1..4; 
foreach (var name in names[range])

将遍历条目 1 到 4


请注意,在撰写此答案时,C# 8.0 尚未正式发布
C# 8.x 和 .Net Core 3。 x 现在可在 Visual Studio 2019 及更高版本中使用

【讨论】:

【解决方案5】:
static byte[] SliceMe(byte[] source, int length)
{
    byte[] destfoo = new byte[length];
    Array.Copy(source, 0, destfoo, 0, length);
    return destfoo;
}

//

var myslice = SliceMe(sourcearray,41);

【讨论】:

  • 我认为 Buffer.BlockCopy() 效率更高,达到同样的效果。
【解决方案6】:

C# 7.2 中,您可以使用Span&lt;T&gt;。新的System.Memory 系统的好处是它不需要复制数据。

你需要的方法是Slice:

Span<byte> slice = foo.Slice(0, 40);

现在很多方法都支持SpanIReadOnlySpan,所以使用这种新类型会非常简单。

请注意,在撰写本文时,最新版本的 .NET (4.7.1) 中尚未定义 Span&lt;T&gt; 类型,因此要使用它,您需要从 NuGet 安装 System.Memory package

【讨论】:

    【解决方案7】:

    我在这里没有看到的另一种可能性:Buffer.BlockCopy() 比 Array.Copy() 稍快,并且它具有能够从原始数组动态转换的额外好处 (比如,short[]) 到一个字节数组,当你有需要通过 Socket 传输的数字数组时,这会很方便。

    【讨论】:

    • Buffer.BlockCopy 产生的结果与 Array.Copy() 不同,即使它们接受相同的参数 - 有很多空元素。为什么?
    • @jocull - 他们实际上并不完全采用相同的参数。 Array.Copy() 在元素中获取其长度和位置参数。 Buffer.BlockCopy() 以字节为单位获取其长度和位置参数。换句话说,如果你想复制一个 10 元素的整数数组,你可以使用Array.Copy(array1, 0, array2, 0, 10),但Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int))
    【解决方案8】:

    如果你想要IEnumerable&lt;byte&gt;,那么就

    IEnumerable<byte> data = foo.Take(x);
    

    【讨论】:

      【解决方案9】:

      C# 8 现在(自 2019 年起)支持Ranges,这使您可以更轻松地实现 Slice(类似于 JS 语法):

      var array = new int[] { 1, 2, 3, 4, 5 };
      var slice1 = array[2..^3];    // array[new Range(2, new Index(3, fromEnd: true))]
      var slice2 = array[..^3];     // array[Range.EndAt(new Index(3, fromEnd: true))]
      var slice3 = array[2..];      // array[Range.StartAt(2)]
      var slice4 = array[..];       // array[Range.All]
      

      您可以使用范围来代替众所周知的 LINQ 函数:Skip()Take()Count()。。 p>

      【讨论】:

        【解决方案10】:

        这是一个简单的扩展方法,它返回一个切片作为一个新数组:

        public static T[] Slice<T>(this T[] arr, uint indexFrom, uint indexTo) {
            if (indexFrom > indexTo) {
                throw new ArgumentOutOfRangeException("indexFrom is bigger than indexTo!");
            }
        
            uint length = indexTo - indexFrom;
            T[] result = new T[length];
            Array.Copy(arr, indexFrom, result, 0, length);
        
            return result;
        }
        

        那么你可以把它当作:

        byte[] slice = foo.Slice(0, 40);
        

        【讨论】:

          【解决方案11】:

          如果您不想添加 LINQ 或其他扩展名,只需:

          float[] subArray = new List<float>(myArray).GetRange(0, 8).ToArray();
          

          【讨论】:

          • Error CS0246: The type or namespace name 'List&lt;&gt;' could not be found (are you missing a using directive or an assembly reference?) Microsoft 文档是毫无希望的,数百个“列表”条目被编入索引。什么是正确的?
          • System.Collections.Generic.List
          【解决方案12】:
          byte[] foo = new byte[4096]; 
          
          byte[] bar = foo.Take(40).ToArray();
          

          【讨论】:

            【解决方案13】:

            您可以在原始数组(即 IList)周围使用包装器,就像在这段(未经测试的)代码中一样。

            public class SubList<T> : IList<T>
            {
                #region Fields
            
                private readonly int startIndex;
                private readonly int endIndex;
                private readonly int count;
                private readonly IList<T> source;
            
                #endregion
            
                public SubList(IList<T> source, int startIndex, int count)
                {
                    this.source = source;
                    this.startIndex = startIndex;
                    this.count = count;
                    this.endIndex = this.startIndex + this.count - 1;
                }
            
                #region IList<T> Members
            
                public int IndexOf(T item)
                {
                    if (item != null)
                    {
                        for (int i = this.startIndex; i <= this.endIndex; i++)
                        {
                            if (item.Equals(this.source[i]))
                                return i;
                        }
                    }
                    else
                    {
                        for (int i = this.startIndex; i <= this.endIndex; i++)
                        {
                            if (this.source[i] == null)
                                return i;
                        }
                    }
                    return -1;
                }
            
                public void Insert(int index, T item)
                {
                    throw new NotSupportedException();
                }
            
                public void RemoveAt(int index)
                {
                    throw new NotSupportedException();
                }
            
                public T this[int index]
                {
                    get
                    {
                        if (index >= 0 && index < this.count)
                            return this.source[index + this.startIndex];
                        else
                            throw new IndexOutOfRangeException("index");
                    }
                    set
                    {
                        if (index >= 0 && index < this.count)
                            this.source[index + this.startIndex] = value;
                        else
                            throw new IndexOutOfRangeException("index");
                    }
                }
            
                #endregion
            
                #region ICollection<T> Members
            
                public void Add(T item)
                {
                    throw new NotSupportedException();
                }
            
                public void Clear()
                {
                    throw new NotSupportedException();
                }
            
                public bool Contains(T item)
                {
                    return this.IndexOf(item) >= 0;
                }
            
                public void CopyTo(T[] array, int arrayIndex)
                {
                    for (int i=0; i<this.count; i++)
                    {
                        array[arrayIndex + i] = this.source[i + this.startIndex];
                    }
                }
            
                public int Count
                {
                    get { return this.count; }
                }
            
                public bool IsReadOnly
                {
                    get { return true; }
                }
            
                public bool Remove(T item)
                {
                    throw new NotSupportedException();
                }
            
                #endregion
            
                #region IEnumerable<T> Members
            
                public IEnumerator<T> GetEnumerator()
                {
                    for (int i = this.startIndex; i < this.endIndex; i++)
                    {
                        yield return this.source[i];
                    }
                }
            
                #endregion
            
                #region IEnumerable Members
            
                IEnumerator IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }
            
                #endregion
            }
            

            【讨论】:

            • 我建议对 IndexOf 使用 EqualityComparer.Default - 这样您就不需要任何特殊的大小写。
            • 我希望它绝对没问题。我当然会先使用更简单的代码。
            • 我认为这样的事情是最好的方法。但显然它比简单的Array.Copy 更有效(第一次),尽管这有很多优点,例如子列表实际上是父列表中的一个区域,而不是列表中条目的副本。
            【解决方案14】:

            对于字节数组System.Buffer.BlockCopy 会给你最好的性能。

            【讨论】:

            • 只有在循环执行数千或数百万次时才真正重要。在套接字应用程序中,您可能会接受一些输入并将其分解为多个部分。如果你只做一次,最好的性能就是下一个程序员最容易理解的。
            【解决方案15】:

            你可以使用Take扩展方法

            var array = new byte[] {1, 2, 3, 4};
            var firstTwoItems = array.Take(2);
            

            【讨论】:

              【解决方案16】:

              这可能是一个解决方案:

              var result = foo.Slice(40, int.MaxValue);
              

              那么结果是一个IEnumerable>,第一个IEnumerable包含foo,第二个 IEnumerable 保存其余部分。

              自己写了一个包装类,整个迭代比较懒,希望能有所帮助:

              public static class CollectionSlicer
              {
                  public static IEnumerable<IEnumerable<T>> Slice<T>(this IEnumerable<T> source, params int[] steps)
                  {
                      if (!steps.Any(step => step != 0))
                      {
                          throw new InvalidOperationException("Can't slice a collection with step length 0.");
                      }
                      return new Slicer<T>(source.GetEnumerator(), steps).Slice();
                  }
              }
              
              public sealed class Slicer<T>
              {
                  public Slicer(IEnumerator<T> iterator, int[] steps)
                  {
                      _iterator = iterator;
                      _steps = steps;
                      _index = 0;
                      _currentStep = 0;
                      _isHasNext = true;
                  }
              
                  public int Index
                  {
                      get { return _index; }
                  }
              
                  public IEnumerable<IEnumerable<T>> Slice()
                  {
                      var length = _steps.Length;
                      var index = 1;
                      var step = 0;
              
                      for (var i = 0; _isHasNext; ++i)
                      {
                          if (i < length)
                          {
                              step = _steps[i];
                              _currentStep = step - 1;
                          }
              
                          while (_index < index && _isHasNext)
                          {
                              _isHasNext = MoveNext();
                          }
              
                          if (_isHasNext)
                          {
                              yield return SliceInternal();
                              index += step;
                          }
                      }
                  }
              
                  private IEnumerable<T> SliceInternal()
                  {
                      if (_currentStep == -1) yield break;
                      yield return _iterator.Current;
              
                      for (var count = 0; count < _currentStep && _isHasNext; ++count)
                      {
                          _isHasNext = MoveNext();
              
                          if (_isHasNext)
                          {
                              yield return _iterator.Current;
                          }
                      }
                  }
              
                  private bool MoveNext()
                  {
                      ++_index;
                      return _iterator.MoveNext();
                  }
              
                  private readonly IEnumerator<T> _iterator;
                  private readonly int[] _steps;
                  private volatile bool _isHasNext;
                  private volatile int _currentStep;
                  private volatile int _index;
              }
              

              【讨论】:

                【解决方案17】:

                我认为 C# 不支持 Range 语义。不过,您可以编写一个扩展方法,例如:

                public static IEnumerator<Byte> Range(this byte[] array, int start, int end);
                

                但正如其他人所说,如果您不需要设置起始索引,那么您只需要Take

                【讨论】:

                  【解决方案18】:

                  这是一个使用泛型的扩展函数,其行为类似于 PHP 函数 array_slice。允许负偏移量和长度。

                  public static class Extensions
                  {
                      public static T[] Slice<T>(this T[] arr, int offset, int length)
                      {
                          int start, end;
                  
                          // Determine start index, handling negative offset.
                          if (offset < 0)
                              start = arr.Length + offset;
                          else
                              start = offset;
                  
                          // Clamp start index to the bounds of the input array.
                          if (start < 0)
                              start = 0;
                          else if (start > arr.Length)
                              start = arr.Length;
                  
                          // Determine end index, handling negative length.
                          if (length < 0)
                              end = arr.Length + length;
                          else
                              end = start + length;
                  
                          // Clamp end index to the bounds of the input array.
                          if (end < 0)
                              end = 0;
                          if (end > arr.Length)
                              end = arr.Length;
                  
                          // Get the array slice.
                          int len = end - start;
                          T[] result = new T[len];
                          for (int i = 0; i < len; i++)
                          {
                              result[i] = arr[start + i];
                          }
                          return result;
                      }
                  }
                  

                  【讨论】:

                  • 相当不错,虽然有一些来自 .NET 世界的东西。如果start 不在0 和arr.Length 之间,它可能会抛出越界异常。另外,end &gt;= start &gt;= 0,所以你不需要检查end &lt; 0,它不可能发生。你可以更简洁地检查length &gt;= 0,然后检查len = Math.min(length, arr.Length - start),而不是乱搞end
                  【解决方案19】:
                  using System;
                  using System.Collections.Generic;
                  using System.Linq;
                  using System.Text;
                  using System.Threading.Tasks;
                  
                  namespace data_seniens
                  {
                      class Program
                      {
                          static void Main(string[] args)
                          {
                              //new list
                              float [] x=new float[]{11.25f,18.0f,20.0f,10.75f,9.50f, 11.25f, 18.0f, 20.0f, 10.75f, 9.50f };
                  
                              //variable
                              float eat_sleep_area=x[1]+x[3];
                              //print
                              foreach (var VARIABLE in x)
                              {
                                  if (VARIABLE < x[7])
                                  {
                                      Console.WriteLine(VARIABLE);
                                  }
                              }
                  
                  
                  
                              //keep app run
                          Console.ReadLine();
                          }
                      }
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2012-11-10
                    • 1970-01-01
                    • 2013-12-27
                    • 1970-01-01
                    • 2014-04-14
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多