LINQ方法,通过一个函数将序列中的每n个元素连接起来,然后将这些结果重新聚合到一个新的列表中

本文关键字:一个 结果 然后 新聚 列表 起来 元素 函数 方法 LINQ 连接 | 更新日期: 2023-09-27 17:59:54

不确定这个问题的标题是否清晰,但这里是。在我的ASP.NET MVC 3应用程序中,我有一些相当混乱的代码,用于在打印出div标记时,将列表中的每2个(但要从1-n进行配置)项打包。我想创建一个LINQ函数,如下所示:

// Can't think of a better name
IEnumerable<V> FormattedSubsetList<T, V>(IEnumerable<T> items, int every = 2, [delegate to a method to join the N elements])
{
}

很抱歉,如果这有点复杂。。。我来举个例子。假设我有一个Widget的列表。我想打印出我所有小部件的名称(假设我有7个),我希望每2个小部件都在一个div中。所以如果我有。。。

var list = new List<Widget>();
list.Add(new Widget() { Name = "One" });
//... you get the picture
list.Add(new Widget() { Name = "Seven" });
IEnumerable<string> newList = FormattedSubsetList<Widget, string>(list, 2, (one, two) => (return "<div>" + string.Join(" ", one.Name, two.Name) + "</div>");
string finalString = string.Join(string.Empty, newList);
// finalString == <div>One Two</div><div>Three Four</div><div>Five Six</div><div>Seven</div>

如果有什么不清楚的地方,我很抱歉,但我只是不知道这是什么类型的东西,也不知道如何实现它。我知道我的LINQ语法在某些地方也有点偏离。

LINQ方法,通过一个函数将序列中的每n个元素连接起来,然后将这些结果重新聚合到一个新的列表中

这里有两个扩展方法,它们将完全满足您的需要。Split方法是可重用的,它只需将任何IEnumerable拆分为一个枚举列表,这样每个子枚举的元素就不超过size。第二种方法ToFormattedList是按照您的要求执行的客户方法。

public static class Extenstions
{
    public static IEnumerable<TRestul> ToFormattedList<TElement, TRestul>(
        this IEnumerable<TElement> source,
        int count,
        Func<List<TElement>, TRestul> formatter)
    {
        return source.Split(count).Select(arg => formatter(arg.ToList()));
    }
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int size)
    {
        var i = 0;
        return
            from element in source
            group element by i++ / size into splitGroups
            select splitGroups.AsEnumerable();
    }
}

如何使用:

var list = new List<Widget> { new Widget { Name = "One" }, new Widget { Name = "Two" }, new Widget { Name = "Three" }, new Widget { Name = "Four" }, new Widget { Name = "Five" }, new Widget { Name = "Six" }, new Widget { Name = "Seven" }, new Widget { Name = "Eight" } };
var newList = list.ToFormattedList(2, args => "<div>" + args[0].Name + args[1].Name + "</div>");
var finalString = string.Join(string.Empty, newList);
// finalString = <div>OneTwo</div><div>ThreeFour</div><div>FiveSix</div><div>SevenEight</div>

然而,若列表中的元素数为奇数,就会出现问题,因为args[1]会在最后一个元素上引发异常。所以你可以做:

var newList = list.ToFormattedList(2, args => "<div>" + string.Join(" ", args.Select(arg => arg.Name)) + "/<div>");

您可以在一个简单的Select调用中实现分组。这里有一个小型控制台应用程序,可以做你想做的事情:

var list = new List<String>();
for (int i = 0; i < 7; i++) list.Add("Widget #" + (i + 1));
var groupedWidgets = list
    .Select((w, i) => new { Widget = w, Index = i })
    .GroupBy(x => (int)(x.Index / 2));
foreach (var g in groupedWidgets)
{
    Console.WriteLine("<div>");
    foreach (var w in g)
    {
        Console.WriteLine("  " + w.Widget);
    }
    Console.WriteLine("</div>");
}

所以我用"Index/2"作为int进行分组,这意味着项目1和2将一起结束,然后是项目3和4等。将"2"改为"3"将小部件分组为三个。

这有道理吗?

Update这是一个返回分组元素列表的通用函数:

public static class Extensions
{
    public static IEnumerable<IEnumerable<T>> GroupSelect<T>(this IEnumerable<T> list, int groupSize)
    {
        return list
            .Select((t, i) => new { t, i })
            .GroupBy(x => (int)(x.i / groupSize), x => x.t);
    }
}

所以现在你可以写:

foreach (var g in list.GroupSelect(3))
{
    Console.WriteLine("<div>");
    foreach (var w in g)
    {
        Console.WriteLine("  " + w);
    }
    Console.WriteLine("</div>");
}

这里有一个应该满足您需求的扩展方法。

  public static IEnumerable<string> FormatSubsetList<T>(this IEnumerable<T> input, int every, Func<IEnumerable<T>,string> formatter)
  {
     List<T[]> list = new List<T[]> ();
     int index = 0;
     foreach (T i in input)
     {
        T[] array;
        if (index % every == 0)
           list.Add (array = new T[every]);
        else
           array = list[list.Count - 1];
        array[index++ % every] = i;
     }
     return list.Select(t => t.Where (i => i != null)).Select(formatter);
  }
  static Program()
  {
     List<Widget> widgets = new List<Widget> ();
     Func<IEnumerable<Widget>,string> formatter = 
        items => items.Aggregate (new StringBuilder ("<div>"), (sb,w) => sb.Append(" ").Append (w.Name), sb => sb.Append ("</div>").ToString ());
     widgets.FormattedSubsetList(3, formatter);
  }