关于C#:有什么方法可以改进这个字符串slice方法吗?

Any way to improve this string slice method?

我写了这个字符串扩展一段时间了,实际上我已经有相当多的用处了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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的数组表示法。

1
"string"[start:end:step]

许多其他语言也有类似的东西。string.Slice(1)相当于string.Substring(1)string.Substring(1,-1)去掉第一个和最后一个字符。string.Substring(null,null,-1)将反转字符串。string.Substring(step:2)将返回一个字符串,其中包含所有其他字符…也类似于JS的切片,但带有一个额外的参数。

根据您的建议重新修订:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();
}


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

从API的角度来看,我考虑的是可选参数,而不是可以为空的整数。

更新

仔细阅读代码后,我可以看到,给定"start"和"end"的值为空,在考虑"step"时有特殊意义,因此,它们不能单独表示为可选int参数,但是它们仍然可以是可选参数。

在更仔细地研究了代码之后,它是一个有点古怪的API,因为各个参数的值相互影响。我之前的评论暗示了这一点。要解决这个问题,您真的需要知道实现,而不是通常的一个好的API方面。可能会带来难以阅读的体验。

我可以看到如何使用"step"来反转字符串,这可能很有用。但反向扩展方法是否更好呢?更具可读性,更不像是精神上的减速带。


当我向python请求"abcdefghijklmn"[::6]时,它返回'agm',但当我向函数请求"abcdefghijklmn".Slice(step:6)时,它返回"ag"

我建议删除不正确的length计算,然后这样执行循环:

1
2
3
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]);


我能看到三件事,非常小的一件

将内部if改为三元形式

1
2
3
4
5
6
7
8
9
10
        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?进入价值

改变

1
   var sb = new StringBuilder(length);

进入之内

1
   StringBuilder sb = new StringBuilder(length);

最大的问题是,如果它做了它需要的,为什么要修复它?

更新以演示如何使用Linq,速度要慢得多(是否有加快速度的方法?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
    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();
                }
            }

        }
    }