Как стать автором
Обновить
54
-3

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

Отправить сообщение
Динамичным сделать такой грид ещё сложнее, чем обычный…
Решарпер, да, не понимает такую запись, но это не столь критичный недостаток.
Код не стал лаконичнее.

Конечно, у вас своё видение, но, по-моему, он стал компактнее в разы да и сложность его не такая высокая :)
Правильнее, на мой взгляд, установить у ненужного элемента свойство Visibility в состояние Collased, либо же использовать ItemsControl. Даже если вы удалите строку номер два, то сам контрол останется в визуальном дереве и код останется засорён, если это можно так назвать.

Хотелось бы иметь возможность указывать номер строки/столбца не абсолютно, а как-нибудь относительно.

В статье описан сам принцип и базовый синтаксис, усовершенствовать реализацию можно как угодно, на что хватит фантазии :)
Скажите, вы пробовали заново переопубликовывать ваши игры, но под другими названиями? Только предварительно подготовившись…

Например, можно использовать старую базу пользователей и попросить их обновиться до свежей версии вручную, как раз в тот момент, когда вы опубликуете апдейт, но уже как новое приложение. Опыт на Windows Phone показывает, что если не воспользоваться сполна первыми неделями сразу после публикации, то никакими вменяемыми действиями и крутыми апдейтами уже ситуацию с приложением не выправишь. Шансы попасть в какой-либо топ максимальны лишь сразу после публикации, а потом крайне резко снижаются.
Материал обновлён.
Замечу насчёт композиции функций, что SkipByRing(x).TakeByRing(y) в кольцевом обобщении не равнозначно SliceByRing(x,y), хотя в простых случаях, когда нет полного обхода кольца, они дают идентичный результат.
Как вам такой способ?
        private static void Main(string[] args)
        {
            var bodyLetters = new[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I'};
            var headIndexes = new[] { 0 ,  1 ,  2 ,  3 ,  4 ,  5 ,  6 ,  7 };
            var tailIndexes = new[] {-8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 };

            // CDEFGICDEF
            bodyLetters.SkipByRing(18).TakeByRing(10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // FEDCBAFEDC
            bodyLetters.SkipByRing(-18).TakeByRing(10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // IGFEDCIGFE
            bodyLetters.SkipByRing(18).TakeByRing(-10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // ABCDEFABCD
            bodyLetters.SkipByRing(-18).TakeByRing(-10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            Console.WriteLine();

            // CDEFGIABCD
            bodyLetters.SliceByRing(18, 10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // GIABCDEFGI
            bodyLetters.SliceByRing(-18, 10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // BAIGFEDCBA
            bodyLetters.SliceByRing(18, -10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // FEDCBAIGFE
            bodyLetters.SliceByRing(-18, -10).ToList().ForEach(Console.Write);
            Console.WriteLine();

            Console.ReadKey();
        }

Имплементация
        // ReSharper disable PossibleMultipleEnumeration
        // ReSharper disable LoopCanBePartlyConvertedToQuery
        public static IEnumerable<T> SkipByRing<T>(this IEnumerable<T> source, int count)
        {
            var originalCount = 0;
            var reverse = count < 0;
            count = reverse ? -count : count;
            source = reverse ? source.Reverse() : source;

            while (true)
            {
                if (originalCount > 0) count %= originalCount;
                foreach (var item in source)
                {
                    originalCount++;
                    if (count > 0)
                    {
                        count--;
                        continue;
                    }
                    yield return item;
                }

                if (count == 0) yield break;
            }
        }

        public static IEnumerable<T> TakeByRing<T>(this IEnumerable<T> source, int count)
        {
            var reverse = count < 0;
            count = reverse ? -count : count;
            source = reverse ? source.Reverse() : source;

            while (true)
            {
                foreach (var item in source)
                {
                    if (count > 0)
                    {
                        count--;
                        yield return item;
                    }
                }

                if (count == 0) yield break;
            }
        }

        public static IEnumerable<T> SliceByRing<T>(this IEnumerable<T> source, int skipCount, int takeCount)
        {
            var originalCount = 0;
            var skipReverse = skipCount < 0;           
            var takeReverse = takeCount < 0;
            skipCount = skipReverse ? -skipCount : skipCount;
            takeCount = takeReverse ? -takeCount : takeCount;
            source = takeReverse ? source.Reverse() : source;

            if (skipReverse ^ takeReverse)
            {
                var count = source.Count();
                skipCount = count - skipCount % count;
            }

            while (true)
            {
                if (originalCount > 0) skipCount %= originalCount;
                foreach (var item in source)
                {
                    originalCount++;
                    if (skipCount > 0)
                    {
                        skipCount--;
                        continue;
                    }

                    if (takeCount > 0)
                    {
                        takeCount--;
                        yield return item;
                    }
                }

                if (takeCount == 0) yield break;
            }
        }

Так может надо подумать немножко дольше, чтобы недочетов не было?

Тогда бы я отвечал на ваши комментарии раз в день и прогресс шёл значительно медленнее.

Кстати, придумал практическое приложение методам Ring и Turn. К примеру, вы делаете UI, и у вас есть коллекция-заглушка с n-элементами, вдруг вы захотели проверить, как работает UI при 2n, 3n, ...mn элементах. Вам достаточно написать что-то вроде Items = _testItems.Ring(0, m).ToList() и всё.

Удобно же, не находите? )
Решения я генерирую в реальном времени, поэтому появление недочётов вполне закономерно.

Вы можете предложить свои оптимизации, мне самому интересно. Менять тип возвращаемого значения я не вижу смысла, так как сам метод Ring внутри срабатывает за O(1), а на выходе при необходимости можно сделать как ToList(), так и ToArray().

Также придумал последнее обобщение с количеством оборотов =)

        public static IEnumerable<T> Turn<T>(this IList<T> items, int skip, int turnsCount = 0)
        {
            var reverse = skip < 0;
            var count = items.Count;
            skip = reverse ? count + skip : skip;
            var take = turnsCount == 0
                ? reverse ? -skip - 1 : count - skip
                : count*turnsCount;
            return items.Ring(skip, take);
        }

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

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

Разве что

var index = reverse ? skip - j : skip + j;

в цикле, возможно, имеет смысл разделить на два цикла, хотя это лучше проверить на практике.
ToList я делаю лишь для того, чтобы сэкономить строчки кода в тестовом примере и только (встроенный метод ForEach() есть только у List), поэтому он здесь не обязателен.

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

Ring
        public static IEnumerable<T> Ring<T>(this IList<T> items, int skip)
        {
            var reverse = skip < 0;
            var count = items.Count;
            skip = reverse ? count + skip : skip;
            var take = reverse ? -skip - 1 : count - skip;
            return items.Ring(skip, take);
        }

        public static IEnumerable<T> Ring<T>(this IList<T> items, int skip, int take)
        {
            var reverse = take < 0;
            var count = items.Count;
            skip = skip < 0 ? count + skip : skip;
            skip = skip < count ? skip : skip%count;
            take = reverse ? -take : take;

            for (var i = 0; i < take; i++)
            {
                var j = i < count ? i : i%count;
                var index = reverse ? skip - j : skip + j;
                index = index < 0 ? count + index : index;
                index = index < count ? index : index%count;
                yield return items[index];
            }
        }

        private static void Main(string[] args)
        {
            var bodyLetters = new[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I'};
            var headIndexes = new[] { 0 ,  1 ,  2 ,  3 ,  4 ,  5 ,  6 ,  7 };
            var tailIndexes = new[] {-8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 };

            // 'C', 'D', 'E', 'F', 'G', 'I', 'A', 'B',
            bodyLetters.Ring(2, 8).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'C', 'B', 'A', 'I', 'G', 'F', 'E', 'D',
            bodyLetters.Ring(2, -8).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'E', 'F', 'G'
            bodyLetters.Ring(3, 4).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'E', 'F', 'G'
            bodyLetters.Ring(-5, 4).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'C', 'B', 'A'
            bodyLetters.Ring(-5, -4).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'E', 'F', 'G', 'I', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'I', 'A', 'B', 'C'
            bodyLetters.Ring(3, 16).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'C', 'B', 'A', 'I', 'G', 'F', 'E', 'D', 'C', 'B', 'A', 'I', 'G', 'F', 'E'
            bodyLetters.Ring(-5, -16).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'D', 'E', 'F', 'G', 'I'
            bodyLetters.Ring(3).ToList().ForEach(Console.Write);
            Console.WriteLine();

            // 'A', 'B', 'C', 'D'
            bodyLetters.Ring(-5).ToList().ForEach(Console.Write);
            Console.WriteLine();

            Console.ReadKey();
        }

Хорошо, соглашусь, что с флагами нужно быть осторожным.

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

var letters = new [] {'A', 'B, 'C', 'D'};
letters.Slice(1, 9); // BCDABCDAB
letters.Slice(-2, -9); // CBADCBADC
А это по сути не одно и то же?

При создании регулярного выражения мы можем указать флаг RegexOptions.IgnoreCase, вместе с тем можем не указывать, а использовать inline character i — результат тот же.

Конечно, если вы видите другие пути к консистентности, то можете их предложить, я со своей колокольни рассуждаю :)
По-моему, ничего в этом страшного нет. Как вы, например, без флага-квантификатора разрулите в регулярном выражении «жадный» захват использовать или «ленивый», а тут очень похожая ситуация — выбрать все элементы или ни одного.
Ведь что мешает вызвать 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). =)

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность