Альтернатива IEnumerable <T>.Skip (1) .Take (1) .Single () - PullRequest
5 голосов
/ 28 августа 2010

У меня трудное время с, казалось бы, легкой и смущающей проблемой. Все, что я хочу, это следующий элемент в IEnumberable без использования Skip (1) .Take (1) .Single (). Этот пример иллюстрирует основную проблему.

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», где «деньги» и «324» являются соседними элементами в IEnumberable, и когда анализатор встречает «деньги», которые я хочу » 324" . (а кто нет?: D Извините за плохой каламбур.)

Ответы [ 6 ]

13 голосов
/ 28 августа 2010

Все мои проблемы будут решены, если была такая функция:

_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;
        }
    }
}

Разве это не намного проще?

4 голосов
/ 28 августа 2010

Я считаю, что вы хотите это:

    public void sortAlphabet() {
        using (var enu = getAlphabet().GetEnumerator()) {
            while (enu.MoveNext()) {
                switch (enu.Current) {
                    case 'A':
                        enu.MoveNext();
                        _nextChar = enu.Current;
                        break;
                }
            }
        }
    }

Обратите внимание, что это потребляет следующий элемент, именно то, что вы хотите, если я правильно прочитал ваш вопрос.

1 голос
/ 28 августа 2010

Как было указано в другом ответе, - это метод MoveNext(), и у вас есть доступ к нему для всех перечислимых элементов через интерфейс 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

(Обратите внимание, что хотя вышеприведенный метод расширения возвращает пары из двух элементов в каждой, пары перекрываются на один элемент! По сути, вы получаете каждый элемент, а также прогноз. Если вам нужен метод расширения чтобы вернуть последний элемент самостоятельно, вы можете адаптировать его, адаптировав используемый метод буферизации.)

0 голосов
/ 28 августа 2010

Если вы используете .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 и класс Tuple.

0 голосов
/ 28 августа 2010

Ваш код будет возвращать 'B' каждый раз, потому что вы звоните getAlphabet(), который каждый раз возвращает новый IEnumerable.

Исходя из того, что вы пытаетесь сделать, я бы, вероятно,предложите использовать основанную на индексе итерацию вместо перечислителя.Если бы вы использовали MoveNext для получения следующего элемента, вы бы испортили цикл, поэтому использование поиска по индексу работало бы более аккуратно и с гораздо меньшими накладными расходами.

0 голосов
/ 28 августа 2010

Как упоминалось ранее, конечный автомат идеально подходит для этих случаев.Это также соответствует тому, как мы думаем о проблеме, поэтому удобочитаемость превосходна.Я не уверен, что именно вы хотите сделать с кодом, поэтому в приведенном ниже примере возвращается следующий символ, когда он встречается с ним.Конечный автомат хорошо масштабируется для сложных задач, его можно смоделировать и проверить вручную на бумаге.

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;
        }
    }
}
...