【问题标题】:C#: Altering values for every item in an arrayC#:更改数组中每个项目的值
【发布时间】:2011-04-21 13:03:29
【问题描述】:

我想知道是否有内置的 .NET 功能可以根据提供的委托的结果更改数组中的每个值。例如,如果我有一个数组{1,2,3} 和一个返回每个值的平方的委托,我希望能够运行一个获取数组和委托并返回{1,4,9} 的方法。类似的东西已经存在了吗?

【问题讨论】:

  • 传统上,这将被称为地图;在 Linq 中称为 Select。

标签: c# linq arrays delegates projection


【解决方案1】:

LINQ 使用 Select 扩展方法提供对投影的支持:

var numbers = new[] {1, 2, 3};
var squares = numbers.Select(i => i*i).ToArray();

你也可以使用不太流畅的Array.ConvertAll方法:

var squares = Array.ConvertAll(numbers, i => i*i);

可以通过嵌套投影来处理锯齿状数组:

var numbers = new[] {new[] {1, 2}, new[] {3, 4}};
var squares = numbers.Select(i => i.Select(j => j*j).ToArray()).ToArray();

多维数组稍微复杂一些。我编写了以下扩展方法,它将每个元素投影到多维数组中,无论其排名如何。

static Array ConvertAll<TSource, TResult>(this Array source,
                                          Converter<TSource, TResult> projection)
{
    if (!typeof (TSource).IsAssignableFrom(source.GetType().GetElementType()))
    {
        throw new ArgumentException();
    }
    var dims = Enumerable.Range(0, source.Rank)
        .Select(dim => new {lower = source.GetLowerBound(dim),
                            upper = source.GetUpperBound(dim)});
    var result = Array.CreateInstance(typeof (TResult),
        dims.Select(dim => 1 + dim.upper - dim.lower).ToArray(),
        dims.Select(dim => dim.lower).ToArray());
    var indices = dims
        .Select(dim => Enumerable.Range(dim.lower, 1 + dim.upper - dim.lower))
        .Aggregate(
            (IEnumerable<IEnumerable<int>>) null,
            (total, current) => total != null
                ? total.SelectMany(
                    item => current,
                    (existing, item) => existing.Concat(new[] {item}))
                : current.Select(item => (IEnumerable<int>) new[] {item}))
        .Select(index => index.ToArray());
    foreach (var index in indices)
    {
        var value = (TSource) source.GetValue(index);
        result.SetValue(projection(value), index);
    }
    return result;
}

上面的方法可以用秩为3的数组进行测试,如下:

var source = new int[2,3,4];

for (var i = source.GetLowerBound(0); i <= source.GetUpperBound(0); i++)
    for (var j = source.GetLowerBound(1); j <= source.GetUpperBound(1); j++)
        for (var k = source.GetLowerBound(2); k <= source.GetUpperBound(2); k++)
            source[i, j, k] = i*100 + j*10 + k;

var result = (int[,,]) source.ConvertAll<int, int>(i => i*i);

for (var i = source.GetLowerBound(0); i <= source.GetUpperBound(0); i++)
    for (var j = source.GetLowerBound(1); j <= source.GetUpperBound(1); j++)
        for (var k = source.GetLowerBound(2); k <= source.GetUpperBound(2); k++)
        {
            var value = source[i, j, k];
            Debug.Assert(result[i, j, k] == value*value);
        }

【讨论】:

  • 您的示例可能应该使用委托而不是在 lambda 中指定函数,以便更具体地回答问题。
  • 非常整洁。这会考虑多维数组吗?
  • 啊,就是这样。我一直在寻找 Apply 方法或其他东西。永远不会想到它被命名为 Select,但我想这与所有有趣的扩展方法的 LINQ 基础相一致。
【解决方案2】:

我不知道(替换每个元素而不是转换为新的数组或序列),但它非常容易编写:

public static void ConvertInPlace<T>(this IList<T> source, Func<T, T> projection)
{
    for (int i = 0; i < source.Count; i++)
    {
        source[i] = projection(source[i]);
    }
}

用途:

int[] values = { 1, 2, 3 };
values.ConvertInPlace(x => x * x);

当然,如果您真的 需要 更改现有数组,则使用 Select 发布的其他答案会更实用。或者 .NET 2 中现有的 ConvertAll 方法:

int[] values = { 1, 2, 3 };
values = Array.ConvertAll(values, x => x * x);

这都是假设一个一维数组。如果你想包含矩形数组,它会变得更棘手,特别是如果你想避免装箱。

【讨论】:

  • +1 for ConvertAll 这实际上是 OP 要求的“获取数组和委托并返回...的方法”
  • 我不明白为什么这比内森对已接受答案的回答要好。我错过了什么? Select 正是他所需要的,不是吗?
  • @Richard:就多维支持而言,当我的回答被接受时,Nathan 的回答可能是一样完整的。我开始阅读 OP 问题的方式是,我认为他想就地修改数组,这只有我的回答涵盖。对于数组到数组的转换,Array.ConvertAll 效率更高,并且不需要 .NET 3.5。如果只需要一个序列,Select 就可以了,正如我的回答中提到的那样。
【解决方案3】:

使用 System.Linq,您可以执行以下操作:

var newArray = arr.Select(x => myMethod(x)).ToArray();

【讨论】:

    【解决方案4】:

    LINQ 查询可以轻松为您解决这个问题 - 确保您引用 System.Core.dll 并拥有一个

    using System.Linq;
    

    声明。例如,如果您将数组放在名为 numberArray 的变量中,则以下代码将为您提供所需的内容:

     var squares = numberArray.Select(n => n * n).ToArray();
    

    仅当您确实需要一个数组而不是 IEnumerable 时才需要最后的“ToArray”调用。

    【讨论】:

    • 太棒了,谢谢。这会考虑多维数组吗?
    • 否 - 为此,您需要一个嵌套选择,例如 numberArrays.Select(as => as.Select(n => n * n).ToArray()).ToArray()。对于可以说更具可读性的方法,只需使用两个嵌套循环。
    【解决方案5】:

    您可以使用 linq 以简写方式完成此操作,但请注意,无论如何都会在下面发生 foreach。

    int[] x =  {1,2,3};
    x = x.Select(( Y ) => { return Y * Y; }).ToArray();
    

    【讨论】:

      【解决方案6】:

      这是 M x N 数组的另一种解决方案,其中 M 和 N 在编译时是未知的。

          // credit: https://blogs.msdn.microsoft.com/ericlippert/2010/06/28/computing-a-cartesian-product-with-linq/
          public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(IEnumerable<IEnumerable<T>> sequences)
          {
              IEnumerable<IEnumerable<T>> result = new[] { Enumerable.Empty<T>() };
              foreach (var sequence in sequences)
              {
                  // got a warning about different compiler behavior
                  // accessing sequence in a closure
                  var s = sequence;
                  result = result.SelectMany(seq => s, (seq, item) => seq.Concat<T>(new[] { item }));
              }
              return result;
          }
      
      
          public static void ConvertInPlace(this Array array, Func<object, object> projection)
          {
              if (array == null)
              {
                  return;
              }
      
              // build up the range for each dimension
              var dimensions = Enumerable.Range(0, array.Rank).Select(r => Enumerable.Range(0, array.GetLength(r)));
      
              // build up a list of all possible indices
              var indexes = EnumerableHelper.CartesianProduct(dimensions).ToArray();
      
              foreach (var index in indexes)
              {
                  var currentIndex = index.ToArray();
                  array.SetValue(projection(array.GetValue(currentIndex)), currentIndex);
              }
          }
      

      【讨论】:

      • 这可能是我找到的最好的一个......很好,你把功劳归功于写它的人(即使问这个问题的人很生气,他没有应用它)
      猜你喜欢
      • 2016-06-23
      • 2021-12-28
      • 1970-01-01
      • 1970-01-01
      • 2020-08-11
      • 1970-01-01
      • 2020-07-12
      • 1970-01-01
      • 2014-10-05
      相关资源
      最近更新 更多