2011-01-28 63 views
13

Tengo una cadena csv que contiene dobles (por ejemplo, "0.3,0.4,0.3"), y quiero poder generar una matriz doble que contenga la suma acumulativa de estos números (p. ej. [0.3,0.7,1.0]).usando LINQ para encontrar la suma acumulativa de una matriz de números en C#

Hasta el momento, no tengo

double[] probabilities = textBox_f.Text.Split(new char[]{','}).Select(s => double.Parse(s)).ToArray();

que da los números como una matriz, pero no la suma acumulada de los números.

¿Hay alguna forma de continuar esta expresión para obtener lo que quiero, o necesito usar iteración para crear una nueva matriz a partir de la matriz que ya tengo?

+2

me gusta aprender nuevas tecnologías y formas de hacer las cosas. Es perfectamente posible que otras formas sean mejores o más rápidas, pero esto es algo que no sé cómo hacer, y por eso me gustaría saber – simonalexander2005

+0

¿Y por qué? Si una solución sin LINQ es más rápida para escribir * y * más rápido para ejecutar, ¿por qué debería estar interesado en la solución LINQ? Y por qué LINQ específicamente, de todos modos, ¿por qué no preguntar acerca de una solución que usa genéricos o '' dinámicos', o cualquier otra característica aleatoria que no es necesaria para responder la pregunta? – Timwi

+0

'Split (new char [] {','})' se puede escribir de forma equivalente como 'Split (',')' ya que el parámetro se declara con 'params'. –

Respuesta

-3
var input=new double[]{ ... } 
double sum=0; 

var output=input 
    .Select(w=>sum+=w); 
+0

Modifiqué esto a: prob = textBox_prob.Text.Split (new char [] {','}). Select (s => double.Parse (s)). ToArray(). Select (w => suma + = w) .ToArray(); que parecía más conciso. De lo contrario genial, gracias :) – simonalexander2005

+5

El uso de consultas LINQ solo por sus efectos secundarios es una mala idea por varias razones, entre las que destaca el hecho de que los futuros lectores del código no esperarán esto ... viola el principio de menos sorpresa. – LBushkin

+0

tan simple y tan bueno. definitivamente mejor que el mío – Andrey

5

Desea utilizar el operador Aggregate, con List<double> como acumulador de agregación. De esta forma, puedes producir una proyección que es en sí misma una secuencia de sumas.

He aquí un ejemplo para empezar:

double[] runningTotal = textBox_f.Text 
      .Split(new char[]{','}) 
      .Select(s => double.Parse(s)) 
      .Aggregate((IEnumerable<double>)new List<double>(), 
         (a,i) => a.Concat(new[]{a.LastOrDefault() + i})) 
      .ToArray(); 
+0

esto es ineficiente, calculará Suma cada vez que lo haga O (n^2) – Andrey

+1

No compila. Por favor, pruebe su código antes de publicar :( – Timwi

+0

Excelente respuesta, y me hizo buscar en los agregados, pero al final elegí otro para usar. Gracias a – simonalexander2005

2

¿Por qué necesita ser LINQ?

var cumulative = new double[probabilities.Length]; 
for (int i = 0; i < probabilities.Length; i++) 
    cumulative[i] = probabilities[i] + (i == 0 ? 0 : cumulative[i-1]); 
+0

Quizás por la misma razón tiene que ser la suma acumulativa, una matriz de números y C#? –

15

Tuve un requerimiento similar hace algún tiempo. Básicamente, necesitaba hacer una agregación, pero también necesitaba seleccionar cada valor intermedio. Así que escribí un método de extensión llamado SelectAggregate (probablemente no es el nombre más adecuado, pero no pude encontrar nada mejor entonces) que puede ser utilizado como esa:

double[] numbers = new [] { 0.3, 0.4, 0.3 }; 
double[] cumulativeSums = numbers.SelectAggregate(0.0, (acc, x) => acc + x).ToArray(); 

Aquí está el código:

public static IEnumerable<TAccumulate> SelectAggregate<TSource, TAccumulate>(
     this IEnumerable<TSource> source, 
     TAccumulate seed, 
     Func<TAccumulate, TSource, TAccumulate> func) 
    { 
     source.CheckArgumentNull("source"); 
     func.CheckArgumentNull("func"); 
     return source.SelectAggregateIterator(seed, func); 
    } 

    private static IEnumerable<TAccumulate> SelectAggregateIterator<TSource, TAccumulate>(
     this IEnumerable<TSource> source, 
     TAccumulate seed, 
     Func<TAccumulate, TSource, TAccumulate> func) 
    { 
     TAccumulate previous = seed; 
     foreach (var item in source) 
     { 
      TAccumulate result = func(previous, item); 
      previous = result; 
      yield return result; 
     } 
    } 
+1

+1 - buena solución que también enseña cómo escribir código reutilizable! – Timwi

3

En primer lugar, no creo que sea una buena tarea para Linq. Plain old foreach lo hará mejor. Pero como un rompecabezas, está bien.

La primera idea fue utilizar subconsultas, pero no me gusta, porque es O (n^2). Aquí está mi solución lineal:

 double[] probabilities = new double[] { 0.3, 0.4, 0.3}; 
     probabilities 
      .Aggregate(
       new {sum=Enumerable.Empty<double>(), last = 0.0d}, 
       (a, c) => new { 
        sum = a.sum.Concat(Enumerable.Repeat(a.last+c,1)), 
        last = a.last + c 
       }, 
       a => a.sum 
      ); 
1

aquí es una manera de hacerlo utilizando LINQ:

double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 }; 
var doublesSummed = new List<double>(); 

Enumerable.Aggregate(doubles, (runningSum, nextFactor) => { 
    double currentSum = runningSum + nextFactor; 
    doublesSummed.Add(currentSum); 
    return currentSum; 
}); 

doublesSummed.Dump(); 

En LINQPad:

  • 5,9
  • 12.9
40

Hay un tiempo para la generalidad, y hay un tiempo para resolver el problema realmente planteado. Este es uno de los últimos tiempos. Si desea realizar un método que convierte una secuencia de dobles en una secuencia de sumas parciales, a continuación, sólo hacer eso:

public static IEnumerable<double> CumulativeSum(this IEnumerable<double> sequence) 
{ 
    double sum = 0; 
    foreach(var item in sequence) 
    { 
     sum += item; 
     yield return sum; 
    }   
} 

fácil. Sin problemas con los agregados y las consultas complicadas y todo eso.Fácil de entender, fácil de depurar, fácil de usar:

textBox_f.Text 
    .Split(new char[]{','}) 
    .Select(s => double.Parse(s)) 
    .CumulativeSum() 
    .ToArray(); 

Ahora, quiero señalar que si esa es la entrada del usuario a continuación double.Parse puede lanzar una excepción; que podría ser una mejor idea de hacer algo como:

public static double? MyParseDouble(this string s) 
{ 
    double d; 
    if (double.TryParse(s, out d)) 
     return d; 
    return null; 
} 

public static IEnumerable<double?> CumulativeSum(this IEnumerable<double?> sequence) 
{ 
    double? sum = 0; 
    foreach(var item in sequence) 
    { 
     sum += item; 
     yield return sum; 
    }   
} 
... 
textBox_f.Text 
    .Split(new char[]{','}) 
    .Select(s => s.MyParseDouble()) 
    .CumulativeSum() 
    .ToArray(); 

y ahora usted no recibe una excepción si el usuario comete un error de mecanografía; obtienes nulos

1

uso RX:

var input=new double[]{ ... } 
var output = new List<double>(); 
input.ToObservable().Scan((e, f) => f + e).Subscribe(output.Add); 
+1

Si bien es un buen código, requiere una importación adicional – simonalexander2005

Cuestiones relacionadas