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

发布于 2024-12-07 19:23:22 字数 638 浏览 3 评论 0原文

可以重写以下内容,以便使用 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;
}

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

Can the following be rewritten so that is uses LINQ, (rather an these old-fashioned foreach loops)

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

I thought I almost had an way of doing this, (it involved FSharp colections) when i realised that it could be done with a foreach loop.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

无法言说的痛 2024-12-14 19:23:22

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

最简单的方法是使用 产量声明。一个简单的方法如下:

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

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

You don't want to use LINQ here. You aren't going to be able to order and group the proper way without doing something gnarly.

The easy thing to do is to take your code and make it defer execution using the yield statement. An easy way to do this is as follows:

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

There's a problem here; for very large groups, it's inefficient to store the sub-groups in a list, they should be streamed out as well. The problem with your approach is that you have a function that is required to be called on each item; it interferes with the stream operation since one can't reset the stream backwards once the grouping is found (as you effectively need two methods that yield results).

往事风中埋 2024-12-14 19:23:22

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

Here's an inefficient but pure LINQ solution:

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));
忆依然 2024-12-14 19:23:22

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

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 之外处理。

You could use a side-effect which is only used within a well-defined area... it's pretty smelly, but:

int id = 0;
return content.Select(x => new { Id = isSectionDivider(x) ? id : ++id,
                                 Value = x })
              .GroupBy(pair => pair.Id, pair.Value)
              .ToList();

There must be a better alternative though... Aggregate will get you there if necessary...

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

... but overall I agree with casperOne, this is a situation best handled outside LINQ.

月亮坠入山谷 2024-12-14 19:23:22

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

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

Well, I use one LINQ method here, though it's not particularly in the spirit of your question, I think:

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

I trust you will note the complete lack of error checking, should you decide to use this code...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文