2010-10-05 8 views
13

Мне интересно, есть ли встроенная функция .NET для изменения каждого значения в массиве на основе результата предоставленного делегата. Например, если у меня был массив {1,2,3} и делегат, который возвращает квадрат каждого значения, я хотел бы иметь возможность запускать метод, который принимает массив и делегировать, и возвращает {1,4,9}. Что-то вроде этого уже существует?C#: Изменение значений для каждого элемента массива

+2

Традиционно это будет называться Карта; в Linq это называется Select. –

ответ

19

Не то, что я знаю (замена каждого элемента, а не преобразовывать в новый массив или последовательности), но это невероятно легко написать:

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, будут более функциональными. Или существующий ConvertAll метод из .NET 2:

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

Это все при условии, один одномерный массив. Если вы хотите включить прямоугольные массивы, становится сложнее, особенно если вы хотите избежать бокса.

+1

+1 для ConvertAll, который на самом деле является тем, что ОП запросил «метод, который принимает массив и делегирует его, и возвращает ...» –

+2

Я не понимаю, почему это бьет ответ Натана для принятого ответа. Что мне не хватает? Выбор делает именно то, что ему нужно, нет? –

+0

@ Рихард: Возможно, ответ Натана был таким же полным, когда мой ответ был принят с точки зрения многомерной поддержки. То, как я прочитал вопрос OP, чтобы начать, я думал, что он хочет изменить массив на месте, на который распространяется только мой ответ. Для преобразования массива в массив «Array.ConvertAll» более эффективен и не требует .NET 3.5. Если требуется только последовательность, 'Select' в порядке, как указано в моем ответе. –

28

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); 
     } 
+0

Ваш пример должен, вероятно, использовать делегат, а не указывать функцию в лямбда, чтобы ответить на вопрос более конкретно. –

+0

Очень аккуратный. Будет ли это учитывать многомерные массивы? – JustOnePixel

+0

Ах, вот оно. Я искал метод Apply или что-то еще. Никогда бы не подумал, что это будет имя Select, но я полагаю, что это согласуется с базой LINQ всех интересных методов расширения. – jdmichal

5

Использование System.Linq вы могли бы сделать что-то вроде:

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

LINQ запросы могут легко разрешите это для вас - убедитесь, что вы ссылаетесь на System.Core.dll и имеете

using System.Linq; 

заявление. Например, если у вас был массив в переменной с именем numberArray, следующий код даст вам именно то, что вы ищете:

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

Последний вызов «ToArray» нужен только если вы на самом деле нужен массив , а не IEnumerable <int>.

+0

Удивительный, спасибо. Будет ли это учитывать многомерные массивы? – JustOnePixel

+0

Нет - для этого вам нужен вложенный выбор, например numberArrays.Select (as => as.Select (n => n * n) .ToArray()). ToArray(). Для возможно более читаемого подхода просто используйте два вложенных цикла. – Ben

1

вы можете использовать linq для выполнения этого в стенографии, но будьте осторожны, помните, что foreach встречается в любом случае.

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

Это еще одно решение для массивов 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); 
     } 
    } 
+0

Thats, наверное, лучший из тех, что я нашел ... и хорошо, что вы дали кредит парню, который его написал (хотя парень, который задал вопрос, разозлился, он не применил его) –

Смежные вопросы