diff --git a/config.json b/config.json index b4a0d642..f2a16a6e 100644 --- a/config.json +++ b/config.json @@ -642,6 +642,14 @@ "practices": [], "prerequisites": [], "difficulty": 4 + }, + { + "slug": "list-ops", + "name": "List Ops", + "uuid": "b1418b5d-9c69-438a-8835-cf12d630c547", + "practices": [], + "prerequisites": [], + "difficulty": 5 } ] }, diff --git a/exercises/practice/list-ops/.docs/instructions.append.md b/exercises/practice/list-ops/.docs/instructions.append.md new file mode 100644 index 00000000..871fe216 --- /dev/null +++ b/exercises/practice/list-ops/.docs/instructions.append.md @@ -0,0 +1,19 @@ +# ignore + +## Dart-specific instructions + +The `append` method mutates the receiver. +All other methods return a new List. + +We can't override builtin methods and properties: +instead of `length`, implement `count`; +instead of `map`, implement `myMap`. + +You will be adding [Extension methods][extension] to the List class. +Try not to rely too much on the builtin methods and properties of List and Iterable, +implement the functionality yourself as much as you can. + +You'll notice that the provided stub file uses [Generics][generics]. + +[extension]: https://dart.dev/language/extension-methods +[generics]: https://dart.dev/language/generics diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md new file mode 100644 index 00000000..ebc5dffe --- /dev/null +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -0,0 +1,19 @@ +# Instructions + +Implement basic list operations. + +In functional languages list operations like `length`, `map`, and `reduce` are very common. +Implement a series of basic list operations, without using existing functions. + +The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include: + +- `append` (_given two lists, add all items in the second list to the end of the first list_); +- `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_); +- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_); +- `length` (_given a list, return the total number of items within it_); +- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_); +- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_); +- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_); +- `reverse` (_given a list, return a list with all the original items, but in reversed order_). + +Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant. diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json new file mode 100644 index 00000000..ea992161 --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "lib/list_ops.dart" + ], + "test": [ + "test/list_ops_test.dart" + ], + "example": [ + ".meta/lib/example.dart" + ] + }, + "blurb": "Implement basic list operations." +} diff --git a/exercises/practice/list-ops/.meta/lib/example.dart b/exercises/practice/list-ops/.meta/lib/example.dart new file mode 100644 index 00000000..235a288c --- /dev/null +++ b/exercises/practice/list-ops/.meta/lib/example.dart @@ -0,0 +1,61 @@ +extension ListOps on List { + void append(List other) { + for (var elem in other) { + this.add(elem); + } + } + + List concat() { + var result = []; + for (var sublist in this) { + result.addAll(sublist); + } + return result; + } + + List filter(bool Function(T elem) predicate) { + var result = []; + for (var elem in this) { + if (predicate(elem)) { + result.add(elem); + } + } + return result; + } + + int count() { + return this.foldl(0, (len, _) => len + 1); + } + + List myMap(T Function(T elem) fn) { + var result = []; + for (var elem in this) { + result.add(fn(elem)); + } + return result; + } + + U foldl(U initial, U Function(U acc, T elem) fn) { + var result = initial; + for (var elem in this) { + result = fn(result, elem); + } + return result; + } + + U foldr(U initial, U Function(T elem, U acc) fn) { + var result = initial; + for (var elem in this.reverse()) { + result = fn(elem, result); + } + return result; + } + + List reverse() { + var result = []; + for (var i = this.length - 1; i >= 0; i--) { + result.add(this[i]); + } + return result; + } +} diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml new file mode 100644 index 00000000..08b1edc0 --- /dev/null +++ b/exercises/practice/list-ops/.meta/tests.toml @@ -0,0 +1,106 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[485b9452-bf94-40f7-a3db-c3cf4850066a] +description = "append entries to a list and return the new list -> empty lists" + +[2c894696-b609-4569-b149-8672134d340a] +description = "append entries to a list and return the new list -> list to empty list" + +[e842efed-3bf6-4295-b371-4d67a4fdf19c] +description = "append entries to a list and return the new list -> empty list to list" + +[71dcf5eb-73ae-4a0e-b744-a52ee387922f] +description = "append entries to a list and return the new list -> non-empty lists" + +[28444355-201b-4af2-a2f6-5550227bde21] +description = "concatenate a list of lists -> empty list" + +[331451c1-9573-42a1-9869-2d06e3b389a9] +description = "concatenate a list of lists -> list of lists" + +[d6ecd72c-197f-40c3-89a4-aa1f45827e09] +description = "concatenate a list of lists -> list of nested lists" + +[0524fba8-3e0f-4531-ad2b-f7a43da86a16] +description = "filter list returning only values that satisfy the filter function -> empty list" + +[88494bd5-f520-4edb-8631-88e415b62d24] +description = "filter list returning only values that satisfy the filter function -> non-empty list" + +[1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad] +description = "returns the length of a list -> empty list" + +[d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e] +description = "returns the length of a list -> non-empty list" + +[c0bc8962-30e2-4bec-9ae4-668b8ecd75aa] +description = "return a list of elements whose values equal the list value transformed by the mapping function -> empty list" + +[11e71a95-e78b-4909-b8e4-60cdcaec0e91] +description = "return a list of elements whose values equal the list value transformed by the mapping function -> non-empty list" + +[613b20b7-1873-4070-a3a6-70ae5f50d7cc] +description = "folds (reduces) the given list from the left with a function -> empty list" +include = false + +[e56df3eb-9405-416a-b13a-aabb4c3b5194] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +include = false + +[d2cf5644-aee1-4dfc-9b88-06896676fe27] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +include = false + +[36549237-f765-4a4c-bfd9-5d3a8f7b07d2] +description = "folds (reduces) the given list from the left with a function -> empty list" +reimplements = "613b20b7-1873-4070-a3a6-70ae5f50d7cc" + +[7a626a3c-03ec-42bc-9840-53f280e13067] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +reimplements = "e56df3eb-9405-416a-b13a-aabb4c3b5194" + +[d7fcad99-e88e-40e1-a539-4c519681f390] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +reimplements = "d2cf5644-aee1-4dfc-9b88-06896676fe27" + +[aeb576b9-118e-4a57-a451-db49fac20fdc] +description = "folds (reduces) the given list from the right with a function -> empty list" +include = false + +[c4b64e58-313e-4c47-9c68-7764964efb8e] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +include = false + +[be396a53-c074-4db3-8dd6-f7ed003cce7c] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +include = false + +[17214edb-20ba-42fc-bda8-000a5ab525b0] +description = "folds (reduces) the given list from the right with a function -> empty list" +reimplements = "aeb576b9-118e-4a57-a451-db49fac20fdc" + +[e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +reimplements = "c4b64e58-313e-4c47-9c68-7764964efb8e" + +[8066003b-f2ff-437e-9103-66e6df474844] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +reimplements = "be396a53-c074-4db3-8dd6-f7ed003cce7c" + +[94231515-050e-4841-943d-d4488ab4ee30] +description = "reverse the elements of the list -> empty list" + +[fcc03d1e-42e0-4712-b689-d54ad761f360] +description = "reverse the elements of the list -> non-empty list" + +[40872990-b5b8-4cb8-9085-d91fc0d05d26] +description = "reverse the elements of the list -> list of lists is not flattened" diff --git a/exercises/practice/list-ops/analysis_options.yaml b/exercises/practice/list-ops/analysis_options.yaml new file mode 100644 index 00000000..c06363d6 --- /dev/null +++ b/exercises/practice/list-ops/analysis_options.yaml @@ -0,0 +1,18 @@ +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + errors: + unused_element: error + unused_import: error + unused_local_variable: error + dead_code: error + +linter: + rules: + # Error Rules + - avoid_relative_lib_imports + - avoid_types_as_parameter_names + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - valid_regexps diff --git a/exercises/practice/list-ops/lib/list_ops.dart b/exercises/practice/list-ops/lib/list_ops.dart new file mode 100644 index 00000000..5c49f1ed --- /dev/null +++ b/exercises/practice/list-ops/lib/list_ops.dart @@ -0,0 +1,33 @@ +extension ListOps on List { + void append(List other) { + // Add your implemenation here. + } + + List concat() { + // Add your implemenation here. + } + + List filter(bool Function(T elem) predicate) { + // Add your implemenation here. + } + + int count() { + // Add your implemenation here. + } + + List myMap(T Function(T elem) fn) { + // Add your implemenation here. + } + + U foldl(U initial, U Function(U acc, T elem) fn) { + // Add your implemenation here. + } + + U foldr(U initial, U Function(T elem, U acc) fn) { + // Add your implemenation here. + } + + List reverse() { + // Add your implemenation here. + } +} diff --git a/exercises/practice/list-ops/pubspec.yaml b/exercises/practice/list-ops/pubspec.yaml new file mode 100644 index 00000000..baf73673 --- /dev/null +++ b/exercises/practice/list-ops/pubspec.yaml @@ -0,0 +1,5 @@ +name: 'list_ops' +environment: + sdk: '>=2.18.0 <3.0.0' +dev_dependencies: + test: '<2.0.0' diff --git a/exercises/practice/list-ops/test/list_ops_test.dart b/exercises/practice/list-ops/test/list_ops_test.dart new file mode 100644 index 00000000..d40de2bd --- /dev/null +++ b/exercises/practice/list-ops/test/list_ops_test.dart @@ -0,0 +1,205 @@ +import 'package:list_ops/list_ops.dart'; +import 'package:test/test.dart'; + +void main() { + group('append entries to a list and return the new list', () { + test('empty lists', () { + final list1 = []; + final list2 = []; + list1.append(list2); + expect(list1, equals([])); + }, skip: false); + + test('list to empty list', () { + final list1 = []; + final list2 = [1, 2, 3, 4]; + list1.append(list2); + expect(list1, equals([1, 2, 3, 4])); + }, skip: true); + + test('empty list to list', () { + final list1 = [1, 2, 3, 4]; + final list2 = []; + list1.append(list2); + expect(list1, equals([1, 2, 3, 4])); + }, skip: true); + + test('non-empty lists', () { + final list1 = [1, 2]; + final list2 = [2, 3, 4, 5]; + list1.append(list2); + expect(list1, equals([1, 2, 2, 3, 4, 5])); + }, skip: true); + }); + + group('concatenate a list of lists', () { + test('empty list', () { + final lists = []; + final result = lists.concat(); + expect(result, equals([])); + }, skip: true); + + test('list of lists', () { + final lists = [ + [1, 2], + [3], + [], + [4, 5, 6] + ]; + final result = lists.concat(); + expect(result, equals([1, 2, 3, 4, 5, 6])); + // does not mutate the original list + expect( + lists, + equals([ + [1, 2], + [3], + [], + [4, 5, 6] + ])); + }, skip: true); + + test('list of nested lists', () { + final lists = [ + [ + [1], + [2] + ], + [ + [3] + ], + [[]], + [ + [4, 5, 6] + ] + ]; + final result = lists.concat(); + expect( + result, + equals([ + [1], + [2], + [3], + [], + [4, 5, 6] + ])); + }, skip: true); + }); + + group('filter list returning only values that satisfy the filter function', () { + test('empty list', () { + final list = []; + final result = list.filter((int x) => x % 2 == 1); + expect(result, equals([])); + }, skip: true); + + test('non-empty list', () { + final list = [1, 2, 3, 5]; + final result = list.filter((int x) => x % 2 == 1); + expect(result, equals([1, 3, 5])); + expect(list, equals([1, 2, 3, 5])); + }, skip: true); + }); + + group('returns the length of a list', () { + test('empty list', () { + final list = []; + final result = list.count(); + expect(result, equals(0)); + }, skip: true); + + test('non-empty list', () { + final list = [1, 2, 3, 4]; + final result = list.count(); + expect(result, equals(4)); + }, skip: true); + }); + + group('return a list of elements whose values equal the list value transformed by the mapping function', () { + test('empty list', () { + final list = []; + final result = list.myMap((int x) => x + 1); + expect(result, equals([])); + }, skip: true); + + test('non-empty list', () { + final list = [1, 3, 5, 7]; + final result = list.myMap((int x) => x + 1); + expect(result, equals([2, 4, 6, 8])); + expect(list, equals([1, 3, 5, 7])); + }, skip: true); + }); + + group('folds (reduces) the given list from the left with a function', () { + test('empty list', () { + final list = []; + final result = list.foldl(2, (int acc, int el) => el * acc); + expect(result, equals(2)); + }, skip: true); + + test('direction independent function applied to non-empty list', () { + final list = [1, 2, 3, 4]; + final result = list.foldl(5, (int acc, int el) => el + acc); + expect(result, equals(15)); + }, skip: true); + + test('direction dependent function applied to non-empty list', () { + final list = [1, 2, 3, 4]; + final result = list.foldl(24, (num acc, int el) => el / acc); + expect(result, equals(64)); + }, skip: true); + }); + + group('folds (reduces) the given list from the right with a function', () { + test('empty list', () { + final list = []; + final result = list.foldr(2, (int el, int acc) => el * acc); + expect(result, equals(2)); + }, skip: true); + + test('direction independent function applied to non-empty list', () { + final list = [1, 2, 3, 4]; + final result = list.foldr(5, (int el, int acc) => el + acc); + expect(result, equals(15)); + }, skip: true); + + test('direction dependent function applied to non-empty list', () { + final list = [1, 2, 3, 4]; + final result = list.foldr(24, (int el, num acc) => el / acc); + expect(result, equals(9)); + }, skip: true); + }); + + group('reverse the elements of the list', () { + test('empty list', () { + final list = []; + final result = list.reverse(); + expect(result, equals([])); + }, skip: true); + + test('non-empty list', () { + final list = [1, 3, 5, 7]; + final result = list.reverse(); + expect(result, equals([7, 5, 3, 1])); + expect(list, equals([1, 3, 5, 7])); + }, skip: true); + + test('list of lists is not flattened', () { + final list = [ + [1, 2], + [3], + [], + [4, 5, 6] + ]; + final result = list.reverse(); + expect( + result, + equals([ + [4, 5, 6], + [], + [3], + [1, 2] + ])); + }, skip: true); + }); +}