Squeak.ru - шаблоны программирования

Есть ли лучший способ получить подпоследовательности, в которых каждый элемент соответствует предикату?

Скажем, у меня есть IEnumerable. Например, {2,1,42,0,9,6,5,3,8}.

Мне нужно получить «серии» элементов, соответствующих предикату. Например, если мой предикат был

bool isSmallerThanSix(int number){...}

Я хотел бы получить следующий вывод: {{2,1},{0},{5,3}}

Есть ли встроенная функция, которая выполняет это?

Пока у меня это:

public static IEnumerable<IEnumerable<T>> GetSequences<T>(this IEnumerable<T> source,
      Func<T, bool> selector) {

        if (source == null || selector == null) {
            yield break;
        }

        IEnumerable<T> rest = source.SkipWhile(obj => !selector(obj));

        while (rest.Count() > 0) {
            yield return rest.TakeWhile(obj => selector(obj));
            rest = rest
                    .SkipWhile(obj => selector(obj))
                    .SkipWhile(obj => !selector(obj));
        }


    }

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

Большое спасибо за ваше время,

Риа.

20.02.2009

Ответы:


1

Насколько я знаю, нет встроенного метода. Однако вызов метода расширения Count для IEnumerable не очень эффективен, поскольку он должен перечислять список, чтобы получить количество. Поэтому я придумал это, которое имеет тот же эффект.

public static IEnumerable<IEnumerable<T>> 
    GetSequences<T>(this IEnumerable<T> source, Func<T, bool> selector)
{
    // omitted null checks for brevity
    var list = new List<T>();

    foreach(var item in source)
    {
        if (selector.Invoke(item))
        {
            list.Add(item);
        }
        else if (list.Count > 0)
        {
            yield return list;
            list = new List<T>();
        }
    }

    if (list.Count > 0)
        yield return list;
}

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

20.02.2009
  • IMO, вы не должны повторно использовать существующий список - если вызывающая сторона буферизует результаты (например, вызывая ToList()), они получат один и тот же список несколько раз. 20.02.2009
  • Тот факт, что мы придумали практически идентичные решения, безусловно, обнадеживает шансы на то, что они сработают :) 20.02.2009
  • Да я как раз об этом и думал! 20.02.2009
  • Значит, нет библиотечного метода, а? Это нехорошо со стороны людей из .Net, но большое спасибо. :) 21.02.2009

  • 2

    Я подозреваю, что ваш код не будет работать во всех случаях. В частности, SkipWhile и TakeWhile оцениваются лениво — если вызывающий код на самом деле не считывает все выданные IEnumerable<T>s (или, что еще хуже, буферизует их и читает в другом порядке!), я сильно подозреваю, что вы ошибетесь. полученные результаты.

    Я подозреваю, что вам действительно нужно сделать что-то вроде:

    public static IEnumerable<IEnumerable<T>> GetSequences<T>(
        this IEnumerable<T> source,
        Func<T, bool> selector)
    {
        List<T> current = new List<T>();
        foreach (T element in source)
        {
            if (selector(element))
            {
                current.Add(element);
            }
            else if (current.Count > 0)
            {
                yield return current;
                current = new List<T>();
            }           
        }
        if (current.Count > 0)
        {
            yield return current;
        }
    }
    

    (При этом игнорируется проверка ошибок — из-за отложенного выполнения блоков итераторов вы захотите сделать это в отдельном методе, который затем вызывает этот метод как частный — это очень распространенный шаблон при написании блоков итераторов производственного качества.)

    Кстати, выбор List<T> несколько произволен - например, вместо него вы могли бы использовать LinkedList<T>. Или, если вы выбираете список, вы можете вернуть IEnumerable<IList<T>> вместо IEnumerable<IEnumerable<T>>, что может облегчить вызывающим сторонам обработку результатов.

    20.02.2009
  • Джон, что вы подразумеваете под отложенным выполнением блоков итератора... сделать это в отдельном методе, который затем вызывает этот метод как частный? Я часто делаю это для рекурсивных подпрограмм, но это не рекурсия, поэтому я не следую этой стратегии. Не могли бы вы уточнить? Весьма признателен... 21.02.2009
  • Ах, неважно, я нашел вашу замечательную статью по этой теме здесь: msmvps.com/blogs/jon_skeet/archive/2009/01/23/. Спасибо, что так быстро ответили на мой вопрос! :-) :-п 21.02.2009
  • Я рад, что вам понравился этот пост в блоге, но более подходящим является связанный: msmvps.com/blogs/jon_skeet/archive/2008/03/02/ 21.02.2009
  • Да, я тоже нашел такой. Большое спасибо, Джон. :-) 22.02.2009

  • 3

    Не уверен, что знаю, о чем говорю, но это не помешает мне говорить...

    Имеет ли смысл определять новый тип итератора, который может переходить к следующей подпоследовательности, когда текущая завершена?

    Используя примеры синтаксиса Java и Integer, клиентский код выглядит следующим образом:

    Iterator2<Integer> iter = getSequencences(someCollection, someCriteria)
    while (iter.hasNextSequence())
    {
      iter.nextSequence();
      while (iter.hasNext())
      {
        Integer i = iter.next();
      }
    }
    

    *утки*

    20.02.2009

    4

    Работая напрямую с счетчиком, можно получить полностью отложенные результаты.

    Я бы, вероятно, использовал версию, представленную Гарри и Джоном, поскольку она проще и выполняет свою работу, но писать ее было весело.

    public static class Program
    {
        static IEnumerable<IEnumerable<T>>
            GetSequences<T>(this IEnumerable<T> source, Func<T, bool> selector)
        {
            var enumerator = source.GetEnumerator();
            while (enumerator.MoveNext())
                if (selector(enumerator.Current))
                    yield return TakeWhile(enumerator, selector);
            yield break;
        }
    
        static IEnumerable<T> 
            TakeWhile<T>(IEnumerator<T> enumerator, Func<T, bool> selector)
        {
            do
            {
                yield return enumerator.Current;
            } while (enumerator.MoveNext() && selector(enumerator.Current));
            yield break;
        }
    
        static void Main()
        {
            var nums = new[] { 2, 1, 42, 0, 9, 6, 5, 3, 8 };
            var seqs = nums.GetSequences(i => i < 6);
    
            Console.WriteLine(string.Join(",", seqs.Select(s =>
                string.Format("{{{0}}}",
                    string.Join(",", s.Select(i => i.ToString()).ToArray())
                )).ToArray()));
        }
    }
    
    20.02.2009
  • Однако у него та же проблема, что и у оригинала - вы все еще используете один счетчик. Это зависит от того, что вызывающая сторона повторяет возвращаемые последовательности точно в ожидаемое время, что не очень надежно. 21.02.2009

  • 5

    Вы можете использовать тот факт, что GroupBy будет обрабатывать элементы последовательно.

        public static IEnumerable<IEnumerable<T>> GetSequences<T>
          (this IEnumerable<T> source, Func<T, bool> predicate)
        {
            bool flag = false;
            int id = 0;
            return source.GroupBy(x =>
                {
                    bool match = predicate(x);
                    if (match != flag)
                    {
                        id += 1;
                        flag = match;
                    }
                    return new { keep = match, id = id };
                })
                .Where(g => g.Key.keep)
                .Select(g => g.AsEnumerable());
        }
    

    Вот тестовый метод:

        public static void Test1()
        {
            List<int> myList = new List<int>() {2,1,42,0,9,6,5,3,8};
    
            foreach (var g in myList.GetSequences(i => i < 6))
            {
                Console.WriteLine("g");
                foreach (int i in g)
                {
                    Console.WriteLine(i);
                }
            }
        }
    
    20.02.2009
    Новые материалы

    Угловая структура архитектуры
    Обратите внимание, что эта статья устарела, я решил создать новую с лучшей структурой и с учетом автономных компонентов: https://medium.com/@marekpanti/angular-standalone-architecture-b645edd0d54a..

    «Данные, которые большинство людей используют для обучения своих моделей искусственного интеллекта, поставляются со встроенным…
    Первоначально опубликовано HalkTalks: https://hacktown.com.br/blog/blog/os-dados-que-a-maioria-das-pessoas-usa-para-treinar-seus-modelos-de-inteligencia-artificial- ja-vem-com-um-vies-embutido/..

    Сильный ИИ против слабого ИИ: различия парадигм искусственного интеллекта
    В последние годы изучению и развитию искусственного интеллекта (ИИ) уделяется большое внимание и прогресс. Сильный ИИ и Слабый ИИ — две основные парадигмы в области искусственного интеллекта...

    Правильный способ добавить Firebase в ваш проект React с помощью React Hooks
    React + Firebase - это мощная комбинация для быстрого и безопасного создания приложений, от проверки концепции до массового производства. Раньше (знаете, несколько месяцев назад) добавление..

    Создайте API с помощью Python FastAPI
    Создание API с помощью Python становится очень простым при использовании пакета FastAPI. После установки и импорта вы можете создать приложение FastAPI и указать несколько конечных точек. Каждой..

    Веселье с прокси-сервером JavaScript
    Прокси-серверы JavaScript — это чистый сахар, если вы хотите создать некоторую общую логику в своих приложениях, чтобы облегчить себе жизнь. Вот один пример: Связь клиент-сервер Мы..

    Получить бесплатный хостинг для разработчиков | Разместите свой сайт за несколько шагов 🔥
    Статические веб-сайты — это веб-страницы с фиксированным содержанием и его постоянным содержанием. Но теперь статические сайты также обрабатывают динамические данные с помощью API и запросов...