2010-10-05 25 views
13

Mi chiedo se non ci sia funzionalità .NET integrata per modificare ciascun valore in un array in base al risultato di un delegato fornito. Ad esempio, se avessi un array {1,2,3} e un delegato che restituisce il quadrato di ciascun valore, mi piacerebbe essere in grado di eseguire un metodo che prende l'array e delegare e restituisce {1,4,9}. Esiste già qualcosa di simile?C#: modifica dei valori per ogni elemento di un array

+2

Tradizionalmente, si chiamerebbe Mappa; in Linq si chiama Select. –

risposta

19

Non che io sappia (sostituzione di ogni elemento, piuttosto che la conversione a un nuovo array o sequenza), ma è incredibilmente facile da scrivere:

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

Usa:

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

Di Certo che se proprio non lo è occorre cambiare la matrice esistente, le altre risposte pubblicate utilizzando Select sarebbero più funzionali. O il ConvertAll metodo esistente da NET 2:

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

Questo è tutto ipotizzando una matrice unidimensionale. Se vuoi includere array rettangolari, diventa più complicato, soprattutto se vuoi evitare la boxe.

+1

+1 per ConvertAll che in realtà è ciò che l'OP chiedeva "metodo che prende l'array e delegare e restituisce ..." –

+2

Non capisco perché questo batte la risposta di Nathan per la risposta accettata. Cosa mi manca? Select fa esattamente ciò di cui ha bisogno, no? –

+0

@Richard: È possibile che la risposta di Nathan sia completa quando la mia risposta è stata accettata, in termini di supporto multidimensionale. Il modo in cui ho letto la domanda dell'OP per cominciare, ho pensato che voleva modificare la matrice in atto, che copre solo la mia risposta. Per la conversione da matrice ad array, 'Array.ConvertAll' è più efficiente e non richiede .NET 3.5. Se è richiesta solo una sequenza, 'Select' va bene come menzionato nella mia risposta. –

28

LINQ fornisce supporto per proiezioni utilizzando il metodo Select estensione:

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

È anche possibile utilizzare il leggermente meno scorrevole Array.ConvertAll metodo:

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

array frastagliati possono essere trattati con l'annidamento proiezioni:

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

Gli array multidimensionali sono un po 'più complessi. Ho scritto il seguente metodo di estensione che proietta ogni elemento in una matrice multidimensionale, indipendentemente dal suo grado.

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

Il metodo di cui sopra può essere testato con una serie di rango 3 come segue:

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

L'esempio dovrebbe probabilmente utilizzare un delegato piuttosto che specificare la funzione nel lambda, per rispondere alla domanda in modo più specifico. –

+0

Molto pulito. Questo prenderà in considerazione gli array multidimensionali? – JustOnePixel

+0

Ah, eccolo. Stavo cercando un metodo Apply o qualcosa del genere. Non avrei mai pensato che si chiamasse Select, ma suppongo che si accompagni alla base LINQ di tutti i divertenti metodi di estensione. – jdmichal

5

Utilizzando System.Linq si potrebbe fare qualcosa di simile:

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

query LINQ potrebbe facilmente risolvi questo problema: assicurati di fare riferimento a System.Core.dll e disponi di uno

using System.Linq; 

dichiarazione. Ad esempio, se si ha l'array in una variabile denominata numberArray, il seguente codice darebbe esattamente quello che stai cercando:

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

La chiamata finale "ToArray" è necessaria solo se è effettivamente necessario un allineamento e non un oggetto IEnumerable <int>.

+0

Fantastico, grazie. Questo prenderà in considerazione gli array multidimensionali? – JustOnePixel

+0

No - per questo, si desidera una selezione nidificata, come numberArrays.Select (as => as.Select (n => n * n) .ToArray()). ToArray(). Per un approccio probabilmente più leggibile, basta usare due anelli annidati. – Ben

1

è possibile utilizzare linq per eseguire questa operazione in stenografia, ma fare attenzione ricordare che un foreach si verifica sotto comunque.

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

Ecco un'altra soluzione per N matrici M x, dove M e N non sono noti al momento della compilazione.

// 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

Questo è probabilmente il migliore che ho trovato ... e buono che hai dato il merito al ragazzo che lo ha scritto (anche se il ragazzo che ha fatto la domanda si è incazzato non l'ha applicato) –

Problemi correlati