Есть ли способ улучшить этот метод среза строки?

Я написал это расширение строки некоторое время назад, и на самом деле я получаю от него довольно много пользы.

public static string Slice(this string str, int? start = null, int? end = null, int step = 1)
{
    if (step == 0) throw new ArgumentException("Step cannot be zero.", "step");

    if (start == null)
    {
        if (step > 0) start = 0;
        else start = str.Length - 1;
    }
    else if (start < 0)
    {
        if (start < -str.Length) start = 0;
        else start += str.Length;
    }
    else if (start > str.Length) start = str.Length;

    if (end == null)
    {
        if (step > 0) end = str.Length;
        else end = -1;
    }
    else if (end < 0)
    {
        if (end < -str.Length) end = 0;
        else end += str.Length;
    }
    else if (end > str.Length) end = str.Length;

    if (start == end || start < end && step < 0 || start > end && step > 0) return "";
    if (start < end && step == 1) return str.Substring((int)start, (int)(end - start));

    int length = (int)(((end - start) / (float)step) + 0.5f);
    var sb = new StringBuilder(length);
    for (int i = (int)start, j = 0; j < length; i += step, ++j)
        sb.Append(str[i]);
    return sb.ToString();
}

Поскольку это сейчас во всех моих проектах, мне интересно, мог бы я сделать это лучше. Более эффективно, или в любом случае это даст неожиданный результат?


Кусочек. Он работает как нотация массива Python.

 "string"[start:end:step]

Во многих других языках тоже есть что-то подобное. string.Slice(1) эквивалентно string.Substring(1). string.Substring(1,-1) обрезает первый и последний символ. string.Substring(null,null,-1) перевернет строку. string.Substring(step:2) вернет строку с каждым вторым символом ... также похожую на фрагмент JS, но с дополнительный аргумент.


Переработано с учетом ваших предложений:

public static string Slice(this string str, int? start = null, int? end = null, int step = 1)
{
    if (step == 0) throw new ArgumentException("Step size cannot be zero.", "step");

    if (start == null) start = step > 0 ? 0 : str.Length - 1;
    else if (start < 0) start = start < -str.Length ? 0 : str.Length + start;
    else if (start > str.Length) start = str.Length;

    if (end == null) end = step > 0 ? str.Length : -1;
    else if (end < 0) end = end < -str.Length ? 0 : str.Length + end;
    else if (end > str.Length) end = str.Length;

    if (start == end || start < end && step < 0 || start > end && step > 0) return "";
    if (start < end && step == 1) return str.Substring(start.Value, end.Value - start.Value);

    var sb = new StringBuilder((int)Math.Ceiling((end - start).Value / (float)step));
    for (int i = start.Value; step > 0 && i < end || step < 0 && i > end; i += step)
        sb.Append(str[i]);
    return sb.ToString();
}

person mpen    schedule 24.11.2010    source источник
comment
Что он должен делать? Я знаю, что смогу решить это, но мне немного лень ...   -  person ChrisF    schedule 24.11.2010
comment
Я заинтригован, знаю, для чего вы его используете? Шаговый бит интригует. Я понимаю, что он делает, но каково его практическое применение. Просто интересно.   -  person Tim Lloyd    schedule 24.11.2010
comment
слишком много, если еще. и самый длинный метод расширения, который я видел   -  person DarthVader    schedule 24.11.2010
comment
Похоже, вы могли просто использовать уже существующие методы расширения Skip() и Take() для выполнения той же задачи ...   -  person Quintin Robinson    schedule 24.11.2010
comment
так что Slice (abcdef, null, null, 2) вернет ace?   -  person Fredou    schedule 24.11.2010
comment
@Quintin Robinson, одно, все будет быстрее, чем LINQ, но может быть действительно некрасиво на вид   -  person Fredou    schedule 24.11.2010
comment
@Fredou Я не согласен с вашим утверждением, что все будет быстрее, чем LINQ   -  person Quintin Robinson    schedule 24.11.2010
comment
@ Фреду: Совершенно верно.   -  person mpen    schedule 24.11.2010
comment
@Quintin Robinson, stackoverflow.com / questions / 3769989 /   -  person Fredou    schedule 24.11.2010
comment
@Fredou, который сильно зависит от контекста, вопрос не дает доказательств того, что LINQ сам по себе является узким местом, особенно с учетом широты того, что на самом деле означает LINQ. Любой может написать код, который плохо работает с множеством различных соглашений (я говорю это только потому, что предоставленный тест является чисто бессмысленным кодом).   -  person Quintin Robinson    schedule 24.11.2010
comment
@Quintin Robinson, вам нужно показать мне хотя бы один пример того, что LINQ превзойдет по скорости, ручную реализацию (хороший, неплохой)   -  person Fredou    schedule 24.11.2010
comment
@Fredou Нет, если хочешь, я позволю тебе самому узнать об этом. Вы должны предоставить доказательства того, что все, что угодно, будет быстрее, чем LINQ.   -  person Quintin Robinson    schedule 24.11.2010
comment
@Quintin Robinson, возможно, вам тоже захочется это прочитать stackoverflow.com/questions/1185209/ и stackoverflow.com/questions/1182922/   -  person Fredou    schedule 24.11.2010
comment
@Fredou Спасибо за дополнительные ссылки, поверьте мне, я очень хорошо разбираюсь, поэтому у меня есть спор с вашим утверждением. Надеюсь, я не буду слишком спорить.   -  person Quintin Robinson    schedule 24.11.2010
comment
@Mark, извините за эту дискуссию, но люди всегда используют LINQ в качестве ответа, но никогда не думают о влиянии, если это сильно называется :-)   -  person Fredou    schedule 24.11.2010
comment
@Quintin Robinson, взято из одной из моих ссылок, авторы LINQ in Action провели сравнительный анализ запросов for, foreach, List ‹T› .FindAll и LINQ, которые выполняли одно и то же. В зависимости от того, как были построены запросы, LINQ был всего примерно на 10% медленнее. По их словам, LINQ не предоставляется бесплатно.   -  person Fredou    schedule 24.11.2010
comment
@Fredou Также из одной из ваших ссылок ... LINQ значительно улучшает выразительность кода, работающего с данными ... и не так уж сложно написать код, который хорошо работает, если вы потратите время, чтобы понять LINQ, чтобы начать с . Если бы кто-нибудь сказал мне не использовать LINQ (особенно LINQ to Objects) из соображений скорости, я бы рассмеялся ему в лицо.   -  person Quintin Robinson    schedule 24.11.2010
comment
@ Квинтин Робинсон, и это цитата Джона Скита ...   -  person jb.    schedule 24.11.2010
comment
@Quintin Robinson, я не говорил не использовать LINQ. Я сказал, что LINQ всегда медленнее, чем ручная реализация, я много использую LINQ и ценю это, но я никогда не видел ни одного, только один пример, где LINQ был быстрее :-)   -  person Fredou    schedule 24.11.2010
comment
@Fredou Звучит как конкретная реализация, извините, что вы столкнулись с ловушками, как и вы. Надеюсь, ваши будущие усилия будут более плодотворными! знак равно   -  person Quintin Robinson    schedule 24.11.2010
comment
@Quintin Robinson, посмотрите на мой ответ ниже, я поставил решение LINQ, протестирую его и расскажу, как бы вы его ускорили, потому что оно очень медленное.   -  person Fredou    schedule 25.11.2010
comment
Марк: В вашей функции есть ошибка. См. Мой ответ (stackoverflow.com/questions/4270928/), чтобы найти способ исправить это.   -  person Gabe    schedule 25.11.2010


Ответы (3)


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

С точки зрения API я бы рассмотрел необязательные аргументы, а не целые числа, допускающие значение NULL.

Обновить

Внимательно прочитав код, я вижу, что присвоение "start" и "end" значения null имеет особое значение при рассмотрении "шага", поэтому они не могут быть представлены только как необязательные параметры int, однако, они все еще могут быть необязательными параметрами.

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

Я вижу, как «шаг» можно использовать для переворота строки, что потенциально полезно. Но разве для этого не лучше использовать метод обратного расширения? Гораздо более читабельный и менее мысленный «лежачий полицейский».

person Tim Lloyd    schedule 24.11.2010
comment
Вы имеете в виду, что тогда я должен перегрузить метод несколько раз? Не могу этого сделать, потому что все они целые ... он не узнает, что есть что. Лучше работает с .net 4, где вы можете просто перейти string.Slice(end:-1), чтобы пропустить первые 2 аргумента. - person mpen; 24.11.2010
comment
@Mark Отсутствие дополнительных аргументов - это новая функция языка C # 4.0. Я обновил свой ответ ссылкой. - person Tim Lloyd; 24.11.2010
comment
@chibacity: я не понимаю. Как вы хотите, чтобы я сделал их необязательными, если они не могут быть нулевыми? Мне нужно дать им значение по умолчанию. 0 - допустимое значение, поэтому мне нужно использовать что-то еще. - person mpen; 24.11.2010
comment
Python делает это. Попробуйте "string"[::-1]. Вы получите гнирты. Пользователю не нужно думать о том, каким будет начальное и конечное значение. Если они опущены, это означает, что нужно использовать всю строку или от начала до конца. Где начало и конец имеют значение алгоритмически, но не должны иметь значения для пользователя API. - person mpen; 24.11.2010
comment
@Mark Я думаю, что bling.Reverse () намного читабельнее, но это только мое мнение. :) - person Tim Lloyd; 24.11.2010
comment
@chibacity: Тогда они могут это использовать. Это универсальный метод. Это не обязательно должно быть -1. Это может быть -2. Затем вам придется перевернуть его и затем взять все остальные символы. Если ты хочешь это сделать, то хорошо, но ты мог бы убить двух зайцев одним выстрелом, не так ли? - person mpen; 24.11.2010
comment
@Mark Я вижу, что он гибкий. Мне просто интересно, действительно ли возможность создать перевернутую строку из любых других n символов так полезна на практике. - person Tim Lloyd; 24.11.2010
comment
@chibacity: Наверное, нет. В 99% случаев я использую только первые два аргумента :) - person mpen; 24.11.2010

Я вижу 3 вещи, очень незначительную

измените внутреннее if на тернарное, например

        if (start == null)
        {
            start = step > 0 ? 0 : str.Length - 1;
        }
        else if (start < 0)
        {
            start = start < -str.Length ? 0 : str.Length + start;
        }
        else if (start > str.Length) 
            start = str.Length; 

можно изменить (int) int? на int.Value

изменять

   var sb = new StringBuilder(length);

в

   StringBuilder sb = new StringBuilder(length);

и главный вопрос: если он делает то, что ему нужно, зачем его исправлять?


обновление, чтобы показать, как это сделать с LINQ, намного медленнее (есть ли способ ускорить его?)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                Stopwatch sw;
                string str;

                sw = Stopwatch.StartNew();
                for (int i = 0; i < 1000000; i++)
                    str = "Step cannot be zero.".Slice(null, null, -3, true);
                sw.Stop();
                Console.WriteLine("LINQ " + sw.Elapsed.TotalSeconds.ToString("0.#######") + " seconds");

                sw = Stopwatch.StartNew();
                for (int i = 0; i < 1000000; i++)
                    str = "Step cannot be zero.".Slice(null, null, -3, false);
                sw.Stop();
                Console.WriteLine("MANUAL " + sw.Elapsed.TotalSeconds.ToString("0.#######") + " seconds");

                Console.ReadLine();
            }
        }

       static class  test
        {
            public static string Slice(this string str, int? start, int? end, int step, bool linq)
            {
                if (step == 0) throw new ArgumentException("Step cannot be zero.", "step");

                if (linq)
                {

                    if (start == null) start = 0;
                    else if (start > str.Length) start = str.Length;

                    if (end == null) end = str.Length;
                    else if (end > str.Length) end = str.Length;

                    if (step < 0)
                    {
                        str = new string(str.Reverse().ToArray());
                        step = Math.Abs(step);
                    }
                }
                else
                {
                    if (start == null)
                    {
                        if (step > 0) start = 0;
                        else start = str.Length - 1;
                    }
                    else if (start < 0)
                    {
                        if (start < -str.Length) start = 0;
                        else start += str.Length;
                    }
                    else if (start > str.Length) start = str.Length;

                    if (end == null)
                    {
                        if (step > 0) end = str.Length;
                        else end = -1;
                    }
                    else if (end < 0)
                    {
                        if (end < -str.Length) end = 0;
                        else end += str.Length;
                    }
                    else if (end > str.Length) end = str.Length;


                }

                if (start == end || start < end && step < 0 || start > end && step > 0) return "";
                if (start < end && step == 1) return str.Substring(start.Value, end.Value - start.Value);

                if (linq)
                {
                    return new string(str.Skip(start.Value).Take(end.Value - start.Value).Where((s, index) => index % step == 0).ToArray ());;
                }
                else
                {
                    int length = (int)(((end.Value - start.Value) / (float)step) + 0.5f);
                    var sb = new StringBuilder(length);
                    for (int i = start.Value, j = 0; j < length; i += step, ++j)
                        sb.Append(str[i]);
                    return sb.ToString();
                }
            }

        }
    }
person Fredou    schedule 24.11.2010
comment
Изменение с var на StringBuilder носит чисто косметический характер и является вопросом личного выбора или стандартов кодирования компании. Это не влияет на эффективность (или иначе) кода. - person ChrisF; 25.11.2010
comment
Удивлен, что не увидел там возможности для тернарного оператора ... Я обычно все время переборщил! Спасибо :) Не знал, что умею int?.Value. - person mpen; 25.11.2010

Когда я прошу Python "abcdefghijklmn"[::6], он возвращает 'agm', но когда я прошу вашу функцию "abcdefghijklmn".Slice(step:6), он возвращает "ag".

Я бы рекомендовал удалить неверный расчет length и просто выполнить ваш цикл следующим образом:

var sb = new StringBuilder((end - start).Value / step);
for (int i = start.Value; step > 0 && i < end || step < 0 && i > end; i += step)
    sb.Append(str[i]);
person Gabe    schedule 25.11.2010
comment
Боялся, что в этом бите все еще может быть ошибка. Спасибо!! - person mpen; 25.11.2010
comment
Хм ... 14/6 = 2,33. +.5 должен был округлить его до 3 ... может быть, ceil было бы более подходящим? - person mpen; 25.11.2010