J'ai les données suivantes.

new Dictionary<DateTime, Dictionary<string, int>>() 
{
    { 
        new DateTime(2020,05,05,10,10,10) , 
        new Dictionary<string, int>() 
        {
            { "Value1", 2 },
            { "Value2", 4 },
            { "ValueXY", 6 },
        }
    },
    { 
        new DateTime(2020,05,05,10,10,12) , 
        new Dictionary<string, int>() 
        {
            { "Value1", 4 },
            { "Value2", 6 },
            { "ValueABC", 12 }
        }
    }
};

Ce que je veux faire avec LINQ, c'est grouper par date / heure, sans les secondes, pour avoir un package par minute. Additionnez la valeur moyenne des clés uniques.

Donc, avec l'exemple ci-dessus, j'obtiendrais ceci

new Dictionary<DateTime, Dictionary<string, int>>()
{
    { 
        new DateTime(2020,05,05,10,10,0) , 
        new Dictionary<string, int>()
        {
            { "Value1", 3 },
            { "Value2", 5 },
            { "ValueABC", 12 },
            { "ValueXY", 6 }
        }
    }
};

J'ai pu faire le regroupement, mais je ne fais pas fonctionner la partie moyenne.

categorie.Value.GroupBy(row => row.Key.AddSeconds(-row.Key.Second));

Des idees pour faire cela?

2
DangerSchwob 5 mai 2020 à 17:05

4 réponses

Meilleure réponse

Vous avez terminé le regroupement, mais ce qui vous manque, c'est la combinaison et la moyenne plus compliquées des valeurs dans les sous-dictionnaires. Vous pouvez le faire de la manière suivante.

var x =new Dictionary<DateTime, Dictionary<string, int>>() 
{
    { 
        new DateTime(2020,05,05,10,10,10) , 
        new Dictionary<string, int>() 
        {
            { "Value1", 2 },
            { "Value2", 4 },
            { "ValueXY", 6 },
        }
    },
    { 
        new DateTime(2020,05,05,10,10,12) , 
        new Dictionary<string, int>() 
        {
            { "Value1", 4 },
            { "Value2", 6 },
            { "ValueABC", 12 }
        }
    }
};

var res = x.GroupBy(kvp => 
        kvp.Key.AddSeconds(-kvp.Key.Second).AddMilliseconds(-kvp.Key.Millisecond))
    .ToDictionary(
        grp => grp.Key,
        grp => grp.SelectMany(kvp => kvp.Value)
            .GroupBy(kvp => kvp.Key)
            .ToDictionary(
                grpInner => grpInner.Key, 
                grpInner => grpInner.Average(kvp => kvp.Value)));

Cela fera votre regroupement, puis créera un dictionnaire basé sur le DateTime sans secondes, puis il aplatira toutes les paires clé / valeur dans les dictionnaires internes, puis regroupera sur ces valeurs clés et générera la moyenne.

Edit: Comme l'a noté Vadim Martynov, vous devez également prendre soin de tronquer les millisecondes.

2
juharr 5 mai 2020 à 15:03

Peut également utiliser {{X0 }} pour regrouper la partie Date de la clé datetime, puis ajoutez les heures avec DateTime.AddHours et minutes avec DateTime.AddMinutes:

var result = categorie
    .ToLookup(
        kvp => kvp.Key.Date
            .AddHours(kvp.Key.Hour)
            .AddMinutes(kvp.Key.Minute),
        kvp => kvp.Value)
    .ToDictionary(
        group => group.Key,
        group => group
            .SelectMany(group => group)
            .ToLookup(
                kvp => kvp.Key, 
                kvp => kvp.Value)
            .ToDictionary(
                group => group.Key, 
                group => group.Average()));

Lequel convertit d'abord les groupes en premier externe Dictionary<DateTime...> en utilisant Enumerable.ToDictionary, aplatit les valeurs internes avec Enumerable.SelectMany, crée des recherches de clés et de valeurs à l'aide de ToLookup, puis crée le Dictionary<string, int> interne et obtient les moyennes avec { {X5}}. Le résultat final sera Dictionary<DateTime, Dictionary<string, double>>.

0
RoadRunner 5 mai 2020 à 16:51

Il y a 3 problèmes dans votre code.

  1. AddSeconds ne fonctionnera pas avec DateTime contient des millisecondes, vous pouvez donc utiliser méthodes plus génériques pour le tour.
  2. Vous groupez uniquement par valeur DateTime et non par clés de dictionnaires internes.
  3. La valeur moyenne de 2 valeurs entières n'est pas un entier (par exemple, moy pour 2 et 3 est 2.5), vous devez donc l'arrondir ou la convertir en entier si vous êtes sûr que le résultat est un entier ou utilisez plutôt double.

Voici un code complet pour votre problème:

DateTime RoundDown(DateTime dt, TimeSpan d)
{
    var delta = dt.Ticks % d.Ticks;
    return new DateTime(dt.Ticks - delta, dt.Kind);
}

Dictionary<DateTime, Dictionary<string, double>> result = categorie
    .GroupBy(kvp => RoundDown(kvp.Key, new TimeSpan(0, 0, 1, 0)), kvp => kvp.Value) // group by DateTime
    .ToDictionary( // retrieve result dictionary
        grouping => grouping.Key, // use DateTime from grouping as the key
        grouping => grouping.SelectMany(g => g) // flatten inner dictionaries to the IEnumerable<KeyValuePair<string, int>>
            .GroupBy(g => g.Key, g => g.Value) // group by string keys
            .ToDictionary(g => g.Key, g => g.Average())); // convert to the inner result dictionary with avg values
2
Vadim Martynov 5 mai 2020 à 14:54

Vous pouvez d'abord aplatir les dictionnaires imbriqués, pour les regrouper par date sans deuxième clé de dictionnaire imbriquée

var dict = new Dictionary<DateTime, Dictionary<string, int>>()
{
    { new DateTime(2020,05,05,10,10,10) , new Dictionary<string, int>()
        {
            { "Value1", 2 },
            { "Value2", 4 },
            { "ValueXY", 6 },
        }
    },
    { new DateTime(2020,05,05,10,10,12) , new Dictionary<string, int>()
        {
            { "Value1", 4 },
            { "Value2", 6 },
            { "ValueABC", 12 }
        }
    }
};

var flatten = dict.SelectMany(d => d.Value,
    (parent, child) => new { Date = parent.Key, Name = child.Key, Value = child.Value });
var result = flatten.GroupBy(x => new { Date = x.Date.AddSeconds(-x.Date.Second), x.Name })
    .Select(g => new { g.Key.Date, g.Key.Name, Avg = g.Average(i => i.Value) })
    .ToLookup(x => x.Date, x => new { x.Name, x.Avg })
    .ToDictionary(x => x.Key, x => x.ToDictionary(i => i.Name, i => i.Avg));

Sélectionnez ensuite la valeur moyenne pour chaque combinaison de date et de nom, et convertissez-la en dictionnaire de résultats par conversion en recherche. Je ne peux pas dire que c'est la meilleure solution, mais cela vous donne le résultat souhaité

0
Pavel Anikhouski 5 mai 2020 à 14:52