Обновить
58

Пользователь

1
Рейтинг
51
Подписчики
Отправить сообщение
По-моему, ничего в этом страшного нет. Как вы, например, без флага-квантификатора разрулите в регулярном выражении «жадный» захват использовать или «ленивый», а тут очень похожая ситуация — выбрать все элементы или ни одного.
Ведь что мешает вызвать Slice(100, 200) на бесконечной выборке, если я хочу получить конечное «окно»

Именно в этом случае, на мой взгляд, проще и лучше использовать связку skip-take.

Каждый метод имеет свои преимущества и недостатки в зависимости от условий и целей использования, но когда мы стремимся сделать единый совершенно универсальный метод на все случаи жизни, то зачастую излишне усложняем реализацию, теряем контроль и гибкость, получая лишь сомнительный выигрыш в распространённых ситуациях, но значительный проигрыш в предельных, по моему мнению.
Хорошенько подумал над вашим замечанием и понял, что оно из разряда вещей подобных флагу RemoveEmptyEntries у метода string.Split() либо квантификаторам «ленивого» и «жадного» захвата в регулярных выражениях, указывающим, наибольшее или наименьшее по длине вхождение нужно искать. Поэтому, чтобы метод Slice стал функционально полным, достаточно ввести

    [Flags]
    public enum SliceOptions
    {
        None = 0,
        Lazy = 1,
    }

И слегка модифицировать сам метод

Slice
        public static IEnumerable<T> Slice<T>(
            this IEnumerable<T> collection,
            int head,
            int tail = 0,
            SliceOptions options = SliceOptions.None)
        {
            var items = collection as T[] ?? collection.ToArray();
            var count = items.Count();
            head = head < 0 ? count + head : head;
            tail = tail < 0 ? count + tail : tail;

            if (head < 0 || count - 1 < head) throw new ArgumentOutOfRangeException("head");
            if (tail < 0 || count - 1 < tail) throw new ArgumentOutOfRangeException("tail");

            if (head == tail && (options & SliceOptions.Lazy) == SliceOptions.Lazy)
            {
                yield break;
            }

            if (head < tail)
            {
                foreach (var item in items.Skip(head).Take(tail - head))
                {
                    yield return item;
                }
            }
            else
            {
                foreach (var item in items.Skip(head))
                {
                    yield return item;
                }

                foreach (var item in items.Skip(0).Take(tail))
                {
                    yield return item;
                }
            }
        }


Благодарю за констуктивную критику, это помогает в стремлении к совершенству.
Конечно, если мы пишем свою реализацию IEnumerable, которая при выполнении Last() и Count() вместо того, чтобы загружать весь список с данных с сервера или БД, будет транслировать это в соответствующий запрос (например, атомарный для Count) и загружать только нужную часть (виртуализация), то да — это будет оптимально. Но стандартные реализации List, Array и прочие ничего подобного не могут.

Или вы что-то другое подразумеваете под специальными случаями?
Сходил в магазин, поразмыслил, и на ум пришла идея:

var enumerator = i == j ? items.Take(0) : items.Slice(i, j);

Быть может, это решит вашу проблему? Не вижу ничего плохого в таком способе =)
Вот именно, что решит. Только пропадут отрицательные индексы и возможность зацикливания, да и не намного лучше будет выглядеть, чем items.Skip(i).Take(n). =)
Я это вижу так, но ваш вопрос лучше задать архитекторам языка Питон, зачем ещё такое могло понадобиться? )
Ок, назовём это всё интеллектуальным упражнением, а не математической моделью ;)
Да, снова моя неточность. Исправил, спасибо! Но теперь уж, думаю, всё точно :)
Почему же в реализации Питона хвост не включается? ) Кто-то тоже напутал?
Тогда воспринимайте это как абстрактную математическую конструкцию, которой пока ещё не нашлось применения :)

Мне эта штука показалась стройной и красивой, заставила немного пошевелить мозгом, поэтому решил поделиться ею с другими людьми. Быть может, кто-то и найдёт для себя практическую ценность в ней.

По крайней мере, как задачка для развития логики мышления, она очень подходит…
Я знаю, что вас не убедить :)

Но спасибо, что приняли участие и указали на неточность в статье!

Соглашусь с вами, что это больше напоминает способ нотации и понятие «пустой слайс» в нём не определено. Но и такая нотация даёт некоторые преимущества. Конечно, довольно просто добавить нужный флаг (useShift по умолчанию равный true) и в случае Slice(5, 5, false) возвращать вместо сдвига пустое значение, если это нужно.
Про пустую выборку я соврал, извините, будет один элемент.
Уже это исправил, надеюсь, теперь всё стало на свои места.

Словами по шестой, а в коде по седьмой?

По шестой включительно (или по седьмой, не включая, как в коде)

Спасибо, что внимательно читаете и обнауживаете неточности!
Наглядные примеры в статье. Конечно, каждый день таких необходимотей возникать не будет, но сдвиги элементов в массивах — распостранённое явление и, как видим, это частный случай выполнения «среза», поэтому логично и красиво обобщить эти операции в один метод, на мой взгляд.
метод не рассчитан на такие коллекции. это то же самое, как случайно вызвать Count(), ToList() или ToArray() у запроса с милионами результатов. вы же не отказываетесь от использования этих методов только из-за того, что они несут в себе такую потенциальную опасность повесить ваше приложение.
а он нужен?
вот так
Enumerable.Empty<T>()

теперь всё становится логично?
Спасибо, разобрались, заметили ошибку! Мой косяк, исправлю. Да, будет один элемент. Пустого результата нет.
Не знаю, как у вас, но в подавляющем большинстве случаев обрабатываются конечные коллекции. За всю мою практику ни разу не доводилось обрабатывать бесконечную. Разве что на собеседовании была задачка, как обнаружить возможную зацикленность в бесконечном однонаправленном списке.

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

Шаг var items = collection as T[] ?? collection.ToArray() я оставил осознанно, поскольку метод расчитан в большинстве своём на материализованные коллекции, а если вас это пугает, то сделайте свою реализацию без отрицательных индексов и проверки длины. Не трудно.

Информация

В рейтинге
2 043-й
Зарегистрирован
Активность