我有些人在买自行车。 桌子看起来像
PeopleID Bicycle 1 Green 1 Yellow 2 Red 2 Red 3 Green 3 Red 3 Yellow
我想吸引只购买同一辆自行车的用户。 输出必须用于此示例,因为PeopleID 2没有其他颜色的自行车
2红色
班级是
class Foo
{
public String PeopleID { get; set; }
public String Bicycle { get; set; }
}
函数是
static void WhoBuyedSameBike(IEnumerable<Foo> foos)
{
var tmpFoos = foos;
var result = tmpFoos.GroupBy(x => x.PeopleID);
var queryResult = new List<QueryResult>();
foreach (var fooItem in result)
{
if (fooItem.Count() == tmpFoos.Count(w => w.PeopleID == fooItem.Key && w.Bicycle == "Red"))
{
queryResult.Add(new QueryResult { PeopleID = fooItem.Key });
}
}
foreach (var item in queryResult)
{
Console.WriteLine($"PeopleId : {item.PeopleID}");
}
}
是否还有其他方法可以通过不使用Linq的foreach循环获取更多声明性代码来获取输出?
答案 0 :(得分:1)
使用Linq,按每个人分组,获得每种自行车颜色的不同计数,然后从计数为1的每个组中选择第一个(唯一的)项目。
IEnumerable<Foo> result =
foos
.GroupBy(foo => foo.PeopleID)
.Where(group => group.Select(foo => foo.Bicycle).Distinct().Count() == 1)
.Select(group => group.First());
像这样输出它:
foreach(var foo in result) {
Console.WriteLine($"PeopleId:{foo.PeopleID}, Bicycle:{foo.Bicycle}");
}
在评论中,Harald Coppoolse建议在将计数计入where语句之前插入.Take(2)。这将更加有效,因为.Count()必须枚举整个集合才能获得大小(除非它实现ICollection)。他还提出了第二种方法,该方法更加有效。
但是,在进行此操作时,我认为我走得更远,将嵌套的Select和Distinct方法压缩为一个聚合,其结果无需再次重复以确定是否存在重复项。
foos
.GroupBy(foo => foo.PeopleID)
.Where(group => group.Aggregate((a,b) => a?.Bicycle == b.Bicycle ? a : null) != null)
.Select(group => group.First());
当然,所有这些只是为了更好地理解。当然,在了解Aggregate方法,terneray运算符和null条件运算符之前,请先将头放在原始版本上。
顺便说一句。我之前没有提到过,如果性能比可读性或代码优雅更重要,那么不要逃避for循环。上面的代码可以不受此限制地提高效率。