将List拆分为List的List,按元素拆分

本文关键字:List 拆分 元素 | 更新日期: 2023-09-27 18:10:23

是否可以重写以下代码,以便使用LINQ(而不是这些老式的foreach循环)

IEnumerable<IEnumerable<T>> SplitIntoSections<T>(IEnumerable<T> content, 
    Func<T, bool> isSectionDivider)
{
    var sections = new List<List<T>>();
    sections.Add(new List<T>());
    foreach (var element in content)
    {
        if (isSectionDivider(element))
        {
            sections.Add(new List<T>());
        }
        else
        {
            sections.Last().Add(element);
        }
    }
    return sections;
}

当我意识到它可以用foreach循环完成时,我认为我几乎有一种方法可以做到这一点(它涉及FSharp集合)。

将List拆分为List的List,按元素拆分

您不希望在这里使用LINQ。如果不做一些粗糙的事情,你将无法以正确的方式排序和分组。

最简单的方法是使用yield语句将代码延迟执行。一个简单的方法是这样做:

IEnumerable<IEnumerable<T>> SplitIntoSections<T>(this IEnumerable<T> source, 
    Func<T, bool> sectionDivider)
{
    // The items in the current group.
    IList<T> currentGroup = new List<T>();
    // Cycle through the items.
    foreach (T item in source)
    {
        // Check to see if it is a section divider, if
        // it is, then return the previous section.
        // Also, only return if there are items.
        if (sectionDivider(item) && currentGroup.Count > 0)
        {
            // Return the list.
            yield return currentGroup;
            // Reset the list.
            currentGroup = new List<T>();
        }
        // Add the item to the list.
        currentGroup.Add(item);
    }
    // If there are items in the list, yield it.
    if (currentGroup.Count > 0) yield return currentGroup;
}

这里有一个问题;对于非常大的组,将子组存储在列表中是低效的,它们也应该流化。你的方法的问题是,你有一个函数,需要在每个项目上调用;它会干扰流操作,因为一旦找到分组就不能向后重置流(因为实际上需要两个方法来产生结果)。

这是一个低效但纯粹的LINQ解决方案:

var dividerIndices = content.Select((item, index) => new { Item = item, Index = index })
                            .Where(tuple => isSectionDivider(tuple.Item))
                            .Select(tuple => tuple.Index);

return new[] { -1 }
        .Concat(dividerIndices)
        .Zip(dividerIndices.Concat(new[] { content.Count() }),
            (start, end) => content.Skip(start + 1).Take(end - start - 1));

您可以使用仅在明确定义的区域内使用的副作用…它很臭,但是:

int id = 0;
return content.Select(x => new { Id = isSectionDivider(x) ? id : ++id,
                                 Value = x })
              .GroupBy(pair => pair.Id, pair.Value)
              .ToList();
但是一定有更好的选择…如果有必要,Aggregate会带你去那里的。
return content.Aggregate(new List<List<T>>(), (lists, value) => {
                             if (lists.Count == 0 || isSectionDivider(value)) {
                                 lists.Add(new List<T>());
                             };
                             lists[lists.Count - 1].Add(value);
                             return lists;
                         });

…但总的来说,我同意casperOne的观点,这种情况最好在LINQ之外处理。

嗯,我在这里使用了一种LINQ方法,尽管它不是特别符合你问题的精神,我认为:

static class Utility
{
    // Helper method since Add is void
    static List<T> Plus<T>(this List<T> list, T newElement)
    {
        list.Add(newElement);
        return list;
    }
    // Helper method since Add is void
    static List<List<T>> PlusToLast<T>(this List<List<T>> lists, T newElement)
    {
        lists.Last().Add(newElement);
        return lists;
    }
    static IEnumerable<IEnumerable<T>> SplitIntoSections<T>
         (IEnumerable<T> content, 
          Func<T, bool> isSectionDivider)
    {
        return content.Aggregate(                      // a LINQ method!
            new List<List<T>>(),                       // start with empty sections
            (sectionsSoFar, element) =>
            isSectionDivider(element)
                ? sectionsSoFar.Plus(new List<T>())
                  // create new section when divider encountered
                : sectionsSoFar.PlusToLast(element)
                  // add normal element to current section
            );
    }
}

我相信你会注意到完全没有错误检查,如果你决定使用这段代码…

相关文章: