题
我在解析文本文件时构建了两个数组。第一个包含列名称,第二个包含当前行的值。我需要一次迭代两个列表来构建地图。现在我有以下内容:
var currentValues = currentRow.Split(separatorChar);
var valueEnumerator = currentValues.GetEnumerator();
foreach (String column in columnList)
{
valueEnumerator.MoveNext();
valueMap.Add(column, (String)valueEnumerator.Current);
}
这工作得很好,但它并不能完全满足我的优雅感,而且如果数组的数量大于两个(我偶尔必须这样做),它会变得非常毛茸茸。还有人有其他更简洁的成语吗?
解决方案
如果有相同数目的列名的,因为有在每行中的元素,能不for循环使用?
var currentValues = currentRow.Split(separatorChar);
for(var i=0;i<columnList.Length;i++){
// use i to index both (or all) arrays and build your map
}
其他提示
你的初始代码中有一个不明显的伪错误 - IEnumerator<T>
延伸 IDisposable
所以你应该处置它。这对于迭代器块来说非常重要!对于数组来说不是问题,但对于其他数组来说会是问题 IEnumerable<T>
实施。
我会这样做:
public static IEnumerable<TResult> PairUp<TFirst,TSecond,TResult>
(this IEnumerable<TFirst> source, IEnumerable<TSecond> secondSequence,
Func<TFirst,TSecond,TResult> projection)
{
using (IEnumerator<TSecond> secondIter = secondSequence.GetEnumerator())
{
foreach (TFirst first in source)
{
if (!secondIter.MoveNext())
{
throw new ArgumentException
("First sequence longer than second");
}
yield return projection(first, secondIter.Current);
}
if (secondIter.MoveNext())
{
throw new ArgumentException
("Second sequence longer than first");
}
}
}
然后您可以在需要时重复使用它:
foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar),
(column, value) => new { column, value })
{
// Do something
}
或者,您可以创建一个通用的 Pair 类型,并删除 PairUp 方法中的投影参数。
编辑:
对于 Pair 类型,调用代码将如下所示:
foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar))
{
// column = pair.First, value = pair.Second
}
这看起来非常简单。是的,您需要将实用方法作为可重用代码放在某处。在我看来这几乎不是问题。现在对于多个数组...
如果数组的类型不同,我们就会遇到问题。您无法在泛型方法/类型声明中表达任意数量的类型参数 - 您可以根据需要为任意数量的类型参数编写 PairUp 版本,就像有 Action
和 Func
最多 4 个委托参数的委托 - 但你不能让它任意。
但是,如果这些值都属于同一类型 - 并且您愿意坚持使用数组 - 这很容易。(非数组也可以,但你不能提前检查长度。)你可以这样做:
public static IEnumerable<T[]> Zip<T>(params T[][] sources)
{
// (Insert error checking code here for null or empty sources parameter)
int length = sources[0].Length;
if (!sources.All(array => array.Length == length))
{
throw new ArgumentException("Arrays must all be of the same length");
}
for (int i=0; i < length; i++)
{
// Could do this bit with LINQ if you wanted
T[] result = new T[sources.Length];
for (int j=0; j < result.Length; j++)
{
result[j] = sources[j][i];
}
yield return result;
}
}
那么调用代码将是:
foreach (var array in Zip(columns, row, whatevers))
{
// column = array[0]
// value = array[1]
// whatever = array[2]
}
当然,这涉及一定量的复制 - 您每次都创建一个数组。您可以通过引入另一种类型来改变这一点,如下所示:
public struct Snapshot<T>
{
readonly T[][] sources;
readonly int index;
public Snapshot(T[][] sources, int index)
{
this.sources = sources;
this.index = index;
}
public T this[int element]
{
return sources[element][index];
}
}
不过,大多数人可能会认为这太过分了;)
老实说,我可以不断想出各种各样的想法......但基本原理是:
- 通过一些可重用的工作,您可以使调用代码变得更好
- 对于任意类型的组合,由于泛型的工作方式,您必须分别执行每个数量的参数(2、3、4...)
- 如果您愿意为每个部分使用相同的类型,您可以做得更好
在函数式语言中,您通常会找到一个“zip”函数,它有望成为 C#4.0 的一部分。 巴特·德·斯梅特 基于现有 LINQ 函数提供了一个有趣的 zip 实现:
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> func)
{
return first.Select((x, i) => new { X = x, I = i })
.Join(second.Select((x, i) => new { X = x, I = i }),
o => o.I,
i => i.I,
(o, i) => func(o.X, i.X));
}
然后你可以这样做:
int[] s1 = new [] { 1, 2, 3 };
int[] s2 = new[] { 4, 5, 6 };
var result = s1.Zip(s2, (i1, i2) => new {Value1 = i1, Value2 = i2});
如果你真的使用数组,最好的办法可能只是使用具有索引的常规for
循环。不是漂亮的,理所当然的,但据我所知.NET不提供这样做的更好的方法。
您也可以封装你的代码放到一个方法叫做zip
- 这是一种常见的高阶列表功能。然而,缺乏合适的元组类型C#中,这是相当这些混沌。你最终会返回一个IEnumerable<KeyValuePair<T1, T2>>
这是不是很漂亮。
顺便说一句,你真的使用IEnumerable
代替IEnumerable<T>
或为什么你投的Current
价值?
使用IEnumerator的两个将是很好的
var currentValues = currentRow.Split(separatorChar);
using (IEnumerator<string> valueEnum = currentValues.GetEnumerator(), columnEnum = columnList.GetEnumerator()) {
while (valueEnum.MoveNext() && columnEnum.MoveNext())
valueMap.Add(columnEnum.Current, valueEnum.Current);
}
或创建一个扩展方法
public static IEnumerable<TResult> Zip<T1, T2, TResult>(this IEnumerable<T1> source, IEnumerable<T2> other, Func<T1, T2, TResult> selector) {
using (IEnumerator<T1> sourceEnum = source.GetEnumerator()) {
using (IEnumerator<T2> otherEnum = other.GetEnumerator()) {
while (sourceEnum.MoveNext() && columnEnum.MoveNext())
yield return selector(sourceEnum.Current, otherEnum.Current);
}
}
}
用法
var currentValues = currentRow.Split(separatorChar);
foreach (var valueColumnPair in currentValues.Zip(columnList, (a, b) => new { Value = a, Column = b }) {
valueMap.Add(valueColumnPair.Column, valueColumnPair.Value);
}
而不是创建两个单独阵列的你可以使一个二维阵列,或一个字典(这将是更好)。不过说真的,如果它的工作原理我不会试图去改变它。