Альтернатива 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 извините за плохой каламбур.)

713   6  

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

    Ничего не найдено.