한 번에 두 배의 배열을 반복하는 방법은 무엇입니까?
-
20-08-2019 - |
문제
텍스트 파일을 구문 분석하는 동안 두 개의 배열이 만들어졌습니다. 첫 번째는 열 이름을 포함하고 두 번째는 현재 행의 값을 포함합니다. 지도를 만들려면 두 목록을 한 번에 반복해야합니다. 지금은 다음과 같습니다.
var currentValues = currentRow.Split(separatorChar);
var valueEnumerator = currentValues.GetEnumerator();
foreach (String column in columnList)
{
valueEnumerator.MoveNext();
valueMap.Add(column, (String)valueEnumerator.Current);
}
이것은 잘 작동하지만 내 우아함을 만족 시키지는 않으며, 어레이의 수가 두 개보다 크면 (때때로해야하기 때문에) 실제로 털이 발생합니다. 다른 사람이 Terser 관용구가 있습니까?
해결책
각 행에 요소가있는 것과 동일한 열 이름이있는 경우 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
}
또는 일반 쌍 유형을 만들고 Pairup 방법에서 투영 매개 변수를 제거 할 수 있습니다.
편집하다:
쌍 유형의 경우 호출 코드는 다음과 같습니다.
foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar))
{
// column = pair.First, value = pair.Second
}
그것은 당신이 얻을 수있는만큼 단순하게 보입니다. 예, 유틸리티 방법을 재사용 가능한 코드로 어딘가에 넣어야합니다. 내 견해로는 거의 문제가되지 않습니다. 이제 여러 배열의 경우 ...
배열이 다른 유형 인 경우 문제가 있습니다. 일반 메소드/유형 선언에서 임의의 수의 유형 매개 변수를 표현할 수 없습니다. 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];
}
}
이것은 아마도 대부분의 사람들에 의해 과잉으로 간주 될 것입니다;)
솔직히 말해서 모든 종류의 아이디어를 계속 생각해 낼 수는 있지만 기본 사항은 다음과 같습니다.
- 약간의 재사용 가능한 작업으로 호출 코드를 더 좋게 만들 수 있습니다.
- Generics의 작동 방식으로 인해 각 매개 변수 수 (2, 3, 4 ...)의 각 매개 변수 (2, 3, 4 ...)를 따로 수행해야합니다.
- 각 부품마다 동일한 유형을 사용하는 것이 행복하다면 더 잘할 수 있습니다.
기능적 언어에서 일반적으로 C#4.0의 일부가되기를 희망하는 "zip"기능을 찾을 수 있습니다. 바트 드 스모트 기존 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# 적절한 튜플 유형이 부족한 C# 이것은 상당히 crufty입니다. 당신은 결국 반환 할 것입니다 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);
}
두 개의 별도 배열을 만드는 대신 2 차원 배열 또는 사전 (더 나은)을 만들 수 있습니다. 그러나 실제로 그것이 작동한다면 나는 그것을 바꾸려고하지 않을 것입니다.