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语法在某些地方也有点偏离。
这里有两个扩展方法,它们将完全满足您的需要。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);
}