C#中的数组切片


228

你怎么做呢?给定一个字节数组:

byte[] foo = new byte[4096];

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

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

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

它将前41个元素返回到@bar数组中。我只是想念C#中的某些东西,还是我应该做的其他事情?

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


3
数组切片是对c#7.2的建议 github.com/dotnet/csharplang/issues/185
Mark

3
C#8.0将看到本机数组切片的引入。查看答案以了解更多详细信息
Remy

1
您可能对ArraySlice <T>感兴趣,该数组实现了对带有原始数据的视图的step的切片:github.com/henon/SliceAndDice
henon

Answers:


196

数组是可枚举的,所以您本身foo就是一个数组IEnumerable<byte>。只需使用LINQ序列方法Take()就可以从中获得想要的内容(不要忘了使用来包含Linq名称空间 using System.Linq;):

byte[] foo = new byte[4096];

var bar = foo.Take(41);

如果确实需要任何IEnumerable<byte>值的数组,则可以使用该ToArray()方法。这里似乎并非如此。


5
如果我们要复制到另一个数组,只需使用Array.Copy静态方法。但是,我认为其他答案已经正确解释了意图,不需要仅在前41个字节上的IEnumberable <byte>即可使用另一个数组。
AnthonyWJones

2
注意,只有一维数组和锯齿数组是可枚举的,而多维数组则不是。
亚伯2010年

11
请注意,使用Array.Copy的性能比使用LINQ的Take或Skip方法快得多。
迈克尔(Michael

4
@Abel实际上非常不正确。多维数组可枚举,但他们列举如下:[2,3] => [1,1], [1,2], [1,3], [2,1], [2,2], [2,3]。锯齿状数组也是可枚举的,但它们返回其内部数组,而不是枚举时返回值。像这样:type[][] jaggedArray; foreach (type[] innerArray in jaggedArray) { }
Aidiakapi'2

3
@Aidiakapi“非常错误”?;)。但是您部分正确,我应该写“ multidim数组未实现IEnumerable<T>”,这样我的陈述会更加清楚。另见本:stackoverflow.com/questions/721882/...
阿贝尔

211

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

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

5
不幸的是,它不是IEnumerable。
递归

1
是的,但是围绕它编写实现IEnumerable的迭代器包装很容易。
Mike Scott 2010年

22
有谁知道为什么它不是IEnumerable?我不。似乎应该如此。
Fantius 2010年

39
从.Net 4.5开始,ArraySegment是IList和IEnumerable。太糟糕了旧版本的用户..
托德李

6
@Zyo我的意思是ArraySegment <T>从.Net 4.5开始实现IEnumerable <T>,而不是IEnumerable <T>本身是新的。
Todd Li

137

您可以使用arrays CopyTo()方法。

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

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

1
+1是个好主意,但我需要将返回的数组用作另一个函数的输入,这使CopyTo需要一个临时变量。我将等待其他答案。
马修·沙利

4
我还不熟悉LINQ,也许这进一步证明了我确实应该这样做。
马修·沙利

11
这种方法比Array.Copy至少慢50倍。在许多情况下这不是问题,但是在循环中进行数组切片时,性能下降非常明显。
Valentin Vasilyev 2010年

3
我正在打一个电话,所以性能对我来说不是问题。这对于提高可读性非常有用...谢谢。
Rich

2
谢谢你Skip()。只是Take()不会让你随心所欲。此外,无论如何我一直在寻找LINQ解决方案(切片IEnumerable,但我知道关于数组的结果会更容易找到)。
Tomasz Gandor

55
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);

11
我认为Buffer.BlockCopy()更有效,并且可以达到相同的结果。
马特·戴维斯

28

从C#8.0 / .Net Core 3.0开始

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

范围结构文档
索引结构文档

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 博客

请注意,^前缀表示从数组末尾开始计数。如docs示例所示

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

Range并且Index还可以在切片数组之外工作,例如使用循环

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及更高版本中可用


不知道这是否创建数组的副本吗?
蒂姆·波尔曼


22

C#7.2中,可以使用Span<T>。新System.Memory系统的好处是它不需要围绕数据进行复制。

您需要的方法是Slice

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

现在有许多方法支持SpanIReadOnlySpan,因此使用此新类型将非常简单。

请注意,在撰写本文时,Span<T>尚未在最新版本的.NET(4.7.1)中定义该类型,因此,要使用该类型,您需要从NuGet 安装System.Memory程序包


1
请注意,该Span<T>类型尚未在.Net的最新版本(4.7.1)中定义,因此要使用该类型,您需要System.Memory从NuGet 安装(在NuGet中搜索它时,请记住在“ include prerelease”之前打钩)
Matthew Watson

@MatthewWatson谢谢。我重写了您的评论,并将其添加到我的答案中。
Patrick Hofman

16

我在这里没有提到的另一种可能性:Buffer.BlockCopy()比Array.Copy()快一点,它还有一个额外的好处,就是能够从原始数组中即时转换(例如,简短[])转换为字节数组,当您需要通过套接字传输数字数组时,可以方便使用。


2
Buffer.BlockCopyArray.Copy()尽管它们接受相同的参数,但产生的结果却有所不同-有很多空元素。为什么?
jocull 2012年

7
@jocull-它们实际上并没有完全相同的参数。Array.Copy()在元素中采用其长度和位置参数。Buffer.BlockCopy()以字节为单位获取其长度和位置参数。换句话说,如果您想复制10个元素的整数数组,则可以使用Array.Copy(array1, 0, array2, 0, 10),但是Buffer.BlockCopy(array1, 0, array2, 0, 10 * sizeof(int))
肯·史密斯


14

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

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);

8

如果您不想添加LINQ或其他扩展名,请执行以下操作:

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

Error CS0246: The type or namespace name 'List<>' could not be found (are you missing a using directive or an assembly reference?) Microsoft文档是毫无希望的,因为有成百上千的“列表”条目已建立索引。这里合适的是什么?
wallyk

1
System.Collections.Generic.List
Tetralux

7

您可以在原始数组(即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

}


4
我建议将EqualityComparer.Default用于IndexOf-这样,您就不需要任何特殊的大小写。
乔恩·斯基特

1
我希望它绝对好。我当然会先使用简单的代码。
乔恩·斯基特

在我看来,这样的事情是最好的方法。但是,显然,它(比第一次)要简单得多Array.Copy,尽管它可以具有很多优点,例如,SubList实际上是父List内的一个区域,而不是List中的条目的副本,但它的工作量更大。
Aidiakapi '02

7
byte[] foo = new byte[4096]; 

byte[] bar = foo.Take(40).ToArray();


5

您可以使用Take扩展方法

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

3

这可能是一个解决方案:

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

然后结果IEnumerable <IEnumerable <字节>>,第一个IEnumerable <字节>包含foo的前40个字节,第二个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;
}

2

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

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

但是就像其他人所说的那样,如果您不需要设置起始索引,那么Take您所需要的就是全部。


1

这是一个使用泛型的扩展函数,其行为类似于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;
    }
}

1
很好,尽管来自.NET世界。如果start不在0和之间arr.Length,则可能会抛出界外异常。另外,end >= start >= 0您也无需检查end < 0,它就不可能发生。你可以通过检查可能做到这一点更加简洁length >= 0,然后len = Math.min(length, arr.Length - start)而不是与fuddling end
马修·沙利

0
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();
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.