我有两个长度相同的列表,是否可以一次遍历这两个列表?
我正在寻找正确的语法来执行以下操作
foreach itemA, itemB in ListA, ListB
{
Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}
你认为这在 C# 中是可能的吗?如果是,与此等效的 lambda 表达式是什么?
[编辑]:澄清;这在不能使用IEnumerable<T>
索引器的通用 LINQ/上下文中很有用,因为 a:它不存在于可枚举对象中,并且 b:您不能保证可以多次读取数据。由于 OP 提到了 lambda,因此 LINQ 可能不会太远(是的,我确实意识到 LINQ 和 lambda 并不完全相同)。
听起来您需要缺少的Zip
运算符;你可以欺骗它:
static void Main()
{
int[] left = { 1, 2, 3, 4, 5 };
string[] right = { "abc", "def", "ghi", "jkl", "mno" };
// using KeyValuePair<,> approach
foreach (var item in left.Zip(right))
{
Console.WriteLine("{0}/{1}", item.Key, item.Value);
}
// using projection approach
foreach (string item in left.Zip(right,
(x,y) => string.Format("{0}/{1}", x, y)))
{
Console.WriteLine(item);
}
}
// library code; written once and stuffed away in a util assembly...
// returns each pais as a KeyValuePair<,>
static IEnumerable<KeyValuePair<TLeft,TRight>> Zip<TLeft, TRight>(
this IEnumerable<TLeft> left, IEnumerable<TRight> right)
{
return Zip(left, right, (x, y) => new KeyValuePair<TLeft, TRight>(x, y));
}
// accepts a projection from the caller for each pair
static IEnumerable<TResult> Zip<TLeft, TRight, TResult>(
this IEnumerable<TLeft> left, IEnumerable<TRight> right,
Func<TLeft, TRight, TResult> selector)
{
using(IEnumerator<TLeft> leftE = left.GetEnumerator())
using (IEnumerator<TRight> rightE = right.GetEnumerator())
{
while (leftE.MoveNext() && rightE.MoveNext())
{
yield return selector(leftE.Current, rightE.Current);
}
}
}
只是在一个普通的旧 for 循环中执行它会更简单......
for(int i=0; i<ListA.Length; i++)
{
Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
你可以明确地做到这一点。
IEnumerator ListAEnum = ListA.GetEnumerator();
IEnumerator ListBEnum = ListB.GetEnumerator();
ListBEnum.MoveNext();
while(ListAEnum.MoveNext()==true)
{
itemA=ListAEnum.getCurrent();
itemB=ListBEnum.getCurrent();
Console.WriteLine(itemA.ToString()+","+itemB.ToString());
}
至少这个(或类似的东西)是编译器为 foreach 循环所做的。我还没有测试过它,我猜枚举器缺少一些模板参数。
只需从 List 和 IEnumerator-Interface 中查找 GetEnumerator()。
我建议使用普通的 for 循环,但您应该考虑不同的数组长度。所以
for(int i=0; i<ListA.Length; i++)
{
Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
可以变成
for(int i = 0; i < Math.Min(ListA.Length, ListB.Lenght); i++)
{
Console.WriteLine(ListA[i].ToString() + ", " + ListB[i].ToString());
}
甚至进入
for(int i = 0; i < Math.Max(ListA.Length, ListB.Lenght); i++)
{
string valueA = i < ListA.Length ? listA[i].ToString() : "";
string valueB = i < ListB.Length ? listB[i].ToString() : "";
Console.WriteLine(valueA+ ", " + valueB);
}
我遇到了同样的问题,但是使用其中包含列表的对象列表..对于它的价值,这可能会帮助遇到同样问题的人。
由于 IndexOf 为 O(n),因此它的运行时间不是很好,但当时,我处理的内部 foreach 循环比本示例中的要多得多,所以我不想处理迭代器变量。
在这样的时候,我非常想念 PHPs foreach($arrayList as $key => $value) 表示法......也许我在 C# 中遗漏了一些东西,必须有一种方法可以在 O(c) 时间内获取索引!(遗憾的是这篇文章说不:在'foreach'循环中获取数组键)
class Stock {
string symbol;
List<decimal> hourlyPrice; // provides a list of 24 decimals
}
// get hourly prices from yesterday and today
List<Stock> stockMondays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now.AddDay(-1));
List<Stock> stockTuesdays = Stocks.GetStock("GOOGL,IBM,AAPL", DateTime.Now);
try {
foreach(Stock sMonday in stockMondays) {
Stock sTuesday = stockTuesday[stockMondays.IndexOf(sMonday)];
foreach(decimal mondayPrice in sMonday.prices) {
decimal tuesdayPrice = sTuesday.prices[sMonday.prices.IndexOf(mondayPrice)];
// do something now
}
}
} catch (Exception ex) { // some reason why list counts aren't matching? }
LINQ 现在有一个内置的 Zip 方法,因此您无需创建自己的方法。结果序列与最短输入一样长。Zip 目前(从 .NET Core 3.0 开始)有 2 个重载。更简单的返回一个元组序列。它让我们可以生成一些非常接近原始请求的非常简洁的代码:
int[] numbers = { 1, 2, 3, 4 };
string[] words = { "one", "two", "three" };
foreach (var (number, word) in numbers.Zip(words))
Console.WriteLine($"{number}, {word}");
// 1, one
// 2, two
// 3, three
或者,同样的事情,但没有元组解包:
foreach (var item in numbers.Zip(words))
Console.WriteLine($"{item.First}, {item.Second}");
另一个重载(出现在 Core 3.0 之前)采用映射函数,让您可以更好地控制结果。这个例子返回一个字符串序列,但你可以返回一个任意序列(例如一些自定义类)。
var numbersAndWords = numbers.Zip(words, (number, word) => $"{number}, {word}");
foreach (string item in numbersAndWords)
Console.WriteLine(item);
如果您在常规对象上使用 LINQ(而不是使用它来生成 SQL),您还可以查看MoreLINQ,它提供了几种压缩方法。这些方法中的每一个都需要最多 4 个输入序列,而不仅仅是 2 个:
EquiZip
- 如果输入序列的长度不同,则会引发异常。
ZipLongest
- 结果序列将始终与最长的输入序列一样长,其中每个较短序列元素类型的默认值用于填充。
ZipShortest
- 生成的序列与最短的输入序列一样短。
请参阅他们的示例和/或使用测试。似乎 MoreLINQ 的压缩比普通的 LINQ 早,所以如果你坚持使用旧版本的 .NET,它可能是一个不错的选择。
我有这个小函数可以帮助我遍历这两个列表对象。schema 是 SqlData 类型,它是一个包含三个属性的类。data 是一个包含动态类型值的列表。首先,我遍历模式集合,而不是使用项目的索引来遍历数据对象。
public List<SqlData> SqlDataBinding(List<SqlData> schema, List<dynamic> data)
{
foreach (SqlData item in schema)
{
item.Values = data[schema.IndexOf(item)];
}
return schema
}
Senthil Kumar 的技术博客有一系列涵盖C# 的 (Python) Itertools 的实现,包括itertools.izip
.
从Itertools for C# - Cycle 和 Zip,您可以找到任意数量的可迭代对象(不仅是List<T>)的解决方案。请注意,每次迭代都会Zip
产生一个:Array
public static IEnumerable<T[]> Zip<T>(params IEnumerable<T>[] iterables)
{
IEnumerator<T>[] enumerators = Array.ConvertAll(iterables, (iterable) => iterable.GetEnumerator());
while (true)
{
int index = 0;
T[] values = new T[enumerators.Length];
foreach (IEnumerator<T> enumerator in enumerators)
{
if (!enumerator.MoveNext())
yield break;
values[index++] = enumerator.Current;
}
yield return values;
}
}
代码获取所有可迭代对象的枚举器,将所有枚举器向前移动,将它们的当前值累积到一个数组中并生成数组。它会这样做,直到任何一个枚举器用完元素。