У меня есть следующие данные.

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

Что я хочу сделать с LINQ, так это сгруппировать по DateTime, без секунд, чтобы получить пакет в минуту. Дополнительно среднее значение для отдельных ключей.

Итак, в приведенном выше примере я бы получил это

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

Я был в состоянии сделать группировку, но у меня не работает средняя часть.

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

Есть идеи, как это сделать?

2
DangerSchwob 5 Май 2020 в 17:05

4 ответа

Лучший ответ

Вы сделали группировку, но вам не хватает более сложного объединения и усреднения значений в под-словарях. Вы можете сделать это следующим образом.

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

Это сделает вашу группировку, а затем создаст словарь на основе DateTime без секунд, а затем сгладит все пары значений ключа во внутренних словарях, затем сгруппирует по этим значениям ключа и сгенерирует среднее значение.

Изменить: Как отметил Вадим Мартынов, вы также должны позаботиться об обрезании миллисекунд.

2
juharr 5 Май 2020 в 15:03

Можно также использовать {{X0 }} для группировки Date части ключа даты и времени, а затем добавьте часы с помощью DateTime.AddHours и минуты с 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()));

Который первым преобразует группы в первый внешний Dictionary<DateTime...>, используя Enumerable.ToDictionary выравнивает внутренние значения с помощью Enumerable.SelectMany, создает поиск ключей и значений с помощью ToLookup, затем создает внутренний Dictionary<string, int> и получает средние значения с помощью Enumerable.Average. Окончательный результат будет Dictionary<DateTime, Dictionary<string, double>>.

0
RoadRunner 5 Май 2020 в 16:51

В вашем коде 3 проблемы.

  1. AddSeconds не будет работать с DateTime, содержит миллисекунды, поэтому вы можете использовать более общие методы для раунда.
  2. Вы группируете только по значению DateTime, а не по ключам внутренних словарей.
  3. Среднее значение для двух целочисленных значений не является целым числом (например, avg для 2 и 3 равно 2.5), поэтому вы должны округлить его или привести к целому числу, если уверены, что результатом является целое число или используйте double вместо этого.

Вот полный код вашей проблемы:

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 Май 2020 в 14:54

Вы можете сначала выровнять вложенные словари, чтобы сгруппировать по дате без второго и вложенного ключа словаря.

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

Затем выберите среднее значение для каждой комбинации даты и имени и преобразуйте его в словарь результатов путем преобразования в поиск. Я не могу сказать, что это лучшее решение, однако оно дает желаемый результат

0
Pavel Anikhouski 5 Май 2020 в 14:52