Альтернатива IEnumerable.Пропустить(1).Возьмите(1).Одиночный()
У меня трудное время с кажущейся легкой и неловкой проблемой. Все, что мне нужно, - это следующий элемент в IEnumberable без использования Skip(1).Возьмите(1).Одиночный(). Этот пример иллюстрирует основную проблему.
private char _nextChar;
private IEnumerable<char> getAlphabet()
{
yield return 'A';
yield return 'B';
yield return 'C';
}
public void sortAlphabet()
{
foreach (char alpha in getAlphabet())
{
switch (alpha)
{
case 'A': //When A pops up, I want to get the next element, ie 'B'
_nextChar = getAlphabet().Skip(1).Take(1).Single();
break;
case 'B': //When B pops up, I want 'C' etc
_nextChar = getAlphabet().Skip(1).Take(1).Single();
break;
}
}
}
Помимо того, что он уродлив, этот пример работает. Но предположим, что IEnumerable содержит 2 миллиона элементов, тогда оператор LINQ делает выполнение программы невыносимо медленным. То, чего я хочу, очень просто. Я просто хочу следующий элемент в IEnumberable. Все мои проблемы были бы решены, если бы существовала такая функция, как:
_nextChar = getAlphabet().moveNext() //or getNext()
Гораздо предпочтительнее, если решение сохраняет ту же структуру / компоновку / функциональность примера, однако я гибок. Моя программа-это файловый парсер, и среди 2 миллионов строк текста есть некоторые ключи, такие как" money=324", где" money "и" 324 "являются соседними элементами в IEnumberable, и когда парсер натыкается на" money", я хочу"324". (а кто не знает? : D извините за плохой каламбур.)
6 ответов:
Все мои проблемы были бы решены, если бы там была функция типа:
_nextChar = getAlphabet().moveNext() //or getNext()Есть функция точно такая. Он просто принадлежит
IEnumerator<T>, а неIEnumerable<T>!private char _nextChar; private IEnumerable<char> getAlphabet() { yield return 'A'; yield return 'B'; yield return 'C'; } public void sortAlphabet() { using (var enumerator = getAlphabet().GetEnumerator()) { while (enumerator.MoveNext()) { char alpha = enumerator.Current; switch (alpha) { case 'A': if (enumerator.MoveNext()) { _nextChar = enumerator.Currrent; } else { // You decide what to do in this case. } break; case 'B': // etc. break; } } } }Но вот вам вопрос. Необходимо ли, чтобы этот код использовал
Я имею в виду, рассмотрим, как код будет выглядеть в этом случае:IEnumerable<char>, а неIList<char>? Я спрашиваю, потому что, как будто это не очевидно, код был бы намного проще, если бы у вас был случайный доступ к элементам, возвращаемымgetAlphabetиндексом (и если кто-то искушен чтобы указать, что вы можете сделать это с помощьюElementAt, пожалуйста, просто выбрось эту мысль из головы прямо сейчас).Разве это не намного проще?private char _nextChar; private IList<char> getAlphabet() { return Array.AsReadOnly(new[] { 'A', 'B', 'C' }); } public void sortAlphabet() { IList<char> alphabet = getAlphabet(); for (int i = 0; i < alphabet.Count - 1; ++i) { char alpha = alphabet[i]; switch (alpha) { case 'A': _nextChar = alphabet[i + 1]; break; case 'B': // etc. break; } } }
Я думаю, вы хотите этого:
Обратите внимание, что это потребляет следующий элемент, именно то, что вы хотите, если я правильно прочитал Ваш вопрос.public void sortAlphabet() { using (var enu = getAlphabet().GetEnumerator()) { while (enu.MoveNext()) { switch (enu.Current) { case 'A': enu.MoveNext(); _nextChar = enu.Current; break; } } } }
Как было указано в другом ответе, существует метод, и у вас есть доступ к нему для всех перечисляемых объектов через интерфейс
IEnumerator<T>, возвращаемый вызовомIEnumerable<T>.GetEnumerator(). Однако работа сMoveNext()иCurrentможет ощущаться несколько "низкоуровневой".Если вы предпочитаете цикл
foreachдля обработки вашей коллекцииgetAlphabet(), вы можете написать метод расширения, который возвращает элементы из любого перечисляемого в парах из двух:public static IEnumerable<T[]> InPairsOfTwo<T>(this IEnumerable<T> enumerable) { if (enumerable.Count() < 2) throw new ArgumentException("..."); T lastItem = default(T); bool isNotFirstIteration = false; foreach (T item in enumerable) { if (isNotFirstIteration) { yield return new T[] { lastItem, item }; } else { isNotFirstIteration = true; } lastItem = item; } }Вы бы использовали его следующим образом:
foreach (char[] letterPair in getAlphabet().InPairsOfTwo()) { char currentLetter = letterPair[0], nextLetter = letterPair[1]; Console.WriteLine("# {0}, {1}", currentLetter, nextLetter); }И ты бы ... получим следующий результат:
# A, B # B, C(обратите внимание, что в то время как приведенный выше метод расширения возвращает пары из двух элементов каждый, пары перекрываются одним элементом! Вы, по существу, получаете каждый пункт, а также Взгляд вперед. Если вы хотите, чтобы метод расширения возвращал последний элемент сам по себе, вы можете адаптировать его, адаптировав используемый метод буферизации.)
Как упоминалось ранее, государственная машина идеально подходит для этих случаев. Он также соответствует тому, как мы думаем о проблеме, поэтому читабельность отличная. Я не знаю точно, что вы хотите сделать с кодом, поэтому приведенный ниже пример возвращает следующий символ, когда он встречается с ним. Государственная машина хорошо масштабируется для решения сложных задач и может быть смоделирована и проверена вручную на бумаге.
enum State { Scan, SaveAndExit }; public void SortAlphabet() { State state = State.Scan; // initialize foreach(char c in getAlphabet()) { switch (state): { case State.Scan: if (c == 'A' || c == 'B') state = State.SaveAndExit; break; case State.SaveAndExit: return (c); break; } } }
Ваш код будет возвращать
'B'каждый раз, потому что вы вызываетеgetAlphabet(), который возвращает новыйIEnumerableкаждый раз.Исходя из того, что вы пытаетесь сделать, я бы, вероятно, предложил использовать итерацию на основе индекса вместо перечислителя. Если вы используете
MoveNextдля получения следующего элемента, вы испортите свой цикл, поэтому использование индексного извлечения будет работать более чисто с гораздо меньшими затратами.
Если вы используете .NET 4.0, то то, что вы пытаетесь достичь, очень просто:
var alphabet = getAlphabet(); var offByOneAlphabet = alphabet.Skip(1); foreach (var pair in alphabet.Zip(offByOneAlphabet, (a, b) => Tuple.Create(a, b))) Console.WriteLine("Letter: {0}, Next: {1}", pair.Item1, pair.Item2); // prints: // Letter: A, Next: B // Letter: B, Next: CЕсли вы используете что-то меньшее, чем .NET 4.0, его все еще очень легко определить свою собственную функцию Zip и класс кортежа.
Comments