有什么办法可以改进这个字符串切片方法吗?

发布于 2024-10-03 21:21:50 字数 2838 浏览 0 评论 0原文

我不久前写了这个字符串扩展,实际上我已经充分利用了它。

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();
}

I wrote this string extension awhile back, and I'm actually getting quite a bit of use out of it.

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();
}

Since it's in all my projects now, I'm wondering if I could have done it better. More efficient, or would it produce unexpected results in any case?


Slice. It works like Python's array notation.

 "string"[start:end:step]

Many other languages have something like this too. string.Slice(1) is equivalent to string.Substring(1). string.Substring(1,-1) trims off the first and last character. string.Substring(null,null,-1) will reverse the string. string.Substring(step:2) will return a string with every other character... also similar to JS's slice but with an extra arg.


Re-revised based on your suggestions:

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();
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

玩世 2024-10-10 21:21:50

如果您有大量测试用例,并且希望尝试不同的实现,那么检测意外结果应该不是问题。

从 API 的角度来看,我会考虑使用 可选参数 而不是可为 null 的整数。

更新

仔细阅读代码后,我发现将“start”和“end”赋予 null 值,在考虑“step”时具有特殊含义,因此无法表示它们然而,单独作为可选 int 参数,它们仍然可以是可选参数。

更仔细地查看代码后,您会发现它是一个有点时髦的 API,因为各个参数的值会相互影响。我之前的评论提到了这一点。您确实必须了解实现才能解决此问题,而不是通常良好的 API 方面。并且可能会造成难以阅读的体验。

我可以看到如何使用“step”来反转字符串,这可能很有用。但是反向扩展方法不是更好吗?更具可读性,更少的精神障碍。

If you have plenty of test cases, then detecting unexpected results shouldn't be an issue if you wish to experiment with different implementations.

From an API perspective I would consider optional arguments rather than nullable ints.

Update

After reading the code closely, I can see that giving "start" and "end" a value of null, has a special meaning when taking "step" into consideration, therefore, they could not be represented as optional int parameters alone, however, they could still be optional parameters.

After looking at the code more closely, it's a bit of a funky API as the values of individual parameters have an affect on each other. My previous comment alludes to this. You really have to know the implementation to work this out, not generally a good API aspect. And possibly makes for a difficult readability experience.

I can see how "step" can be used to reverse a string, which is potentially useful. But wouldn't a Reverse extension method be better for this? Much more readable and less of a mental speedbump.

清眉祭 2024-10-10 21:21:50

我可以看到三件事,非常小的一个

将内部 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();
                }
            }

        }
    }

I can see 3 things, very really minor one

change the inner if into ternary like

        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; 

maybe change the (int)int? into int.Value

change

   var sb = new StringBuilder(length);

into

   StringBuilder sb = new StringBuilder(length);

and the big question is, if it does what it need, why fixing it?


update to show how to do it with LINQ, way slower (is there a way to speed it up?)

    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();
                }
            }

        }
    }
无名指的心愿 2024-10-10 21:21:50

当我向 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]);

When I ask Python for "abcdefghijklmn"[::6] it returns 'agm', but when I ask your function for "abcdefghijklmn".Slice(step:6) it returns "ag".

I would recommend removing the incorrect length calculation and just performing your loop like this:

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]);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文