From f2aee10cddadb69e7ad5bdf0889369b832f7b064 Mon Sep 17 00:00:00 2001 From: meatball Date: Wed, 21 Feb 2024 20:49:59 +0100 Subject: [PATCH] Implement ListOps functions and update package configuration --- .../practice/list-ops/.docs/instructions.md | 18 +- .../Sources/ListOps/ListOpsExample.swift | 113 +++++----- .../practice/list-ops/.meta/template.swift | 62 ++++++ exercises/practice/list-ops/.meta/tests.toml | 21 +- exercises/practice/list-ops/Package.swift | 30 +-- .../list-ops/Sources/ListOps/ListOps.swift | 4 +- .../Tests/ListOpsTests/ListOpsTests.swift | 193 ++++++++++-------- .../Sources/Generator/generator-plugins.swift | 20 ++ 8 files changed, 288 insertions(+), 173 deletions(-) create mode 100644 exercises/practice/list-ops/.meta/template.swift diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md index d34533387..ebc5dffed 100644 --- a/exercises/practice/list-ops/.docs/instructions.md +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -7,11 +7,13 @@ 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 using `function(accumulator, item)`*); -- `foldr` (*given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`*); -- `reverse` (*given a list, return a list with all the original items, but in reversed order*); +- `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/Sources/ListOps/ListOpsExample.swift b/exercises/practice/list-ops/.meta/Sources/ListOps/ListOpsExample.swift index 0d4b54e31..c1047a71c 100644 --- a/exercises/practice/list-ops/.meta/Sources/ListOps/ListOpsExample.swift +++ b/exercises/practice/list-ops/.meta/Sources/ListOps/ListOpsExample.swift @@ -1,81 +1,82 @@ struct ListOps { - - static func append(_ array1: [T], _ array2: [T]) -> [T] { - var result = array1 - for value in array2 { - result.append(value) - } - - return result + static func append(_ array1: [T], _ array2: [T]) -> [T] { + var result = array1 + for value in array2 { + result.append(value) } - static func concat(_ arrays: [T]...) -> [T] { - var result = [T]() + return result + } - for array in arrays { - result = append(result, array) - } + static func concat(_ arrays: [T]...) -> [T] { + var result = [T]() - return result + for array in arrays { + result = append(result, array) } - static func filter(_ array: [T], predicate: (T) -> Bool) -> [T] { - var filtered = [T]() - for value in array { - if predicate(value) { - filtered.append(value) - } - } + return result + } - return filtered + static func filter(_ array: [T], predicate: (T) -> Bool) -> [T] { + var filtered = [T]() + for value in array { + if predicate(value) { + filtered.append(value) + } } - static func length(_ array: [T]) -> Int { - var length = 0 + return filtered + } - for _ in array { - length += 1 - } + static func length(_ array: [T]) -> Int { + var length = 0 - return length + for _ in array { + length += 1 } - static func map(_ array: [T], transform: (T) -> T) -> [T] { - var result = [T]() + return length + } - for value in array { - result.append(transform(value)) - } + static func map(_ array: [T], transform: (T) -> T) -> [T] { + var result = [T]() - return result + for value in array { + result.append(transform(value)) } - static func foldLeft(_ array: [T], accumulated: T, combine: (T, T) -> T) -> T { - if length(array) == 0 { - return accumulated - } else { - return foldLeft(Array(array.dropFirst()), accumulated: combine(accumulated, array[0]), combine: combine) - } - } + return result + } - static func foldRight(_ array: [T], accumulated: T, combine: (T, T) -> T) -> T { - if length(array) == 0 { - return accumulated - } else { - return combine(array[0], foldRight(Array(array.dropFirst()), accumulated: accumulated, combine: combine)) - } + static func foldLeft(_ array: [T], accumulated: T, combine: (T, T) -> T) -> T { + if length(array) == 0 { + return accumulated + } else { + return foldLeft( + Array(array.dropFirst()), accumulated: combine(accumulated, array[0]), combine: combine) } + } + + static func foldRight(_ array: [T], accumulated: T, combine: (T, T) -> T) -> T { + if length(array) == 0 { + return accumulated + } else { + return combine( + array[0], foldRight(Array(array.dropFirst()), accumulated: accumulated, combine: combine)) + } + } - static func reverse(_ array: [T]) -> [T] { - var result = [T]() - var index = length(array) - 1 - - while index >= 0 { - result.append(array[index]) - index -= 1 - } + static func reverse(_ array: [T]) -> [T] { + var result = [T]() + var index = length(array) - 1 - return result + while index >= 0 { + result.append(array[index]) + index -= 1 } + return result + } + } diff --git a/exercises/practice/list-ops/.meta/template.swift b/exercises/practice/list-ops/.meta/template.swift new file mode 100644 index 000000000..6333ecf6a --- /dev/null +++ b/exercises/practice/list-ops/.meta/template.swift @@ -0,0 +1,62 @@ +import XCTest +@testable import {{exercise|camelCase}} +class {{exercise|camelCase}}Tests: XCTestCase { + let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false + + {% outer: for case in cases %} + {%- for subCases in case.cases %} + {%- if forloop.outer.first and forloop.first %} + func test{{subCases.description |camelCase }}{{ forloop.outer.counter }}{{ forloop.counter }}() { + {%- else %} + func test{{subCases.description |camelCase }}{{ forloop.outer.counter }}{{ forloop.counter }}() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + {%- endif %} + {%- if subCases.property == "append" %} + let input1 : [Int] = {{ subCases.input.list1 }} + let input2 : [Int]= {{ subCases.input.list2 }} + let expected : [Int] = {{ subCases.expected }} + XCTAssertEqual(ListOps.append(input1, input2), expected) + {%- elif subCases.property == "concat" %} + {%- ifnot subCases.expected %} + let expected = [Int]() + {%- else %} + let expected = {{ subCases.expected }} + {%- endif %} + XCTAssertEqual(ListOps.concat({% ifnot subCases.input.lists %}[]{% endif %} {% for list in subCases.input.lists %}{% ifnot forloop.first %},{% endif %}{{list}} {% endfor %}), expected) + {%- elif subCases.property == "filter" %} + let input : [Int] = {{ subCases.input.list }} + let expected : [Int] = {{ subCases.expected }} + XCTAssertEqual(ListOps.filter(input) {{subCases.input.function | listOps}}, expected) + {%- elif subCases.property == "length" %} + let input : [Int] = {{ subCases.input.list }} + let expected : Int = {{ subCases.expected }} + XCTAssertEqual(ListOps.length(input), expected) + {%- elif subCases.property == "map" %} + let input : [Int] = {{ subCases.input.list }} + let expected : [Int] = {{ subCases.expected }} + XCTAssertEqual(ListOps.map(input) {{subCases.input.function | listOps}} , expected) + {%- elif subCases.property == "foldl" %} + let input : [Int] = {{ subCases.input.list }} + let expected : Int = {{ subCases.expected }} + XCTAssertEqual(ListOps.foldLeft(input, accumulated: {{ subCases.input.initial }}, combine: {{ subCases.input.function | listOps }}), expected) + {%- elif subCases.property == "foldr" %} + let input : [Int] = {{ subCases.input.list }} + let expected : Int = {{ subCases.expected }} + XCTAssertEqual(ListOps.foldRight(input, accumulated: {{ subCases.input.initial }}, combine: {{ subCases.input.function | listOps }}), expected) + {%- elif subCases.property == "reverse" %} + {%- ifnot subCases.input.list %} + let input = [Int]() + {%- else %} + let input = {{ subCases.input.list }} + {%- endif %} + {%- ifnot subCases.expected %} + let expected = [Int]() + {%- else %} + let expected = {{ subCases.expected }} + {%- endif %} + XCTAssertEqual(ListOps.reverse(input), expected) + {%- endif %} + } + {% endfor -%} + {% endfor -%} +} diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml index d195e4111..7f16d8df0 100644 --- a/exercises/practice/list-ops/.meta/tests.toml +++ b/exercises/practice/list-ops/.meta/tests.toml @@ -9,9 +9,6 @@ # 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" @@ -36,9 +33,6 @@ description = "filter list returning only values that satisfy the filter functio [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" @@ -50,16 +44,15 @@ description = "return a list of elements whose values equal the list value trans [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" - -[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" +include = false [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" @@ -68,15 +61,19 @@ 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" +include = false # Decided to remove this one to keep compatabillity [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" @@ -89,12 +86,10 @@ 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" +include = false # Decided to remove this one to keep compatabillity [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/Package.swift b/exercises/practice/list-ops/Package.swift index 8007eea2e..a354cb05a 100644 --- a/exercises/practice/list-ops/Package.swift +++ b/exercises/practice/list-ops/Package.swift @@ -3,19 +3,19 @@ import PackageDescription let package = Package( - name: "ListOps", - products: [ - .library( - name: "ListOps", - targets: ["ListOps"]), - ], - dependencies: [], - targets: [ - .target( - name: "ListOps", - dependencies: []), - .testTarget( - name: "ListOpsTests", - dependencies: ["ListOps"]), - ] + name: "ListOps", + products: [ + .library( + name: "ListOps", + targets: ["ListOps"]) + ], + dependencies: [], + targets: [ + .target( + name: "ListOps", + dependencies: []), + .testTarget( + name: "ListOpsTests", + dependencies: ["ListOps"]), + ] ) diff --git a/exercises/practice/list-ops/Sources/ListOps/ListOps.swift b/exercises/practice/list-ops/Sources/ListOps/ListOps.swift index cbe2927e1..ad76c40f6 100644 --- a/exercises/practice/list-ops/Sources/ListOps/ListOps.swift +++ b/exercises/practice/list-ops/Sources/ListOps/ListOps.swift @@ -1 +1,3 @@ -//Solution goes in Sources +struct ListOps { + // Write your code for the 'ListOps' exercise in this file. +} diff --git a/exercises/practice/list-ops/Tests/ListOpsTests/ListOpsTests.swift b/exercises/practice/list-ops/Tests/ListOpsTests/ListOpsTests.swift index 1742e5ebf..4f9c1a149 100644 --- a/exercises/practice/list-ops/Tests/ListOpsTests/ListOpsTests.swift +++ b/exercises/practice/list-ops/Tests/ListOpsTests/ListOpsTests.swift @@ -1,85 +1,118 @@ import XCTest + @testable import ListOps class ListOpsTests: XCTestCase { - - func testAppendEmptyLists() { - XCTAssertEqual(ListOps.append([Int](), []), []) - } - - func testAppendEmptyListToList() { - XCTAssertEqual(ListOps.append([], [1, 2, 3, 4]), [1, 2, 3, 4]) - } - - func testAppendNonemptyLists() { - XCTAssertEqual(ListOps.append([1, 2], [2, 3, 4, 5]), [1, 2, 2, 3, 4, 5]) - } - - func testConcatEmptyList() { - XCTAssertEqual(ListOps.concat([Int]()), []) - } - - func testConcatListOfLists() { - XCTAssertEqual(ListOps.concat([1, 2], [3], [], [4, 5, 6]), [1, 2, 3, 4, 5, 6]) - } - - func testFilterEmptyList() { - XCTAssertEqual(ListOps.filter([]) { $0 % 2 == 1 }, []) - } - - func testFilterNonemptyList() { - XCTAssertEqual(ListOps.filter([1, 2, 3, 4, 5]) { $0 % 2 == 1 }, [1, 3, 5]) - } - - func testLengthEmptyList() { - XCTAssertEqual(ListOps.length([]), 0) - } - - func testLengthNonemptyList() { - XCTAssertEqual(ListOps.length([1, 2, 3, 4]), 4) - } - - func testMapEmptyList() { - XCTAssertEqual(ListOps.map([]) { $0 + 1 }, []) - } - - func testMapNonemptyList() { - XCTAssertEqual(ListOps.map([1, 3, 5, 7]) { $0 + 1 }, [2, 4, 6, 8]) - } - - func testFoldLeftEmptyList() { - XCTAssertEqual(ListOps.foldLeft([], accumulated: 2, combine: +), 2) - } - - func testFoldLeftNonemptyListAddition() { - XCTAssertEqual(ListOps.foldLeft([1, 2, 3, 4], accumulated: 5, combine: +), 15) - } - - func testFoldLeftNonemptyListDivision() { - XCTAssertEqual(ListOps.foldLeft([2, 5], accumulated: 5, combine: /), 0) - } - - func testFoldRightEmptyList() { - XCTAssertEqual(ListOps.foldRight([], accumulated: 2, combine: *), 2) - } - - func testFoldRightNonemptyListAddition() { - XCTAssertEqual(ListOps.foldRight([1, 2, 3, 4], accumulated: 5, combine: +), 15) - } - - func testFoldRightNonemptyListDivision() { - XCTAssertEqual(ListOps.foldRight([2, 5], accumulated: 5, combine: /), 2) - } - - func testFoldRightAddString() { - XCTAssertEqual(ListOps.foldRight(["e", "x", "e", "r", "c", "i", "s", "m"], accumulated: "!", combine: +), "exercism!") - } - - func testReverseEmptyList() { - XCTAssertEqual(ListOps.reverse([Int]()), []) - } - - func testReverseNonemptyList() { - XCTAssertEqual(ListOps.reverse([1, 3, 5, 7]), [7, 5, 3, 1]) - } + let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false + + func testListToEmptyList11() { + let input1: [Int] = [] + let input2: [Int] = [1, 2, 3, 4] + let expected: [Int] = [1, 2, 3, 4] + XCTAssertEqual(ListOps.append(input1, input2), expected) + } + + func testEmptyListToList12() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input1: [Int] = [1, 2, 3, 4] + let input2: [Int] = [] + let expected: [Int] = [1, 2, 3, 4] + XCTAssertEqual(ListOps.append(input1, input2), expected) + } + + func testNonEmptyLists13() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input1: [Int] = [1, 2] + let input2: [Int] = [2, 3, 4, 5] + let expected: [Int] = [1, 2, 2, 3, 4, 5] + XCTAssertEqual(ListOps.append(input1, input2), expected) + } + + func testEmptyList21() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let expected = [Int]() + XCTAssertEqual(ListOps.concat([]), expected) + } + + func testListOfLists22() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let expected = [1, 2, 3, 4, 5, 6] + XCTAssertEqual(ListOps.concat([1, 2], [3], [], [4, 5, 6]), expected) + } + + func testListOfNestedLists23() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let expected = [[1], [2], [3], [], [4, 5, 6]] + XCTAssertEqual(ListOps.concat([[1], [2]], [[3]], [[]], [[4, 5, 6]]), expected) + } + + func testEmptyList31() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input: [Int] = [] + let expected: [Int] = [] + XCTAssertEqual(ListOps.filter(input) { $0 % 2 == 1 }, expected) + } + + func testNonEmptyList32() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input: [Int] = [1, 2, 3, 5] + let expected: [Int] = [1, 3, 5] + XCTAssertEqual(ListOps.filter(input) { $0 % 2 == 1 }, expected) + } + + func testNonEmptyList41() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input: [Int] = [1, 2, 3, 4] + let expected: Int = 4 + XCTAssertEqual(ListOps.length(input), expected) + } + + func testEmptyList51() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input: [Int] = [] + let expected: [Int] = [] + XCTAssertEqual(ListOps.map(input) { $0 + 1 }, expected) + } + + func testNonEmptyList52() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input: [Int] = [1, 3, 5, 7] + let expected: [Int] = [2, 4, 6, 8] + XCTAssertEqual(ListOps.map(input) { $0 + 1 }, expected) + } + + func testDirectionIndependentFunctionAppliedToNonEmptyList61() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input: [Int] = [1, 2, 3, 4] + let expected: Int = 15 + XCTAssertEqual(ListOps.foldLeft(input, accumulated: 5, combine: +), expected) + } + + func testEmptyList71() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input: [Int] = [] + let expected: Int = 2 + XCTAssertEqual(ListOps.foldRight(input, accumulated: 2, combine: *), expected) + } + + func testDirectionIndependentFunctionAppliedToNonEmptyList72() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input: [Int] = [1, 2, 3, 4] + let expected: Int = 15 + XCTAssertEqual(ListOps.foldRight(input, accumulated: 5, combine: +), expected) + } + + func testEmptyList81() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input = [Int]() + let expected = [Int]() + XCTAssertEqual(ListOps.reverse(input), expected) + } + + func testListOfListsIsNotFlattened82() throws { + try XCTSkipIf(true && !runAll) // change true to false to run this test + let input = [[1, 2], [3], [], [4, 5, 6]] + let expected = [[4, 5, 6], [], [3], [1, 2]] + XCTAssertEqual(ListOps.reverse(input), expected) + } } diff --git a/generator/Sources/Generator/generator-plugins.swift b/generator/Sources/Generator/generator-plugins.swift index fe98d5bb2..0da5ad0e8 100644 --- a/generator/Sources/Generator/generator-plugins.swift +++ b/generator/Sources/Generator/generator-plugins.swift @@ -222,6 +222,26 @@ class GeneratorPlugins { return [] } + ext.registerFilter("listOps") { (value: Any?) in + if let input = value as? String { + switch input { + case "(x) -> x modulo 2 == 1": + return "{ $0 % 2 == 1 }" + case "(x) -> x + 1": + return "{ $0 + 1 }" + case "(acc, el) -> el * acc": + return "*" + case "(acc, el) -> el + acc": + return "+" + case "(acc, el) -> el / acc": + return "/" + default: + return "" + } + } + return [] + } + ext.registerFilter("round") { (value: Any?, args: [Any?]) in if let inputNumber = value as? Double { if let precision = args.first as? Int {