在C#中合并具有相同类型的通用列表中的项目的最有效方法是什么?

3

例如,我有以下接口和类:

public interface IRole {
    DateTime Since {get;}
    DateTime Until {get;}
}

public class Manager : IRole {
    public DateTime Since {get; private set;}
    public DateTime Until {get; private set;}
}
public class Employee : IRole {
    public DateTime Since {get; private set;}
    public DateTime Until {get; private set;}
}
public class Ceo: IRole {
    public DateTime Since {get; private set;}
    public DateTime Until {get; private set;}
}

如果一个通用列表包含以下项:

list[0]=new Manager();
list[1]=new Manager();
list[2]=new Employee();
list[3]=new Manager();
list[4]=new Ceo();
list[5]=new Ceo();

我将合并相同类型的内容,将Since/Until组合在一起,并缩小列表项,使输出结果如下:

newList[0]=new Manager() //(Since is from list[0], Until is from list[1])
newList[1]=new Employee() //(list[2])
newList[2]=new Manager() //(list[3])
newList[3]=new Ceo() //(Since is from list[4], Until is from list[5])

在回答问题之前,请确保您理解了问题,因为我有模糊不清的历史,我不想让人们感到不安。如果您觉得“要求”不清楚,请发表评论。

我的方法有点愚蠢:

for each item in list
    the current item shall always be merged into the previous item
        check if current item has the same type as the previous item
            get last item from newList and merge last item with current item

我想知道是否有更好的解决方案。
更新:
我刚刚意识到我的“愚蠢的解决方案”无法处理连续超过2个相同类型的项目。
例如:
list[0]=new Manager();
list[1]=new Manager();
list[2]=new Employee();
list[3]=new Manager();
list[4]=new Ceo();
list[5]=new Ceo();
list[6]=new Ceo();

你突然在第三个代码块中切换到伪代码了,是吗? - Jouke van der Maas
@Jourke van der Maas,现在好了吗?我只是想解释一下新对象应该是什么(所有相邻的具有相同类型的项目的组合)。 - Jeff
是的,注释更清晰。 - Jouke van der Maas
4个回答

3
我写了一篇博客文章来介绍这个问题 :-).
这几乎感觉像是group by,但是你不想全局分组。相反,你只想将输入列表中相邻的元素分组。博客文章提供了一些代码,允许您更改LINQ查询中group by的含义,因此您可以仅编写以下内容:
var groups =
  from person in list.WithAdjacentGrouping()
  group person by person.GetType().Name into g
  select new { 
    Type = g.Key,
    Since = new DateTime(g.Select(p => p.Since.Ticks).Min()),
    Until = new DateTime(g.Select(p => p.Until.Ticks).Max())
  }

WithAdjacentGrouping 的调用指定了分组只应该将相邻的元素分组。然后,我们可以通过类型(使用GetType().Name作为键)收集相邻的人员组。

最后,我们返回一个包含类型名称(例如“Ceo”)和两个时间 - SinceUntil 的集合,这些时间是从收集的组中计算出的最小/最大时间。


分组听起来不错,但如何合并像Since/Until这样的属性呢? - Jeff
@Jeffrey:添加了最小“Since”值/最大“Until”值的计算。目前,它只返回它们 - 您想创建一个新的实例,例如“Ceo”,还是重用现有实例(例如组中的第一个实例)? - Tomas Petricek
这确实非常聪明,为什么我没有想到在group by上下文中使用这种解决方案...我确实需要使用反射来创建新实例,因为Since/Until只能通过构造函数传递。但是我明白了你的意思。谢谢。 - Jeff
@Jeffrey:这实际上不是一个 group by(你需要使用我博客中的替代实现才能使其按照你的需求工作)。然而,你想要执行的操作具有与 group by 相同的结构,这使得可以误用 LINQ 来编码它 :-) - Tomas Petricek

2

如果您的伪代码按照您的期望工作,我不认为它是愚蠢的。我不相信您会找到一个简单的捷径来完成您正在尝试做的事情,因为这是一个相当不寻常的算法。总之:如果该算法每天不会运行数百万次,并且列表中没有数百万个对象,那么我不会担心效率问题。


1
List<IRole> newList = new Lis<IRole>();
for (int i = 1; i < list.Count; i++) // Start at 1, so we can do i - 1 on the first iteration
{
  if (list[i - 1].GetType() != list[i].GetType()) // they're not the same
  {
    newList.Add(list[i - 1]); // so add the first one too
  }
  newList.Add(list[i]); // always add second one
}

我也是,太晚意识到了。不管怎样还是谢谢你;-) - Jouke van der Maas
我之所以这么快就完成了它,是因为我很好奇你写了什么 :) - devuxer
@Jouke van der Maas,如果有超过2个相同类型的项目相邻,会发生什么?例如list[0]=manager,list[1]=manager和list[2]=manager? - Jeff
@@Jouke van der Maas,我现在认为循环应该从最后一项开始,如果类型相同,则将当前项与上一项合并,并从列表中删除最后一项(如果可能的话),听起来像是一个while循环。天啊...我就是无法从脑海中抽离出while循环。 - Jeff
@jeffrey 你说得对,我没想到。至于倒序循环,你可以这样做:for (int i = list.Count - 1; i >= 0; i--) { } - Jouke van der Maas

0

我认为这是一个非常特定于要求的事情。我不确定是否以任何方式由框架支持。 如果类型不是直接派生自IRole会发生什么。如果有像IEmployee从中派生出IManager这样的东西,怎么办。我不确定应用程序特定的语义如何被框架理解。

如果问题非常特定于应用程序,则可以使用linq(在类型上)使用组子句完成此操作。我以前没有尝试过,因此无法给出确切的解决方案。


类型根据定义是从IRole派生的。他不是要求一个单一的方法来使用IRole,而是要求实现。 - Stilgar
同意。正如我之前提到的,这是一个特定于应用程序的答案。 - Chetan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接