将列表拆分为列表列表,拆分元素

时间:2011-10-03 14:25:03

标签: c# linq aggregate

可以重写以下内容以便使用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的选择)。

4 个答案:

答案 0 :(得分:2)

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

最简单的方法是使用yield statement来获取代码并使其延迟执行。一个简单的方法如下:

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;
}

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

答案 1 :(得分:0)

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

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之外最好处理的情况。

答案 2 :(得分:0)

这是一个低效但纯粹的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));

答案 3 :(得分:0)

嗯,我在这里使用一种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
            );
    }
}

如果您决定使用此代码,我相信您会注意到完全没有错误检查...