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] |
许多其他语言也有类似的东西。
根据您的建议重新修订:
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请求
我建议删除不正确的
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 |
进入之内
1 |
最大的问题是,如果它做了它需要的,为什么要修复它?
更新以演示如何使用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(); } } } } |