Может кто-нибудь придумать лучшую версию этого перечислителя? - PullRequest
2 голосов
/ 24 ноября 2010

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

Может кто-нибудь придумать более элегантный кусок кода?В первую очередь меня интересуют реализации C #, но если у кого-то есть трехсимвольная реализация APL, это тоже круто.

Ответы [ 6 ]

0 голосов
/ 25 ноября 2010

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

public static IEnumerable<T> WalkRanges<T>(IEnumerable<T> source, List<Pair<int, int>> ranges)
{
    int currentIndex = 0;
    int currentRangeIndex = 0;
    int maxRangeIndex = ranges.Length;
    bool done = false;
    foreach(var item in source)
    {
        if(currentIndex > range[currentRangeIndex].Second)
        {
           while(currentIndex > range[currentRangeIndex].Second)
           {
               if(!++currentRangeIndex < maxRangeIndex)
               {
                   // We've passed last range => 
                   // set done = true to break outer loop and then break
                   done = true;
                   break;
               }
           }
           if(currentIndex > range[currentRangeIndex].First)
               yield item; // include if larger than first since we now it's smaller than second
        }
        else if(currentIndex > range[currentRangeIndex].First)
        {
            // If higher than first and lower than second we're in range
            yield item;
        }
        if(done) // if done break outer loop
            break;

        currentIndex++; // always increase index when advancint through source
    }
}
0 голосов
/ 24 ноября 2010

Вы можете выполнить итерацию по коллекции вручную, чтобы предотвратить перечисление текущим элементом перечислителя при его пропуске:

public static IEnumerable<T> WalkRanges<T>(IEnumerable<T> source, List<Pair<int, int>> ranges)
{
    Debug.Assert(ranges == null || ranges.Count > 0);

    int currentItem = 0;
    Pair<int, int> currentRange = new Pair<int, int>();
    int currentRangeIndex = -1;
    bool betweenRanges = false;
    if (ranges != null)
    {
        currentRange = ranges[0];
        currentRangeIndex = 0;
        betweenRanges = currentRange.First > 0;
    }

    using (IEnumerator<T> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {

            if (ranges != null)
            {
                if (betweenRanges)
                {
                    if (currentItem == currentRange.First)
                        betweenRanges = false;
                    else
                    {
                        currentItem++;
                        continue;
                    }
                }
            }

            yield return enumerator.Current;

            if (ranges != null)
            {
                if (currentItem == currentRange.Second)
                {
                    if (currentRangeIndex == ranges.Count - 1)
                        break; // We just visited the last item in the ranges

                    currentRangeIndex = currentRangeIndex + 1;
                    currentRange = ranges[currentRangeIndex];
                    betweenRanges = true;
                }
            }

            currentItem++;
        }
    }
}
0 голосов
/ 24 ноября 2010

Как насчет этого (не проверено)?Должны иметь довольно похожие характеристики производительности (чистая потоковая передача, без ненужной буферизации, быстрый выход), но легче следовать, IMO:

public static IEnumerable<T> WalkRanges<T>(IEnumerable<T> source,
                                           List<Pair<int, int>> ranges)
{
    if (source == null)
        throw new ArgumentNullException("source");

    // If ranges is null, just return the source. From spec.
    return ranges == null ? source : RangeIterate(source, ranges);
}

private static IEnumerable<T> RangeIterate<T>(IEnumerable<T> source, 
                                              List<Pair<int, int>> ranges)
{
    // The key bit: a lazy sequence of all valid indices belonging to
    // each range. No buffering.
    var validIndices = from range in ranges
                       let start = Math.Max(0, range.First)
                       from validIndex in Enumerable.Range(start, range.Second - start + 1)
                       select validIndex;

    int currentIndex = -1;

    using (var indexErator = validIndices.GetEnumerator())
    {
        // Optimization: Get out early if there are no ranges.
        if (!indexErator.MoveNext())
            yield break;

        foreach (var item in source)
        {
            if (++currentIndex == indexErator.Current)
            {
               // Valid index, yield.
                yield return item;

                // Move to the next valid index.
                // Optimization: get out early if there aren't any more.
                if (!indexErator.MoveNext())
                    yield break;
            }
        }
    }
}

Если вы не возражаете против буферизации индексов, вы можете сделать что-то подобное, что еще яснее, ИМО:

public static IEnumerable<T> WalkRanges<T>(IEnumerable<T> source, 
                                           List<Pair<int, int>> ranges)
{
    if (source == null)
        throw new ArgumentNullException("source");

    if (ranges == null)
        return source;

    // Optimization: Get out early if there are no ranges.    
    if (!ranges.Any())
        return Enumerable.Empty<T>();

    var validIndices = from range in ranges
                       let start = Math.Max(0, range.First)
                       from validIndex in Enumerable.Range(start, range.Second - start + 1)
                       select validIndex;

    // Buffer the valid indices into a set.
    var validIndicesSet = new HashSet<int>(validIndices);

    // Optimization: don't take an item beyond the last index of the last range.
    return source.Take(ranges.Last().Second + 1)
                 .Where((item, index) => validIndicesSet.Contains(index));

}
0 голосов
/ 24 ноября 2010

Вот мой дубль.Мне легче понять, если не элегантнее.

public static IEnumerable<T> WalkRanges<T>(IEnumerable<T> source, List<Tuple<int, int>> ranges)
{
    if (ranges == null)
        return source;

    Debug.Assert(ranges.Count > 0);
    return WalkRangesInternal(source, ranges);
}

static IEnumerable<T> WalkRangesInternal<T>(IEnumerable<T> source, List<Tuple<int, int>> ranges)
{
    int currentItem = 0;
    var rangeEnum = ranges.GetEnumerator();
    bool moreData = rangeEnum.MoveNext();

    using (var sourceEnum = source.GetEnumerator())
        while (moreData)
        {
            // skip over every item in the gap between ranges
            while (currentItem < rangeEnum.Current.Item1
               && (moreData = sourceEnum.MoveNext()))
                currentItem++;
            // yield all the elements in the range
            while (currentItem <= rangeEnum.Current.Item2
               && (moreData = sourceEnum.MoveNext()))
            {
                yield return sourceEnum.Current;
                currentItem++;
            }
            // advance to the next range
            moreData = rangeEnum.MoveNext();
        }
}
0 голосов
/ 24 ноября 2010

Вы можете скопировать исходный список в массив, а затем для каждого диапазона вы можете заблокировать копирование из вашего нового исходного массива в целевой массив в нужном месте.Если вы можете передать исходную коллекцию в виде массива, это сделает этот подход еще лучше.Если вам нужно сделать начальную копию, это O (N) для этой операции плюс O (M), где M - общее количество элементов в конечном массиве.Таким образом, в любом случае он заканчивается O (N).

0 голосов
/ 24 ноября 2010

Возможно, используйте linq на вашем source что-то вроде:

public static IEnumerable<T> WalkRanges<T>(IEnumerable<T> source, List<Pair<int, int>> ranges)
{
    if(ranges == null)
        return null;
    return source.Where((item, index) => ranges.Any(y => y.First < index && y.Second > index)).AsEnumerable();
}

У меня нет моего ПК с Windows передо мной, и я не уверен, что правильно понял ваш код, но я пыталсявместо этого, чтобы понять ваш текст, и приведенный выше код может сработать .... или что-то в этом роде.

ОБНОВЛЕНО: Что касается проблемы производительности, я бы порекомендовал вам проверить производительность с помощью простого тестаи время обеих функций.

...