我正在尝试根据某些Record
信息将Linq中的Type
类型列表拆分为子列表。在每组记录之后总是有一个类型为“a”的记录和一个类型为“b”的记录。我有一个班级Record
:
class Record
{
public string Type { get; set; }
public string SomeOtherInformation { get; set; }
}
以下是一个示例列表(List<Record> records
):
Type SomeOtherInformation
a ......
x ......
x ......
b ......
a ......
b ......
a ......
x ......
x ......
x ......
x ......
x ......
b ......
所需的输出是(List<List<Record>> lists
):
List #1: List #2: List #3:
a ...... a ...... a ......
x ...... b ...... x ......
x ...... x ......
b ...... x ......
x ......
x ......
b ......
我目前正在使用for循环浏览此列表,并在类型为“a”时创建新列表,并在项目类型为“b”时将其添加到子列表中。我想知道Linq是否有更好的方法。可以用Linq完成,如果是的话,怎么做?
答案 0 :(得分:4)
据我所知,您不能干净地使用普通的LINQ执行此操作。 LINQ中的流媒体运营商依赖于您能够基于该项目来决定项目(例如,是否过滤它,如何投影,如何对其进行分组),以及可能是原始来源中的索引。在您的情况下,您确实需要更多信息 - 您需要知道您已经看过多少b
项。
你可以这样做:
int bs = 0;
var groups = records.GroupBy(item => item.Type == 'b' ? bs++ : bs,
(key, group) => group.ToList())
.ToList();
然而,这取决于分组投影中b++
的副作用(以跟踪我们已经看过多少b
个项目 - 它是肯定不惯用LINQ,我不推荐它。
答案 1 :(得分:2)
我会使用扩展方法代替:
public static IEnumerable<IEnumerable<TSource>> SplitItems<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> startItem,
Func<TSource, bool> endItem)
{
var tempList = new List<TSource>();
int counter = 0;
foreach (var item in source)
{
if (startItem(item) || endItem(item)) counter++;
tempList.Add(item);
if (counter%2 == 0)
{
yield return tempList;
tempList = new List<TSource>();
}
}
}
以下是用法:
var result = list.SplitItems(x => x.Type == "a", x => x.Type == "b").ToList();
这将为您返回List<IEnumerable<Record>>
项3
项。因此,该方法假设开头至少有一个开始项,最后一个项结束。您可能需要添加一些检查并根据您的要求进行改进。
答案 2 :(得分:1)
绝对不是纯粹的LINQ,但我可以想象在循环中使用TakeWhile来做到这一点:
List<Record> data;
List<List<Record>> result = new List<List<Record>>();
IEnumerable<Record> workingData = data;
while (workingData.Count() > 0)
{
IEnumerable<Record> subList = workingData.Take(1).Concat(workingData.Skip(1).TakeWhile(c => c.Type != 'a'));
result.Add(subList.ToList());
workingData = workingData.Except(subList);
}
为了解释,我们得到了&#39; a&#39;我们知道是在我们的序列的开头,然后跳过它,直到我们遇到另一个&#39; a&#39;。这构成了其中一个子记录,因此我们将其添加到结果中。然后我们从&#34; working&#34;中删除这个子列表。设置,并再次枚举,直到我们用完元素。
我不确定这会比现有解决方案更好,但希望它有所帮助!
这实际上可行,(在VS 2013,.NET 4.5.1上测试)通过使用workingData而不是循环中的数据(我自己的错字,上面已修复)。 Except
将使用默认比较器来比较对象,因为我们不会覆盖.Equals,它会比较引用(实际上是指针)。因此,重复数据不是问题。如果.Equals 被覆盖,则需要确保每条记录都是唯一的。
如果有人想验证这一点,这是我的测试程序(只是在Console.ReadKey
上设置一个断点,你会看到结果有正确的数据):
class Program
{
static void Main(string[] args)
{
List<Record> testData = new List<Record>()
{
new Record() { Type = 'a', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'b', Data="Data" },
new Record() { Type = 'a', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'b', Data="Data" },
new Record() { Type = 'a', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'x', Data="Data" },
new Record() { Type = 'b', Data="Data" }
};
List<List<Record>> result = new List<List<Record>>();
IEnumerable<Record> workingData = testData;
while (workingData.Count() > 0)
{
IEnumerable<Record> subList = workingData.Take(1).Concat(workingData.Skip(1).TakeWhile(c => c.Type != 'a'));
result.Add(subList.ToList());
workingData = workingData.Except(subList);
}
Console.ReadKey();
}
}
class Record
{
public char Type;
public String Data;
}
答案 3 :(得分:0)
正如已经提到的,这不是LINQ处理得好的情况,因为你只能根据当前项目做出决策,而不是之前看到的。您需要维护某种状态以跟踪分组。依靠副作用
编写自己的扩展方法将是更好的选择。你可以保持状态并使其全部自包含(很像现有的运算符,如GroupBy()
和其他)。这是我的一个实现,可以选择包含未包含在开始和结束项目中的项目。
public static IEnumerable<IImmutableList<TSource>> GroupByDelimited<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> startDelimiter,
Func<TSource, bool> endDelimiter,
bool includeUndelimited = false)
{
var delimited = default(ImmutableList<TSource>.Builder);
var undelimited = default(ImmutableList<TSource>.Builder);
foreach (var item in source)
{
if (delimited == null)
{
if (startDelimiter(item))
{
if (includeUndelimited && undelimited != null)
{
yield return undelimited.ToImmutable();
undelimited = null;
}
delimited = ImmutableList.CreateBuilder<TSource>();
}
else if (includeUndelimited)
{
if (undelimited == null)
{
undelimited = ImmutableList.CreateBuilder<TSource>();
}
undelimited.Add(item);
}
}
if (delimited != null)
{
delimited.Add(item);
if (endDelimiter(item))
{
yield return delimited.ToImmutable();
delimited = null;
}
}
}
}
但是,如果确实想要,您仍然可以使用LINQ运算符(Aggregate()
)来完成此操作,但它不是真正的LINQ解决方案。它将再次看起来像一个自包含的foreach
循环。
var result = records.Aggregate(
Tuple.Create(default(List<Record>), new List<List<Record>>()),
(acc, record) =>
{
var grouping = acc.Item1;
var result = acc.Item2;
if (grouping == null && record.Type == "a")
{
grouping = new List<Record>();
}
if (grouping != null)
{
grouping.Add(record);
if (record.Type == "b")
{
result.Add(grouping);
grouping = null;
}
}
return Tuple.Create(grouping, result);
},
acc => acc.Item2
);