【问题标题】:Replace repeated values in collection with its sum用总和替换集合中的重复值
【发布时间】:2018-09-27 12:09:40
【问题描述】:

我有一个自定义类ModeTime的列表,其结构如下:

private class ModeTime
{
    public DateTime Date { get; set; }
    public string LineName { get; set; }
    public string Mode { get; set; }
    public TimeSpan Time { get; set; }
}

在这个列表中我有一些项目,它们的LineNameMode 是相同的,它们被一一写在列表中。我需要对这些项目的Time 属性求和,并将其替换为一个具有Time 属性总和的项目而不更改LineNameModeDate 应该取自替换项目的第一个。下面我举个例子:

Original:                                   Modified:
Date       | LineName | Mode   | Time               Date | LineName | Mode   | Time
01.09.2018 | Line1    | Auto   | 00:30:00     01.09.2018 | Line1    | Auto   | 00:30:00
01.09.2018 | Line2    | Auto   | 00:10:00     01.09.2018 | Line2    | Auto   | 00:15:00
01.09.2018 | Line2    | Auto   | 00:05:00     01.09.2018 | Line2    | Manual | 00:02:00
01.09.2018 | Line2    | Manual | 00:02:00     01.09.2018 | Line2    | Auto   | 00:08:00
01.09.2018 | Line2    | Auto   | 00:08:00     01.09.2018 | Line1    | Manual | 00:25:00
01.09.2018 | Line1    | Manual | 00:25:00     01.09.2018 | Line2    | Auto   | 00:24:00
01.09.2018 | Line2    | Auto   | 00:05:00     02.09.2018 | Line1    | Auto   | 00:05:00
02.09.2018 | Line2    | Auto   | 00:12:00
02.09.2018 | Line2    | Auto   | 00:07:00
02.09.2018 | Line1    | Auto   | 00:05:00

我已经尝试写了方法来做到这一点,它部分工作,但一些未总结的项目仍然存在。

private static List<ModeTime> MergeTime(List<ModeTime> modeTimes)
{
    modeTimes = modeTimes.OrderBy(e => e.Date).ToList();
    var mergedModeTimes = new List<ModeTime>();

    for (var i = 0; i < modeTimes.Count; i++)
    {
        if (i - 1 != -1)
        {
            if (modeTimes[i].LineName == modeTimes[i - 1].LineName &&
                modeTimes[i].Mode == modeTimes[i - 1].Mode)
            {
                mergedModeTimes.Add(new ModeTime
                {
                    Date = modeTimes[i - 1].Date,
                    LineName = modeTimes[i - 1].LineName,
                    Mode = modeTimes[i - 1].Mode,
                    Time = modeTimes[i - 1].Time + modeTimes[i].Time
                });
                i += 2;
            }
            else
            {
                mergedModeTimes.Add(modeTimes[i]);
            }
        }
        else
        {
            mergedModeTimes.Add(modeTimes[i]);
        }
    } 

    return mergedModeTimes;
}

我还尝试用do {} while() 包装for 并减少源列表modeTimes 的长度。不幸的是,它会导致循环和内存泄漏(我等到 5GB 内存使用)。

希望有人可以帮助我。我搜索了这个问题,在一些熟悉的情况下人们使用GroupBy。但我不认为它适用于我的情况,我必须用相同的LineNameMode 对项目求和,前提是它们一一在列表中。

【问题讨论】:

  • 从不、从不、从不增加 for 循环内的索引计数 (i += 2;)。而是使用 while 循环。您应该增加 1 而不是 2,因为 for 循环计数也增加 1,所以您增加 3 而不是 2。

标签: c# list linq ienumerable


【解决方案1】:

最原始的解决方案是这样的。

var items = GetItems();
var sum = TimeSpan.Zero;

for (int index = items.Count - 1; index > 0; index--)
{
    var item = items[index];
    var nextItem = items[index - 1];

    if (item.LineName == nextItem.LineName && item.Mode == nextItem.Mode)
    {
        sum += item.Time;
        items.RemoveAt(index);
    }
    else
    {
        item.Time += sum;
        sum = TimeSpan.Zero;
    }
}
items.First().Time += sum;

编辑:我错过了最后一行,您必须在其中添加剩菜。这仅适用于集合的第一个和第二个元素相同。没有它,它不会将聚合时间分配给第一个元素。

【讨论】:

  • 谢谢,这可能有效。我将对其进行测试并稍后提供反馈。
  • 感谢编辑,我没有注意到。在测试数据库上效果很好。为什么在这里使用 reversed?
  • @QuarK 因为我删除了项目。如果我要使用普通的for,我将不得不减少index
【解决方案2】:

您可以使用 LINQ 的 GroupBy。为了只对连续的元素进行分组,这使用了一个技巧。它将键值与组索引一起存储在一个元组中,该组索引仅在LineNameMode 更改时递增。

int i = 0; // Used as group index.
(int Index, string LN, string M) prev = default; // Stores previous key for later comparison.
var modified = original
    .GroupBy(mt => {
        var ret = (Index: prev.LN == mt.LineName && prev.M == mt.Mode ? i : ++i,
                   LN: mt.LineName, M: mt.Mode);
        prev = (Index: i, LN: mt.LineName, M: mt.Mode);
        return ret;
    })
    .Select(g => new ModeTime {
        Date = g.Min(mt => mt.Date),
        LineName = g.Key.LN,
        Mode = g.Key.M,
        Time = new TimeSpan(g.Sum(mt => mt.Time.Ticks))
    })
    .ToList();

这会产生预期的 7 行结果。

【讨论】:

  • 它是否破坏了列表项目的原始顺序?我做不到,因为这是工艺过程中生产线状态的切换。
  • 这按DateLineNameMode 分组,并对时间求和。如果您需要特定订单,可以添加OrderBy
  • 好吧,那么看起来你只想对连续的项目进行分组。
  • 完全更新了我的答案,只对连续元素进行分组。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
  • 1970-01-01
  • 2018-02-26
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多