2010-10-05 11 views
13

Me pregunto si existe una funcionalidad .NET incorporada para cambiar cada valor en una matriz en función del resultado de un delegado proporcionado. Por ejemplo, si tuviera una matriz {1,2,3} y un delegado que devuelve el cuadrado de cada valor, me gustaría poder ejecutar un método que tome la matriz y delegar, y devuelva {1,4,9}. ¿Ya existe algo como esto?C#: Alteración de valores para cada elemento en una matriz

+2

Tradicionalmente, eso se llamaría Mapa; en Linq se llama Seleccionar. –

Respuesta

19

No es que esté al tanto de (reemplazando cada elemento en lugar de convertir a un nuevo conjunto o secuencia), pero es muy fácil de escribir:

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

Uso:

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

De Por supuesto, si realmente no necesita para cambiar la matriz existente, las otras respuestas publicadas usando Select serían más funcionales. O el ConvertAll método existente de .NET 2:

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

todo esto es suponiendo una sola dimensional array. Si desea incluir matrices rectangulares, se torna más complicado, especialmente si desea evitar el boxeo.

+1

+1 para ConvertAll que en realidad es lo que el OP solicitó "método que toma el conjunto y delega, y devuelve ..." –

+2

No entiendo por qué esto supera la respuesta de Nathan para la respuesta aceptada. ¿Qué me estoy perdiendo? Select hace exactamente lo que necesita, ¿no? –

+0

@Richard: Es posible que la respuesta de Nathan fuera tan completa cuando se aceptó mi respuesta, en términos del soporte multidimensional. La forma en que leí la pregunta de OP para empezar, pensé que quería modificar la matriz en su lugar, que solo cubre mi respuesta. Para la conversión de matriz a matriz, 'Array.ConvertAll' es más eficiente y no requiere .NET 3.5. Si solo se requiere una secuencia, 'Seleccionar' está bien como se menciona en mi respuesta. –

28

LINQ proporciona soporte para proyecciones utilizando el método Select extensión:

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

También puede utilizar el poco menos fluidez Array.ConvertAll método:

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

matrices dentadas pueden ser procesados ​​por los nidos de las proyecciones:

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

Las matrices multidimensionales son un poco más complejas. He escrito el siguiente método de extensión que proyecta cada elemento en una matriz multidimensional sin importar su rango.

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

El método anterior se puede probar con una serie de rango 3 de la siguiente manera:

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

Su ejemplo probablemente debería usar un delegado en lugar de especificar la función en el lambda, para responder la pregunta más específicamente. –

+0

Muy limpio. ¿Esto tomará en cuenta las matrices multidimensionales? – JustOnePixel

+0

Ah, ahí está. Estaba buscando un método de Apply o algo así. Nunca habría pensado en llamarlo Select, pero supongo que eso concuerda con la base LINQ de todos los métodos de extensión divertidos. – jdmichal

5

Usando System.Linq que podría hacer algo como:

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

consultas LINQ podría fácilmente resuelva esto para usted - asegúrese de que está haciendo referencia a System.Core.dll y tenga un

using System.Linq; 

declaración. Por ejemplo, si usted tenía su matriz en una variable llamada numberArray, el siguiente código sería darle exactamente lo que está buscando:

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

La llamada final "ToArray" sólo es necesario si realmente necesita una matriz , y no un IEnumerable <int>.

+0

Impresionante, gracias. ¿Esto tomará en cuenta las matrices multidimensionales? – JustOnePixel

+0

No, para eso, desearía una selección anidada, como numberArrays.Select (as => as.Select (n => n * n) .ToArray()). ToArray(). Para un enfoque discutible más legible, simplemente use dos bucles anidados. – Ben

1

puede usar linq para lograr esto en forma abreviada, pero tenga cuidado, recuerde que de todos modos se produce un foreach debajo.

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

Aquí hay otra solución para las matrices M x N, donde M y N no se conocen en tiempo de compilació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

Esa es probablemente la mejor que he encontrado ... y es bueno que le hayas dado el crédito al tipo que la escribió (aunque el tipo que hizo la pregunta se cabreó no la aplicó) –

Cuestiones relacionadas