Pregunta

Tengo una cadena CSV que contiene dobles (por ejemplo, "0.3,0.4,0.3"), y quiero poder generar una matriz doble que contiene la suma acumulativa de estos números (por ejemplo, [0.3,0.7,1.0]).

Hasta ahora 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 acumulativa de los números.

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

¿Fue útil?

Solución

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

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

Otros consejos

Hay un tiempo para la generalidad, y hay un tiempo para resolver el problema que realmente plantea. Esta es una de las últimas veces. Si desea hacer un método que convierta una secuencia de dobles en una secuencia de sumas parciales, entonces hágalo:

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

Fácil. No se metía con agregados y consultas complicadas y otras cosas. 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, noto que si esa es la entrada del usuario, entonces el doble. El parente puede lanzar una excepción; Puede ser una mejor idea 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 no obtiene una excepción si el usuario comete un error de escritura; obtienes nulos.

Tenía un requisito similar hace algún tiempo. Básicamente, necesitaba hacer una agregación, pero también necesitaba seleccionar cada valor intermedio. Entonces escribí un método de extensión llamado SelectAggregate (probablemente no sea el nombre más apropiado, pero no pude encontrar nada mejor que) que se puede usar así:

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

Quieres usar el Aggregate operador, con un List<double> Como acumulador de agregación. De esa manera, puede producir una proyección que es una secuencia de sumas.

Aquí hay un ejemplo para comenzar:

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();

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

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

La primera idea era usar subconsules, 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
            );

usa rx:

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

En realidad, esto es bastante sencillo para generalizar el uso del generador. Aquí hay un nuevo método de extensión llamado Accumulate que funciona como una combinación de Select y Aggregate. Devuelve una nueva secuencia aplicando una función binaria a cada elemento en la secuencia y el valor acumulado hasta ahora.

 public static class EnumerableHelpers 
 {
    public static IEnumerable<U> Accumulate<T, U>(this IEnumerable<T> self, U init, Func<U, T, U> f) 
    {
        foreach (var x in self)
            yield return init = f(init, x);
    }

    public static IEnumerable<T> Accumulate<T>(this IEnumerable<T> self, Func<T, T, T> f)
    {
        return self.Accumulate(default(T), f);
    }

    public static IEnumerable<double> PartialSums(this IEnumerable<double> self)
    {
        return self.Accumulate((x, y) => x + y);
    }

    public static IEnumerable<int> PartialSums(this IEnumerable<int> self)
    {
        return self.Accumulate((x, y) => x + y);
    }
 }

Aquí es a forma de hacerlo usando 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:

  • 4
  • 5.9
  • 10
  • 12.9

Suma acumulativa para List<double>:

var nums = new List<double>() { 0.3, 0.0, 0.4, 1.1 };
var cumsum = nums.Aggregate(new List<double> (), 
              (list, next) => { list.Add(list.LastOrDefault() + next); return list; });
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top