我有一个IEnumerable<T>
和一个IEnumerable<U>
我想合并到一个IEnumerable<KeyValuePair<T,U>>
KeyValuePair 中连接在一起的元素的索引相同的地方。请注意,我没有使用 IList,因此我没有要合并的项目的计数或索引。我怎样才能最好地做到这一点?我更喜欢 LINQ 的答案,但是任何以优雅的方式完成工作的东西都可以。
10 回答
注意:从 .NET 4.0 开始,该框架.Zip
在 IEnumerable 上包含一个扩展方法,在此处记录。以下内容是为了后代和在 4.0 之前的 .NET 框架版本中使用而维护的。
我使用这些扩展方法:
// From http://community.bartdesmet.net/blogs/bart/archive/2008/11/03/c-4-0-feature-focus-part-3-intermezzo-linq-s-new-zip-operator.aspx
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> func) {
if (first == null)
throw new ArgumentNullException("first");
if (second == null)
throw new ArgumentNullException("second");
if (func == null)
throw new ArgumentNullException("func");
using (var ie1 = first.GetEnumerator())
using (var ie2 = second.GetEnumerator())
while (ie1.MoveNext() && ie2.MoveNext())
yield return func(ie1.Current, ie2.Current);
}
public static IEnumerable<KeyValuePair<T, R>> Zip<T, R>(this IEnumerable<T> first, IEnumerable<R> second) {
return first.Zip(second, (f, s) => new KeyValuePair<T, R>(f, s));
}
编辑:评论后我有义务澄清和解决一些问题:
- 我最初从Bart De Smet 的博客中逐字获取了第一个 Zip 实现
- 添加了枚举器处理(在 Bart 的原始帖子中也有说明)
- 添加了空参数检查(也在 Bart 的帖子中讨论)
作为对遇到此问题的任何人的更新,.Net 4.0 原生支持此问题,例如来自 MS:
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
var numbersAndWords = numbers.Zip(words, (first, second) => first + " " + second);
文档:
该方法将第一个序列的每个元素与第二个序列中具有相同索引的元素合并。如果序列没有相同数量的元素,则该方法会合并序列,直到它到达其中一个的末尾。例如,如果一个序列有三个元素,而另一个序列有四个,则结果序列将只有三个元素。
想想你在这里更仔细地问什么:
您想组合两个 IEnumerable,其中“在 KeyValuePair 中连接在一起的元素的索引相同”,但您“没有 我正在合并的项目的计数或索引”。
无法保证您的 IEnumerables 甚至已排序或未排序。你的两个 IEnumerable 对象之间没有关联,那么你怎么能期望它们关联呢?
看下一个:
当前实现的方法
IEnumerable
- ForEach 对 IEnumerable 的每个元素执行指定的操作。
- 将物品组合成相同大小的批次。
- Scan 通过将委托应用于 IEnumerable 中的项目对来创建列表。
- AtLeast 检查 IEnumerable 中至少有一定数量的项目。
- AtMost 检查 IEnumerable 中的项目数量不超过一定数量。
- Zip通过将两个其他列表合并为一个来创建一个列表。
- Cycle 通过重复另一个列表来创建一个列表。
我会使用类似的东西 -
IEnumerable<KeyValuePair<T,U>> Merge<T,U>(IEnumerable<T> keyCollection, IEnumerable<U> valueCollection)
{
var keys = keyCollection.GetEnumerator();
var values = valueCollection.GetEnumerator();
try
{
keys.Reset();
values.Reset();
while (keys.MoveNext() && values.MoveNext())
{
yield return new KeyValuePair<T,U>(keys.Current,values.Current);
}
}
finally
{
keys.Dispose();
values.Dispose();
}
}
这应该可以正常工作,并在之后正确清理。
未经测试,但应该可以工作:
IEnumerable<KeyValuePair<T, U>> Zip<T, U>(IEnumerable<T> t, IEnumerable<U> u) {
IEnumerator<T> et = t.GetEnumerator();
IEnumerator<U> eu = u.GetEnumerator();
for (;;) {
bool bt = et.MoveNext();
bool bu = eu.MoveNext();
if (bt != bu)
throw new ArgumentException("Different number of elements in t and u");
if (!bt)
break;
yield return new KeyValuePair<T, U>(et.Current, eu.Current);
}
}
您可以使用MoreLINQ中的Zip方法。
MSDN 有以下自定义序列运算符示例。Welbog 是对的;如果您对基础数据没有索引,则无法保证该操作符合您的预期。
Alexey Romanov的功能点网项目的另一个实现:
/// <summary>
/// Takes two sequences and returns a sequence of corresponding pairs.
/// If one sequence is short, excess elements of the longer sequence are discarded.
/// </summary>
/// <typeparam name="T1">The type of the 1.</typeparam>
/// <typeparam name="T2">The type of the 2.</typeparam>
/// <param name="sequence1">The first sequence.</param>
/// <param name="sequence2">The second sequence.</param>
/// <returns></returns>
public static IEnumerable<Tuple<T1, T2>> Zip<T1, T2>(
this IEnumerable<T1> sequence1, IEnumerable<T2> sequence2) {
using (
IEnumerator<T1> enumerator1 = sequence1.GetEnumerator())
using (
IEnumerator<T2> enumerator2 = sequence2.GetEnumerator()) {
while (enumerator1.MoveNext() && enumerator2.MoveNext()) {
yield return
Pair.New(enumerator1.Current, enumerator2.Current);
}
}
//
//zip :: [a] -> [b] -> [(a,b)]
//zip (a:as) (b:bs) = (a,b) : zip as bs
//zip _ _ = []
}
替换Pair.New
为 new KeyValuePair<T1, T2>
(和返回类型),你就可以开始了。