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

时间:2015-07-02 07:31:21

标签: c#

我想像这样使用ConvertAll:

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

但是我遇到了编译器错误:

  

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

2 个答案:

答案 0 :(得分:6)

你可以写一个简单的转换extension

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接口,因此需要使用初始强制转换将序列从IEnumerable<T>范例转换为IEnumerable<T>范例。大多数LINQ转换只适用于此。

答案 1 :(得分:0)

如果您的数组的等级未知,您可以使用此扩展方法(这取决于 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[]>());
    }
}