如何使用ConvertAll()转换多秩数组

本文关键字:数组 转换 何使用 ConvertAll | 更新日期: 2023-09-27 18:00:41

我想像这样使用ConvertAll:

 var sou = new[,] { { true, false, false }, { true, true, true } };
 var tar = Array.ConvertAll<bool, int>(sou, x => (x ? 1 : 0));

但我得到了编译器错误:

无法将类型bool[]隐式转换为bool[]

如何使用ConvertAll()转换多秩数组

您可以编写一个简单的转换扩展:

public static class ArrayExtensions
{
    public static TResult[,] ConvertAll<TSource, TResult>(this TSource[,] source, Func<TSource, TResult> projection)
    {
        if (source == null)
            throw new ArgumentNullException("source");
        if (projection == null)
            throw new ArgumentNullException("projection");  
        var result = new TResult[source.GetLength(0), source.GetLength(1)];
        for (int x = 0; x < source.GetLength(0); x++)
            for (int y = 0; y < source.GetLength(1); y++)
                result[x, y] = projection(source[x, y]);
        return result;
    }
}

示例用法如下:

var tar = sou.ConvertAll(x => x ? 1 : 0);

不利的一面是,如果你想做投影之外的任何其他变换,你会陷入困境。


或者,如果您希望能够在序列上使用LINQ运算符,则可以使用常规LINQ方法轻松地做到这一点。然而,您仍然需要一个自定义实现来将序列转换回2D阵列:

public static T[,] To2DArray<T>(this IEnumerable<T> source, int rows, int columns)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (rows < 0 || columns < 0)
        throw new ArgumentException("rows and columns must be positive integers.");
    var result = new T[rows, columns];
    if (columns == 0 || rows == 0)
        return result;            
    int column = 0, row = 0;
    foreach (T element in source)
    {
        if (column >= columns)
        {
            column = 0;                    
            if (++row >= rows)                    
                throw new InvalidOperationException("Sequence elements do not fit the array.");                         
        }
        result[row, column++] = element;                
    }
    return result;
}

这将允许更大的灵活性,因为您可以将源数组作为IEnumerable{T}序列进行操作。

示例用法:

var tar = sou.Cast<bool>().Select(x => x ? 1 : 0).To2DArray(sou.GetLength(0), sou.GetLength(1));

注意,由于多维数组不实现通用IEnumerable<T>接口,因此需要初始强制转换才能将序列从IEnumerable范式转换为IEnumerable<T>范式。大多数LINQ转换仅适用于此。

如果您的数组的秩未知,您可以使用此扩展方法(这取决于MoreLinq-Nuget包)。我相信这可以优化很多,不过,这对我来说是有效的

using MoreLinq;
using System;
using System.Collections.Generic;
using System.Linq;
public static class ArrayExtensions
{
    public static Array ConvertAll<TOutput>(this Array array, Converter<object, TOutput> converter)
    {
        foreach (int[] indices in GenerateIndices(array))
        {
            array.SetValue(converter.Invoke(array.GetValue(indices)), indices);
        }
        return array;
    }
    private static IEnumerable<int[]> GenerateCartesianProductOfUpperBounds(IEnumerable<int> upperBounds, IEnumerable<int[]> existingCartesianProduct)
    {
        if (!upperBounds.Any())
            return existingCartesianProduct;
        var slice = upperBounds.Slice(0, upperBounds.Count() - 1);
        var rangeOfIndices = Enumerable.Range(0, upperBounds.Last() + 1);
        IEnumerable<int[]> newCartesianProduct;
        if (existingCartesianProduct.Any())
            newCartesianProduct = rangeOfIndices.Cartesian(existingCartesianProduct, (i, p1) => new[] { i }.Concat(p1).ToArray()).ToArray();
        else
            newCartesianProduct = rangeOfIndices.Select(i => new int[] { i }).ToArray();
        return GenerateCartesianProductOfUpperBounds(slice, newCartesianProduct);
    }
    private static IEnumerable<int[]> GenerateIndices(Array array)
    {
        var upperBounds = Enumerable.Range(0, array.Rank).Select(r => array.GetUpperBound(r));
        return GenerateCartesianProductOfUpperBounds(upperBounds, Array.Empty<int[]>());
    }
}