diff --git a/CHANGELOG.md b/CHANGELOG.md index d07d323048d..ba4829f04e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,7 +90,8 @@ the assignment to `y`. `whereType`. * `Iterable.singleWhere` added `orElse` parameter. * `List` added `+` operator, `first` and `last` setters, and `indexWhere` - and `lastIndexWhere` methods. + and `lastIndexWhere` methods, and static `copyRange` and `writeIterable` + methods. * `Map` added `fromEntries` constructor. * `Map` added `addEntries`, `cast`, `entries`, `map`, `removeWhere`, `retype`, `update` and `updateAll` members. diff --git a/sdk/lib/core/list.dart b/sdk/lib/core/list.dart index fe9ee969e3b..77e40e09a30 100644 --- a/sdk/lib/core/list.dart +++ b/sdk/lib/core/list.dart @@ -171,6 +171,68 @@ abstract class List implements EfficientLengthIterable { */ static List castFrom(List source) => new CastList(source); + /** + * Copy a range of one list into another list. + * + * This is a utility function that can be used to implement methods like + * [setRange]. + * + * The range from [start] to [end] must be a valid range of [source], + * and there must be room for `end - start` elements from position [at]. + * If [start] is omitted, it defaults to zero. + * If [end] is omitted, it defaults to [source.length]. + * + * If [source] and [target] is the same list, overlapping source and target + * ranges are respected so that the target range ends up containing the + * initial content of the source range. + * Otherwise the order of element copying is not guaranteed. + */ + static void copyRange(List target, int at, List source, + [int start, int end]) { + start ??= 0; + end = RangeError.checkValidRange(start, end, source.length); + int length = end - start; + if (target.length < at + length) { + throw new ArgumentError.value(target, "target", + "Not big enough to hold $length elements at position $at"); + } + if (!identical(source, target) || start >= at) { + for (int i = 0; i < length; i++) { + target[at + i] = source[start + i]; + } + } else { + for (int i = length; --i >= 0;) { + target[at + i] = source[start + i]; + } + } + } + + /** + * Write the elements of an iterable into a list. + * + * This is a utility function that can be used to implement methods like + * [setAll]. + * + * The elements of [source] are written into [target] from position [at]. + * The [source] must not contain more elements after writing the last + * position of [target]. + * + * If the source is a list, the [copyRange] function is likely to be more + * efficient. + */ + static void writeIterable(List target, int at, Iterable source) { + RangeError.checkValueInInterval(at, 0, target.length, "at"); + int index = at; + int targetLength = target.length; + for (var element in source) { + if (index == targetLength) { + throw new IndexError(targetLength, target); + } + target[index] = element; + index++; + } + } + /** * Returns a view of this list as a list of [R] instances, if necessary. * diff --git a/tests/corelib_2/list_copy_range_test.dart b/tests/corelib_2/list_copy_range_test.dart new file mode 100644 index 00000000000..009f445bfe4 --- /dev/null +++ b/tests/corelib_2/list_copy_range_test.dart @@ -0,0 +1,115 @@ +// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "dart:typed_data"; +import "package:expect/expect.dart"; + +// Tests of List.copyRange. + +void main() { + var list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + List.copyRange(list, 3, [10, 11, 12, 13], 0, 4); + + Expect.listEquals([0, 1, 2, 10, 11, 12, 13, 7, 8, 9], list); + + List.copyRange(list, 6, [20, 21, 22, 23], 1, 3); + + Expect.listEquals([0, 1, 2, 10, 11, 12, 21, 22, 8, 9], list); + + // Empty ranges won't change anything. + List.copyRange(list, 7, [30, 31, 32, 33], 3, 3); + List.copyRange(list, list.length, [30, 31, 32, 33], 3, 3); + + Expect.listEquals([0, 1, 2, 10, 11, 12, 21, 22, 8, 9], list); + + List.copyRange(list, 0, [40, 41, 42, 43], 0, 2); + + Expect.listEquals([40, 41, 2, 10, 11, 12, 21, 22, 8, 9], list); + + // Overlapping self-ranges + + List.copyRange(list, 2, list, 0, 4); + + Expect.listEquals([40, 41, 40, 41, 2, 10, 21, 22, 8, 9], list); + + List.copyRange(list, 4, list, 6, 10); + + Expect.listEquals([40, 41, 40, 41, 21, 22, 8, 9, 8, 9], list); + + // Invalid source ranges. + + Expect.throwsArgumentError(() { + List.copyRange(list, 0, [0, 0, 0], -1, 1); + }); + + Expect.throwsArgumentError(() { + List.copyRange(list, 0, [0, 0, 0], 0, 4); + }); + + Expect.throwsArgumentError(() { + List.copyRange(list, 0, [0, 0, 0], 2, 1); + }); + + Expect.throwsArgumentError(() { + List.copyRange(list, 0, [], 1, 1); + }); + + // Invalid target range. + Expect.throwsArgumentError(() { + List.copyRange(list, list.length - 3, [0, 0, 0, 0], 0, 4); + }); + + // Invalid target range. + Expect.throwsArgumentError(() { + List.copyRange(list, list.length + 1, [0, 0, 0, 0], 0, 0); + }); + + // Argument errors throw before changing anything, so list is unchanged. + Expect.listEquals([40, 41, 40, 41, 21, 22, 8, 9, 8, 9], list); + + // Omitting start/end (or passing null). + List.copyRange(list, 2, [1, 2, 3]); + + Expect.listEquals([40, 41, 1, 2, 3, 22, 8, 9, 8, 9], list); + + List.copyRange(list, 5, [1, 2, 3], 1); + + Expect.listEquals([40, 41, 1, 2, 3, 2, 3, 9, 8, 9], list); + + // Other kinds of lists. + var listu8 = new Uint8List.fromList([1, 2, 3, 4]); + var list16 = new Int16List.fromList([11, 12, -13, -14]); + List.copyRange(listu8, 2, list16, 1, 3); + Expect.listEquals([1, 2, 12, 256 - 13], listu8); + + var clist = const [1, 2, 3, 4]; + var flist = new List(4)..setAll(0, [10, 11, 12, 13]); + List.copyRange(flist, 1, clist, 1, 3); + Expect.listEquals([10, 2, 3, 13], flist); + + // Invoking with a type parameter that is a supertype of the list types + // is valid and useful. + List ilist = [1, 2, 3, 4]; + List nlist = [11, 12, 13, 14]; + + List.copyRange(ilist, 1, nlist, 1, 3); + Expect.listEquals([1, 12, 13, 4], ilist); + List.copyRange(ilist, 1, nlist, 0, 2); + Expect.listEquals([1, 11, 12, 4], ilist); + List.copyRange(ilist, 1, nlist, 2, 4); + Expect.listEquals([1, 13, 14, 4], ilist); + + var d = new D(); + List bdlist = [d]; + List cdlist = [null]; + List.copyRange(cdlist, 0, bdlist, 0, 1); + Expect.identical(d, cdlist[0]); +} + +class B {} + +class C {} + +class D implements B, C {} diff --git a/tests/corelib_2/list_write_elements_test.dart b/tests/corelib_2/list_write_elements_test.dart new file mode 100644 index 00000000000..3f382cec4eb --- /dev/null +++ b/tests/corelib_2/list_write_elements_test.dart @@ -0,0 +1,75 @@ +// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "dart:typed_data"; +import "package:expect/expect.dart"; + +void main() { + test([1, 2, 3, 4], [5, 6], 2, [1, 2, 5, 6]); + test([1, 2, 3, 4], [5, 6], 0, [5, 6, 3, 4]); + test([1, 2, 3, 4], [5, 6], 1, [1, 5, 6, 4]); + test([1, 2, 3, 4], [], 2, [1, 2, 3, 4]); + test([1, 2, 3, 4], [5, 6], 2, [1, 2, 5, 6]); + test([1, 2, 3, 4], [5, 6], 2, [1, 2, 5, 6]); + test([1, 2, 3, 4], [5, 6], 2, [1, 2, 5, 6]); + test([], [], 0, []); + test([1], [2], 0, [2]); + + // Other (non-list) iterables. + test([1, 2, 3, 4], new Iterable.generate(2, (x) => x + 7), 1, [1, 7, 8, 4]); + test([1, 2, 3, 4], [9, 9, 5, 6].skip(2), 1, [1, 5, 6, 4]); + test([1, 2, 3, 4], [5, 6, 9, 9].take(2), 1, [1, 5, 6, 4]); + test([1, 2, 3, 4], [9, 5, 9, 6, 9, 9].where((x) => x != 9), 1, [1, 5, 6, 4]); + test([1, 2, 3, 4], new Set.from([5, 6]), 1, [1, 5, 6, 4]); + + // Other types of lists. + test(new Uint8List(4), [5, 6], 1, [0, 5, 6, 0]); + test(new Uint8List(4), [-5, 6], 1, [0, 256 - 5, 6, 0]); + + // Over-long iterables. Updates until end, then throws. + testThrows([], [1], 0); + testThrows([2], [1], 1); + testThrows([2], [1], 1); + testThrows([1, 2, 3, 4], [5, 6, 7, 8], 2, [1, 2, 5, 6]); + + // Throwing iterable. + Expect.throws(() { + Iterable throwingIterable() sync* { + yield 1; + throw "2"; + } + + List.writeIterable([1, 2, 3], 1, throwingIterable()); + }, (e) => e == "2"); + + // Bad start index. + testThrows([1, 2], [1], -1); + testThrows([1, 2], [1], 2); + + // Working at a supertype is practical and useful. + test([1, 2, 3, 4], [5, 6], 1, [1, 5, 6, 4]); + + var d = new D(); + test([null], [d], 0, [d]); +} + +testThrows(list, iterable, start, [expect]) { + Expect.throws(() { + List.writeIterable(list, start, iterable); + }); + if (expect != null) { + Expect.listEquals(expect, list); + } +} + +test(List list, Iterable iterable, int start, List expect) { + List.writeIterable(list, start, iterable); + Expect.listEquals(expect, list); +} + +class B {} + +class C {} + +class D implements B, C {}