Question

J'ai une double chaîne contenant csv (par exemple « 0.3,0.4,0.3 »), et je veux être en mesure de produire un tableau à double contenant la somme cumulée de ces chiffres (par exemple [0.3,0.7,1.0]).

Jusqu'à présent, je

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

qui donne les chiffres comme un tableau, mais pas la somme cumulée des chiffres.

Est-il possible de continuer cette expression pour obtenir ce que je veux, ou dois-je utiliser itération pour créer un nouveau tableau à partir du tableau que je l'ai déjà?

Était-ce utile?

La solution

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

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

Autres conseils

Il y a un temps pour la généralité, et il y a un temps pour résoudre le problème posé en fait. Ceci est l'un des derniers temps. Si vous souhaitez effectuer une méthode qui transforme une séquence de doubles en une séquence de sommes partielles, alors faites juste que:

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

facile. Pas déconner avec des agrégats et des requêtes compliquées et ainsi de suite. Facile à comprendre, facile à déboguer, facile à utiliser:

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

Maintenant, je note que si cela est entrée utilisateur alors double.Parse peut lancer une exception; il pourrait être une meilleure idée de faire quelque chose comme:

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

et maintenant vous ne recevez pas une exception si l'utilisateur fait une erreur de frappe; vous obtenez des valeurs NULL.

J'ai eu une exigence similaire il y a quelque temps. En fait, je devais faire une agrégation, mais je devais aussi sélectionner chaque valeur intermédiaire. J'ai donc écrit une méthode d'extension du nom SelectAggregate (probablement pas le nom le plus approprié, mais je ne pouvais pas trouver quelque chose de mieux) qui peut être utilisé comme ça:

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

Voici le code:

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

Vous voulez utiliser l'opérateur Aggregate, avec un List<double> comme l'accumulateur d'agrégation. De cette façon, vous pouvez produire une projection qui est lui-même une séquence de sommes.

Voici un exemple pour vous aider à démarrer:

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

Pourquoi est-il besoin d'être 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]);

Tout d'abord je ne pense pas que ce soit une bonne tâche pour Linq. vieux foreach fera mieux plaine. Mais comme un casse-tête, il est très bien.

Première idée était de sous-requêtes d'utilisation, mais je ne l'aime pas, car il est O (n ^ 2). Voici ma solution linéaire:

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

RX utilisation:

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

Ceci est en fait assez simple de généraliser en utilisant un générateur. Voici une nouvelle méthode d'extension appelée Accumulate qui fonctionne comme une combinaison de Select et Aggregate. Elle retourne une nouvelle séquence en appliquant une fonction binaire à chaque élément dans la séquence et la valeur accumulée jusqu'à présent.

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

Voici façon de le faire en utilisant 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

somme cumulative pour 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; });
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top