最近开始学习Javascript语言,看到splice方法,以下引用其说明:该方法是一个通用删除和插入元素的方法,它可以在数组指定的位置开始删除或插入元素。其包括3个参数:第一个参数指定插入的起始位置,第二个参数指定要删除元素的个数,第三个参数开始表示插入的具体元素,方法返回被删除的元素数组。觉得此方法很不错,所以也想尝试用C#实现一个。
刚开始马上想到用扩展方法实现,如此才会跟Javascript里的用法一样。很快发现C#里的数组大小是固定的,不能修改,而Javascript里的splice方法需要动态的改变数组的大小。C#的System.Array类没有提供修改数组的方法,包括Clear()方法也仅仅是将数组中的每个元素都设置为其默认值(false,0或null);Resize()方法则是创建新数组,并将所有元素复制到新数组。最终只能以比较丑陋的Helper类的方式实现,如有高人知道更好的方式,请不吝赐教。
由于需求比较明确,所以直接写了第一个测试,即删除数组第3、第4个元素并返回被删除的元素。TDD是很好的开发方法,让你目标明确,明白自己下一步要做什么,以及知道自己是否已经达成了目标,也让你放心的去重构代码。这里不再一步步说明怎么按照TDD的要求,用最少的代码达成测试的要求,直接贴出两段测试,以说明Splice方法是怎么工作的。这也是TDD的好处之一,测试就是示例代码。
[Fact] public void TrancateTwoElements() { int[] a = { 1, 2, 3, 4, 5, 6 }; int[] b = ArrayHelper.Splice(ref a, 2, 2); Assert.Equal(new int[] { 1, 2, 5, 6 }, a); Assert.Equal(new int[] { 3, 4 }, b); }
[Fact] public void TrancateArrayAndInsertNewElements() { int[] a = { 1, 2, 3, 4, 5, 6 }; int[] b = ArrayHelper.Splice(ref a, 2, 2, 7, 8, 9); Assert.Equal(new int[] { 1, 2, 7, 8, 9, 5, 6 }, a); Assert.Equal(new int[] { 3, 4 }, b); }
代码的整体思路是,先新建两个数组,分别用于存放被删除元素和剩余的元素加上插入的新元素(如果有的话),然后把要删除的元素拷贝到用于保存被删除元素的数组,接着拷贝源数组删除位置前的需要保留的元素到新数组,然后把要插入的元素拷贝到新数组,最后把源数组末尾的元素拷贝到新数组。还是用代码说话会计较清楚:
public static T[] Splice(ref T[] sourceArray, int sourceIndex, int length, params T[] insertedElements) {
// 此处省略防卫语句 // 如果要截取的元素个数大于从sourceIndex开始到数组结束的元素个数 int acturalLength = (sourceIndex + length) > sourceArray.Length ? (sourceArray.Length - sourceIndex) : length;
var deletedItems = new T[acturalLength]; Array.ConstrainedCopy(sourceArray, sourceIndex, deletedItems, 0, acturalLength); int arrayLengthDifference = insertedElements.Length - acturalLength; int newArrayLength = sourceArray.Length + arrayLengthDifference; var newArray = new T[newArrayLength]; int newArrayCopyedElementsIndex = sourceIndex; Array.Copy(sourceArray, newArray, newArrayCopyedElementsIndex); Array.ConstrainedCopy(insertedElements, 0, newArray, newArrayCopyedElementsIndex, insertedElements.Length); newArrayCopyedElementsIndex += insertedElements.Length; int remainedElementsIndex = sourceIndex + acturalLength; // 源数组末尾还有元素才进行拷贝 if (remainedElementsIndex < sourceArray.Length) { Array.ConstrainedCopy(sourceArray, remainedElementsIndex, newArray, newArrayCopyedElementsIndex, sourceArray.Length - remainedElementsIndex); } sourceArray = newArray; return deletedItems; }
完成的代码及测试请查看https://github.com/axbxcxdx/ZxbLib
通过写这段代码体会到了日常用惯了List等集合类,现在用数组去存放操作元素好像又回到C语言写代码的感觉,要注意数组下标、数组边界和数组的大小。