我有IEnumerable<string>
,看起来像{"First", "1", "Second", "2", ... }
。
我需要遍历列表并创建IEnumerable<Tuple<string, string>>
,其中元组将如下所示:
"First", "1"
"Second", "2"
所以我需要从列表中创建对,我必须得到如上所述的对。
你可以这样做:
var pairs = source.Select((value, index) => new {Index = index, Value = value})
.GroupBy(x => x.Index / 2)
.Select(g => new Tuple<string, string>(g.ElementAt(0).Value,
g.ElementAt(1).Value));
这将给你一个IEnumerable<Tuple<string, string>>
。它的工作原理是将元素按奇/偶位置分组,然后将每个组扩展为Tuple
。这种方法相对于BrokenGlass建议的Zip
方法的好处是它只列举了原始的可枚举一次。
然而,乍一看有些人很难理解,所以我要么采取另一种方式(即不使用linq),或者将其意图记录在使用它的旁边。
实现此目的的延迟扩展方法是:
public static IEnumerable<Tuple<T, T>> Tupelize<T>(this IEnumerable<T> source)
{
using (var enumerator = source.GetEnumerator())
while (enumerator.MoveNext())
{
var item1 = enumerator.Current;
if (!enumerator.MoveNext())
throw new ArgumentException();
var item2 = enumerator.Current;
yield return new Tuple<T, T>(item1, item2);
}
}
请注意,如果元素的数量碰巧不均匀,则会抛出。另一种方法是使用此扩展方法将源集合拆分为2的块:
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> list, int batchSize)
{
var batch = new List<T>(batchSize);
foreach (var item in list)
{
batch.Add(item);
if (batch.Count == batchSize)
{
yield return batch;
batch = new List<T>(batchSize);
}
}
if (batch.Count > 0)
yield return batch;
}
然后你可以这样做:
var tuples = items.Chunk(2)
.Select(x => new Tuple<string, string>(x.First(), x.Skip(1).First()))
.ToArray();
最后,仅使用现有的扩展方法:
var tuples = items.Where((x, i) => i % 2 == 0)
.Zip(items.Where((x, i) => i % 2 == 1),
(a, b) => new Tuple<string, string>(a, b))
.ToArray();
你可以使用LINQ .Zip()
扩展方法来完成这项工作:
IEnumerable<string> source = new List<string> { "First", "1", "Second", "2" };
var tupleList = source.Zip(source.Skip(1),
(a, b) => new Tuple<string, string>(a, b))
.Where((x, i) => i % 2 == 0)
.ToList();
基本上这种方法是将源Enumerable自身拉上来,跳过第一个元素,这样第二个枚举就是一个 - 这会给你一对(“First,”1“),(”1“,”Second“),( “第二”,“2”)。
然后我们过滤奇数元组,因为我们不想要那些并最终得到正确的元组对(“First”,“1”),(“Second”,“2”)等等。
编辑:
我实际上同意评论的观点 - 这就是我认为“聪明”的代码 - 看起来很聪明,但有明显(并不那么明显)的缺点:
Enumerables
,即来自网络流的数据。话虽如此,我可能会使用一个好的旧foreach
循环自己给出选择,或者使用列表作为源集合for
循环,所以我可以直接使用索引。
IEnumerable<T> items = ...;
using (var enumerator = items.GetEnumerator())
{
while (enumerator.MoveNext())
{
T first = enumerator.Current;
bool hasSecond = enumerator.MoveNext();
Trace.Assert(hasSecond, "Collection must have even number of elements.");
T second = enumerator.Current;
var tuple = new Tuple<T, T>(first, second);
//Now you have the tuple
}
}
如果您使用的是.NET 4.0,则可以使用元组对象(请参阅http://mutelight.org/articles/finally-tuples-in-c-sharp.html)。与LINQ一起,它应该为您提供所需的一切。如果没有,那么你可能需要定义自己的元组来编写或编码那些字符串,例如"First:1"
,"Second:2"
然后解码它(也使用LINQ)。