From f72e3f0616ae0dba618d736ac442e275283a7052 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 21 Dec 2024 22:00:59 +0100 Subject: [PATCH 1/7] Update Swift tools version to 6.0 and add swift-numerics dependency --- Package.swift | 4 +- .../practice/space-age/.meta/template.swift | 17 +-- exercises/practice/space-age/Package.swift | 10 +- .../space-age/Sources/SpaceAge/SpaceAge.swift | 15 ++- .../Tests/SpaceAgeTests/SpaceAgeTests.swift | 54 +++++---- .../practice/strain/.meta/template.swift | 16 +-- exercises/practice/strain/Package.swift | 2 +- .../Tests/StrainTests/StrainTests.swift | 89 +++++++------- .../practice/sublist/.docs/instructions.md | 4 +- .../practice/sublist/.meta/template.swift | 16 +-- exercises/practice/sublist/Package.swift | 2 +- .../Tests/SublistTests/SublistTests.swift | 113 +++++++++--------- .../sum-of-multiples/.meta/template.swift | 16 +-- .../practice/sum-of-multiples/Package.swift | 2 +- .../SumOfMultiplesTests.swift | 103 ++++++++-------- 15 files changed, 252 insertions(+), 211 deletions(-) diff --git a/Package.swift b/Package.swift index 35292fd26..32c684de3 100644 --- a/Package.swift +++ b/Package.swift @@ -53,7 +53,8 @@ let practiceExerciseTargets: [Target] = practiceExercises.flatMap { .testTarget( name:"\($0.pascalCased)Tests", dependencies: [ - .target(name:"\($0.pascalCased)") + .target(name:"\($0.pascalCased)"), + .product(name: "Numerics", package: "swift-numerics") ], path:"./exercises/practice/\($0)/Tests") ] @@ -68,5 +69,6 @@ let package = Package( name: "xswift", targets: allTargets.filter { $0.type == .regular }.map { $0.name }) ], + dependencies: [.package(url: "https://github.com/apple/swift-numerics", from: "1.0.0"),], targets: allTargets ) diff --git a/exercises/practice/space-age/.meta/template.swift b/exercises/practice/space-age/.meta/template.swift index ca6622e6c..76023a291 100644 --- a/exercises/practice/space-age/.meta/template.swift +++ b/exercises/practice/space-age/.meta/template.swift @@ -1,17 +1,20 @@ -import XCTest +import Testing +import Foundation +import Numerics @testable import {{exercise|camelCase}} -class {{exercise|camelCase}}Tests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false + +@Suite struct {{exercise|camelCase}}Tests { {% for case in cases %} {% if forloop.first -%} - func test{{case.description |camelCase }}() { + @Test("{{case.description}}") {% else -%} - func test{{case.description |camelCase }}() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("{{case.description}}", .enabled(if: RUNALL)) {% endif -%} + func test{{case.description |camelCase }}() { let age = SpaceAge({{case.input.seconds}}) - XCTAssertEqual(age.on{{case.input.planet |camelCase}}, {{case.expected | round:2 }}, accuracy: 0.02) + #expect(age.on{{case.input.planet |camelCase}}.isApproximatelyEqual(to: {{case.expected | round:2 }}, relativeTolerance: 0.03)) } {% endfor -%} } diff --git a/exercises/practice/space-age/Package.swift b/exercises/practice/space-age/Package.swift index 1c6b780b8..c1ae8b424 100644 --- a/exercises/practice/space-age/Package.swift +++ b/exercises/practice/space-age/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:6.0 import PackageDescription @@ -9,13 +9,15 @@ let package = Package( name: "SpaceAge", targets: ["SpaceAge"]) ], - dependencies: [], + dependencies: [ + .package(url: "https://github.com/apple/swift-numerics", from: "1.0.2"), + ], targets: [ .target( name: "SpaceAge", - dependencies: []), + dependencies: [.product(name: "Numerics", package: "swift-numerics")]), .testTarget( name: "SpaceAgeTests", - dependencies: ["SpaceAge"]), + dependencies: [.product(name: "Numerics", package: "swift-numerics"), "SpaceAge"]), ] ) diff --git a/exercises/practice/space-age/Sources/SpaceAge/SpaceAge.swift b/exercises/practice/space-age/Sources/SpaceAge/SpaceAge.swift index f8a8f6fc2..475232072 100644 --- a/exercises/practice/space-age/Sources/SpaceAge/SpaceAge.swift +++ b/exercises/practice/space-age/Sources/SpaceAge/SpaceAge.swift @@ -1,3 +1,16 @@ class SpaceAge { - // Write your code for the 'SpaceAge' exercise in this file. + var seconds: Float = 0 + + var onMercury: Float { return ((seconds / 7_600_530.24) * 100).rounded() / 100 } + var onVenus: Float { return ((seconds / 19_413_907.2) * 100).rounded() / 100 } + var onEarth: Float { return ((seconds / 31_558_149.76) * 100).rounded() / 100 } + var onMars: Float { return ((seconds / 59_354_294.4) * 100).rounded() / 100 } + var onJupiter: Float { return ((seconds / 374_335_776.0) * 100).rounded() / 100 } + var onSaturn: Float { return ((seconds / 929_596_608.0) * 100).rounded() / 100 } + var onUranus: Float { return ((seconds / 2_661_041_808.0) * 100).rounded() / 100 } + var onNeptune: Float { return ((seconds / 5_200_418_592.0) * 100).rounded() / 100 } + + init(_ input: Float) { + self.seconds = input + } } diff --git a/exercises/practice/space-age/Tests/SpaceAgeTests/SpaceAgeTests.swift b/exercises/practice/space-age/Tests/SpaceAgeTests/SpaceAgeTests.swift index 1366966d2..580537923 100644 --- a/exercises/practice/space-age/Tests/SpaceAgeTests/SpaceAgeTests.swift +++ b/exercises/practice/space-age/Tests/SpaceAgeTests/SpaceAgeTests.swift @@ -1,54 +1,58 @@ -import XCTest +import Foundation +import Numerics +import Testing @testable import SpaceAge -class SpaceAgeTests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +@Suite struct SpaceAgeTests { + + @Test("age on Earth") func testAgeOnEarth() { let age = SpaceAge(1_000_000_000) - XCTAssertEqual(age.onEarth, 31.69, accuracy: 0.02) + #expect(age.onEarth.isApproximatelyEqual(to: 31.69, relativeTolerance: 0.03)) } - func testAgeOnMercury() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("age on Mercury", .enabled(if: RUNALL)) + func testAgeOnMercury() { let age = SpaceAge(2_134_835_688) - XCTAssertEqual(age.onMercury, 280.88, accuracy: 0.02) + #expect(age.onMercury.isApproximatelyEqual(to: 280.88, relativeTolerance: 0.03)) } - func testAgeOnVenus() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("age on Venus", .enabled(if: RUNALL)) + func testAgeOnVenus() { let age = SpaceAge(189_839_836) - XCTAssertEqual(age.onVenus, 9.78, accuracy: 0.02) + #expect(age.onVenus.isApproximatelyEqual(to: 9.78, relativeTolerance: 0.03)) } - func testAgeOnMars() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("age on Mars", .enabled(if: RUNALL)) + func testAgeOnMars() { let age = SpaceAge(2_129_871_239) - XCTAssertEqual(age.onMars, 35.88, accuracy: 0.02) + #expect(age.onMars.isApproximatelyEqual(to: 35.88, relativeTolerance: 0.03)) } - func testAgeOnJupiter() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("age on Jupiter", .enabled(if: RUNALL)) + func testAgeOnJupiter() { let age = SpaceAge(901_876_382) - XCTAssertEqual(age.onJupiter, 2.41, accuracy: 0.02) + #expect(age.onJupiter.isApproximatelyEqual(to: 2.41, relativeTolerance: 0.03)) } - func testAgeOnSaturn() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("age on Saturn", .enabled(if: RUNALL)) + func testAgeOnSaturn() { let age = SpaceAge(2_000_000_000) - XCTAssertEqual(age.onSaturn, 2.15, accuracy: 0.02) + #expect(age.onSaturn.isApproximatelyEqual(to: 2.15, relativeTolerance: 0.03)) } - func testAgeOnUranus() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("age on Uranus", .enabled(if: RUNALL)) + func testAgeOnUranus() { let age = SpaceAge(1_210_123_456) - XCTAssertEqual(age.onUranus, 0.46, accuracy: 0.02) + #expect(age.onUranus.isApproximatelyEqual(to: 0.46, relativeTolerance: 0.03)) } - func testAgeOnNeptune() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("age on Neptune", .enabled(if: RUNALL)) + func testAgeOnNeptune() { let age = SpaceAge(1_821_023_456) - XCTAssertEqual(age.onNeptune, 0.35, accuracy: 0.02) + #expect(age.onNeptune.isApproximatelyEqual(to: 0.35, relativeTolerance: 0.03)) } } diff --git a/exercises/practice/strain/.meta/template.swift b/exercises/practice/strain/.meta/template.swift index 181fe63a7..e49bf8608 100644 --- a/exercises/practice/strain/.meta/template.swift +++ b/exercises/practice/strain/.meta/template.swift @@ -1,15 +1,17 @@ -import XCTest +import Testing +import Foundation @testable import {{exercise|camelCase}} -class {{exercise|camelCase}}Tests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false + +@Suite struct {{exercise|camelCase}}Tests { {% for case in cases %} {% if forloop.first -%} - func test{{case.description |camelCase }}() { + @Test("{{case.description}}") {% else -%} - func test{{case.description |camelCase }}() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("{{case.description}}", .enabled(if: RUNALL)) {% endif -%} + func test{{case.description |camelCase }}() { {%- if case.input.list.count == 0 -%} let input : [Int] = [] {%- else -%} @@ -20,7 +22,7 @@ class {{exercise|camelCase}}Tests: XCTestCase { {%- else %} let expected = {{case.expected | toStringArray}} {%- endif %} - XCTAssertEqual(input.{{case.property}} {{case.input.predicate | strain}}, expected) + #expect(input.{{case.property}} {{case.input.predicate | strain}} == expected) } {% endfor -%} } diff --git a/exercises/practice/strain/Package.swift b/exercises/practice/strain/Package.swift index 778628ef4..3944cabcf 100644 --- a/exercises/practice/strain/Package.swift +++ b/exercises/practice/strain/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:6.0 import PackageDescription diff --git a/exercises/practice/strain/Tests/StrainTests/StrainTests.swift b/exercises/practice/strain/Tests/StrainTests/StrainTests.swift index 0adf43202..1a97c910c 100644 --- a/exercises/practice/strain/Tests/StrainTests/StrainTests.swift +++ b/exercises/practice/strain/Tests/StrainTests/StrainTests.swift @@ -1,108 +1,111 @@ -import XCTest +import Foundation +import Testing @testable import Strain -class StrainTests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +@Suite struct StrainTests { + + @Test("keep on empty list returns empty list") func testKeepOnEmptyListReturnsEmptyList() { let input: [Int] = [] let expected: [Int] = [] - XCTAssertEqual(input.keep { x in true }, expected) + #expect(input.keep { x in true } == expected) } - func testKeepsEverything() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("keeps everything", .enabled(if: RUNALL)) + func testKeepsEverything() { let input = [1, 3, 5] let expected = [1, 3, 5] - XCTAssertEqual(input.keep { x in true }, expected) + #expect(input.keep { x in true } == expected) } - func testKeepsNothing() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("keeps nothing", .enabled(if: RUNALL)) + func testKeepsNothing() { let input = [1, 3, 5] let expected: [Int] = [] - XCTAssertEqual(input.keep { x in false }, expected) + #expect(input.keep { x in false } == expected) } - func testKeepsFirstAndLast() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("keeps first and last", .enabled(if: RUNALL)) + func testKeepsFirstAndLast() { let input = [1, 2, 3] let expected = [1, 3] - XCTAssertEqual(input.keep { x in x % 2 == 1 }, expected) + #expect(input.keep { x in x % 2 == 1 } == expected) } - func testKeepsNeitherFirstNorLast() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("keeps neither first nor last", .enabled(if: RUNALL)) + func testKeepsNeitherFirstNorLast() { let input = [1, 2, 3] let expected = [2] - XCTAssertEqual(input.keep { x in x % 2 == 0 }, expected) + #expect(input.keep { x in x % 2 == 0 } == expected) } - func testKeepsStrings() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("keeps strings", .enabled(if: RUNALL)) + func testKeepsStrings() { let input = ["apple", "zebra", "banana", "zombies", "cherimoya", "zealot"] let expected = ["zebra", "zombies", "zealot"] - XCTAssertEqual(input.keep { x in x.starts(with: "z") }, expected) + #expect(input.keep { x in x.starts(with: "z") } == expected) } - func testKeepsLists() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("keeps lists", .enabled(if: RUNALL)) + func testKeepsLists() { let input = [ [1, 2, 3], [5, 5, 5], [5, 1, 2], [2, 1, 2], [1, 5, 2], [2, 2, 1], [1, 2, 5], ] let expected = [[5, 5, 5], [5, 1, 2], [1, 5, 2], [1, 2, 5]] - XCTAssertEqual(input.keep { x in x.contains(5) }, expected) + #expect(input.keep { x in x.contains(5) } == expected) } - func testDiscardOnEmptyListReturnsEmptyList() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("discard on empty list returns empty list", .enabled(if: RUNALL)) + func testDiscardOnEmptyListReturnsEmptyList() { let input: [Int] = [] let expected: [Int] = [] - XCTAssertEqual(input.discard { x in true }, expected) + #expect(input.discard { x in true } == expected) } - func testDiscardsEverything() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("discards everything", .enabled(if: RUNALL)) + func testDiscardsEverything() { let input = [1, 3, 5] let expected: [Int] = [] - XCTAssertEqual(input.discard { x in true }, expected) + #expect(input.discard { x in true } == expected) } - func testDiscardsNothing() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("discards nothing", .enabled(if: RUNALL)) + func testDiscardsNothing() { let input = [1, 3, 5] let expected = [1, 3, 5] - XCTAssertEqual(input.discard { x in false }, expected) + #expect(input.discard { x in false } == expected) } - func testDiscardsFirstAndLast() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("discards first and last", .enabled(if: RUNALL)) + func testDiscardsFirstAndLast() { let input = [1, 2, 3] let expected = [2] - XCTAssertEqual(input.discard { x in x % 2 == 1 }, expected) + #expect(input.discard { x in x % 2 == 1 } == expected) } - func testDiscardsNeitherFirstNorLast() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("discards neither first nor last", .enabled(if: RUNALL)) + func testDiscardsNeitherFirstNorLast() { let input = [1, 2, 3] let expected = [1, 3] - XCTAssertEqual(input.discard { x in x % 2 == 0 }, expected) + #expect(input.discard { x in x % 2 == 0 } == expected) } - func testDiscardsStrings() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("discards strings", .enabled(if: RUNALL)) + func testDiscardsStrings() { let input = ["apple", "zebra", "banana", "zombies", "cherimoya", "zealot"] let expected = ["apple", "banana", "cherimoya"] - XCTAssertEqual(input.discard { x in x.starts(with: "z") }, expected) + #expect(input.discard { x in x.starts(with: "z") } == expected) } - func testDiscardsLists() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("discards lists", .enabled(if: RUNALL)) + func testDiscardsLists() { let input = [ [1, 2, 3], [5, 5, 5], [5, 1, 2], [2, 1, 2], [1, 5, 2], [2, 2, 1], [1, 2, 5], ] let expected = [[1, 2, 3], [2, 1, 2], [2, 2, 1]] - XCTAssertEqual(input.discard { x in x.contains(5) }, expected) + #expect(input.discard { x in x.contains(5) } == expected) } } diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md index 7535931af..8228edc6c 100644 --- a/exercises/practice/sublist/.docs/instructions.md +++ b/exercises/practice/sublist/.docs/instructions.md @@ -8,8 +8,8 @@ Given any two lists `A` and `B`, determine if: - None of the above is true, thus lists `A` and `B` are unequal Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. -List `A` is a superlist of `B` if `A` contains a sub-sequence of values equal to `B`. -List `A` is a sublist of `B` if `B` contains a sub-sequence of values equal to `A`. +List `A` is a superlist of `B` if `A` contains a contiguous sub-sequence of values equal to `B`. +List `A` is a sublist of `B` if `B` contains a contiguous sub-sequence of values equal to `A`. Examples: diff --git a/exercises/practice/sublist/.meta/template.swift b/exercises/practice/sublist/.meta/template.swift index 09b3e1852..bbfe98726 100644 --- a/exercises/practice/sublist/.meta/template.swift +++ b/exercises/practice/sublist/.meta/template.swift @@ -1,16 +1,18 @@ -import XCTest +import Testing +import Foundation @testable import {{exercise|camelCase}} -class {{exercise|camelCase}}Tests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false + +@Suite struct {{exercise|camelCase}}Tests { {% for case in cases %} {% if forloop.first -%} - func test{{case.description |camelCase }}() { + @Test("{{case.description}}") {% else -%} - func test{{case.description |camelCase }}() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("{{case.description}}", .enabled(if: RUNALL)) {% endif -%} - XCTAssertEqual(.{{case.expected}}, classifier(listOne: {{case.input.listOne}}, listTwo: {{case.input.listTwo}})) + func test{{case.description |camelCase }}() { + #expect(.{{case.expected}} == classifier(listOne: {{case.input.listOne}}, listTwo: {{case.input.listTwo}})) } {% endfor -%} } diff --git a/exercises/practice/sublist/Package.swift b/exercises/practice/sublist/Package.swift index f76b5bd24..879055d7f 100644 --- a/exercises/practice/sublist/Package.swift +++ b/exercises/practice/sublist/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:6.0 import PackageDescription diff --git a/exercises/practice/sublist/Tests/SublistTests/SublistTests.swift b/exercises/practice/sublist/Tests/SublistTests/SublistTests.swift index 5e46e5977..53b3fc334 100644 --- a/exercises/practice/sublist/Tests/SublistTests/SublistTests.swift +++ b/exercises/practice/sublist/Tests/SublistTests/SublistTests.swift @@ -1,96 +1,99 @@ -import XCTest +import Foundation +import Testing @testable import Sublist -class SublistTests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +@Suite struct SublistTests { + + @Test("empty lists") func testEmptyLists() { - XCTAssertEqual(.equal, classifier(listOne: [], listTwo: [])) + #expect(.equal == classifier(listOne: [], listTwo: [])) } - func testEmptyListWithinNonEmptyList() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.sublist, classifier(listOne: [], listTwo: [1, 2, 3])) + @Test("empty list within non empty list", .enabled(if: RUNALL)) + func testEmptyListWithinNonEmptyList() { + #expect(.sublist == classifier(listOne: [], listTwo: [1, 2, 3])) } - func testNonEmptyListContainsEmptyList() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.superlist, classifier(listOne: [1, 2, 3], listTwo: [])) + @Test("non empty list contains empty list", .enabled(if: RUNALL)) + func testNonEmptyListContainsEmptyList() { + #expect(.superlist == classifier(listOne: [1, 2, 3], listTwo: [])) } - func testListEqualsItself() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.equal, classifier(listOne: [1, 2, 3], listTwo: [1, 2, 3])) + @Test("list equals itself", .enabled(if: RUNALL)) + func testListEqualsItself() { + #expect(.equal == classifier(listOne: [1, 2, 3], listTwo: [1, 2, 3])) } - func testDifferentLists() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.unequal, classifier(listOne: [1, 2, 3], listTwo: [2, 3, 4])) + @Test("different lists", .enabled(if: RUNALL)) + func testDifferentLists() { + #expect(.unequal == classifier(listOne: [1, 2, 3], listTwo: [2, 3, 4])) } - func testFalseStart() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.sublist, classifier(listOne: [1, 2, 5], listTwo: [0, 1, 2, 3, 1, 2, 5, 6])) + @Test("false start", .enabled(if: RUNALL)) + func testFalseStart() { + #expect(.sublist == classifier(listOne: [1, 2, 5], listTwo: [0, 1, 2, 3, 1, 2, 5, 6])) } - func testConsecutive() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.sublist, classifier(listOne: [1, 1, 2], listTwo: [0, 1, 1, 1, 2, 1, 2])) + @Test("consecutive", .enabled(if: RUNALL)) + func testConsecutive() { + #expect(.sublist == classifier(listOne: [1, 1, 2], listTwo: [0, 1, 1, 1, 2, 1, 2])) } - func testSublistAtStart() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.sublist, classifier(listOne: [0, 1, 2], listTwo: [0, 1, 2, 3, 4, 5])) + @Test("sublist at start", .enabled(if: RUNALL)) + func testSublistAtStart() { + #expect(.sublist == classifier(listOne: [0, 1, 2], listTwo: [0, 1, 2, 3, 4, 5])) } - func testSublistInMiddle() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.sublist, classifier(listOne: [2, 3, 4], listTwo: [0, 1, 2, 3, 4, 5])) + @Test("sublist in middle", .enabled(if: RUNALL)) + func testSublistInMiddle() { + #expect(.sublist == classifier(listOne: [2, 3, 4], listTwo: [0, 1, 2, 3, 4, 5])) } - func testSublistAtEnd() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.sublist, classifier(listOne: [3, 4, 5], listTwo: [0, 1, 2, 3, 4, 5])) + @Test("sublist at end", .enabled(if: RUNALL)) + func testSublistAtEnd() { + #expect(.sublist == classifier(listOne: [3, 4, 5], listTwo: [0, 1, 2, 3, 4, 5])) } - func testAtStartOfSuperlist() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.superlist, classifier(listOne: [0, 1, 2, 3, 4, 5], listTwo: [0, 1, 2])) + @Test("at start of superlist", .enabled(if: RUNALL)) + func testAtStartOfSuperlist() { + #expect(.superlist == classifier(listOne: [0, 1, 2, 3, 4, 5], listTwo: [0, 1, 2])) } - func testInMiddleOfSuperlist() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.superlist, classifier(listOne: [0, 1, 2, 3, 4, 5], listTwo: [2, 3])) + @Test("in middle of superlist", .enabled(if: RUNALL)) + func testInMiddleOfSuperlist() { + #expect(.superlist == classifier(listOne: [0, 1, 2, 3, 4, 5], listTwo: [2, 3])) } - func testAtEndOfSuperlist() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.superlist, classifier(listOne: [0, 1, 2, 3, 4, 5], listTwo: [3, 4, 5])) + @Test("at end of superlist", .enabled(if: RUNALL)) + func testAtEndOfSuperlist() { + #expect(.superlist == classifier(listOne: [0, 1, 2, 3, 4, 5], listTwo: [3, 4, 5])) } - func testFirstListMissingElementFromSecondList() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.unequal, classifier(listOne: [1, 3], listTwo: [1, 2, 3])) + @Test("first list missing element from second list", .enabled(if: RUNALL)) + func testFirstListMissingElementFromSecondList() { + #expect(.unequal == classifier(listOne: [1, 3], listTwo: [1, 2, 3])) } - func testSecondListMissingElementFromFirstList() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.unequal, classifier(listOne: [1, 2, 3], listTwo: [1, 3])) + @Test("second list missing element from first list", .enabled(if: RUNALL)) + func testSecondListMissingElementFromFirstList() { + #expect(.unequal == classifier(listOne: [1, 2, 3], listTwo: [1, 3])) } - func testFirstListMissingAdditionalDigitsFromSecondList() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.unequal, classifier(listOne: [1, 2], listTwo: [1, 22])) + @Test("first list missing additional digits from second list", .enabled(if: RUNALL)) + func testFirstListMissingAdditionalDigitsFromSecondList() { + #expect(.unequal == classifier(listOne: [1, 2], listTwo: [1, 22])) } - func testOrderMattersToAList() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.unequal, classifier(listOne: [1, 2, 3], listTwo: [3, 2, 1])) + @Test("order matters to a list", .enabled(if: RUNALL)) + func testOrderMattersToAList() { + #expect(.unequal == classifier(listOne: [1, 2, 3], listTwo: [3, 2, 1])) } - func testSameDigitsButDifferentNumbers() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(.unequal, classifier(listOne: [1, 0, 1], listTwo: [10, 1])) + @Test("same digits but different numbers", .enabled(if: RUNALL)) + func testSameDigitsButDifferentNumbers() { + #expect(.unequal == classifier(listOne: [1, 0, 1], listTwo: [10, 1])) } } diff --git a/exercises/practice/sum-of-multiples/.meta/template.swift b/exercises/practice/sum-of-multiples/.meta/template.swift index 11a4feb6a..09aa4754c 100644 --- a/exercises/practice/sum-of-multiples/.meta/template.swift +++ b/exercises/practice/sum-of-multiples/.meta/template.swift @@ -1,16 +1,18 @@ -import XCTest +import Testing +import Foundation @testable import {{exercise|camelCase}} -class {{exercise|camelCase}}Tests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false + +@Suite struct {{exercise|camelCase}}Tests { {% for case in cases %} {% if forloop.first -%} - func test{{case.description |camelCase }}() { + @Test("{{case.description}}") {% else -%} - func test{{case.description |camelCase }}() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test + @Test("{{case.description}}", .enabled(if: RUNALL)) {% endif -%} - XCTAssertEqual(toLimit({{case.input.limit}}, inMultiples: {{case.input.factors}}), {{case.expected}}) + func test{{case.description |camelCase }}() { + #expect(toLimit({{case.input.limit}}, inMultiples: {{case.input.factors}}) == {{case.expected}}) } {% endfor -%} } diff --git a/exercises/practice/sum-of-multiples/Package.swift b/exercises/practice/sum-of-multiples/Package.swift index d94b6886a..ed4af850d 100644 --- a/exercises/practice/sum-of-multiples/Package.swift +++ b/exercises/practice/sum-of-multiples/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:6.0 import PackageDescription diff --git a/exercises/practice/sum-of-multiples/Tests/SumOfMultiplesTests/SumOfMultiplesTests.swift b/exercises/practice/sum-of-multiples/Tests/SumOfMultiplesTests/SumOfMultiplesTests.swift index 4119106d0..c5ed774b5 100644 --- a/exercises/practice/sum-of-multiples/Tests/SumOfMultiplesTests/SumOfMultiplesTests.swift +++ b/exercises/practice/sum-of-multiples/Tests/SumOfMultiplesTests/SumOfMultiplesTests.swift @@ -1,86 +1,91 @@ -import XCTest +import Foundation +import Testing @testable import SumOfMultiples -class SumOfMultiplesTests: XCTestCase { - let runAll = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false +@Suite struct SumOfMultiplesTests { + + @Test("no multiples within limit") func testNoMultiplesWithinLimit() { - XCTAssertEqual(toLimit(1, inMultiples: [3, 5]), 0) + #expect(toLimit(1, inMultiples: [3, 5]) == 0) } - func testOneFactorHasMultiplesWithinLimit() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(4, inMultiples: [3, 5]), 3) + @Test("one factor has multiples within limit", .enabled(if: RUNALL)) + func testOneFactorHasMultiplesWithinLimit() { + #expect(toLimit(4, inMultiples: [3, 5]) == 3) } - func testMoreThanOneMultipleWithinLimit() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(7, inMultiples: [3]), 9) + @Test("more than one multiple within limit", .enabled(if: RUNALL)) + func testMoreThanOneMultipleWithinLimit() { + #expect(toLimit(7, inMultiples: [3]) == 9) } - func testMoreThanOneFactorWithMultiplesWithinLimit() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(10, inMultiples: [3, 5]), 23) + @Test("more than one factor with multiples within limit", .enabled(if: RUNALL)) + func testMoreThanOneFactorWithMultiplesWithinLimit() { + #expect(toLimit(10, inMultiples: [3, 5]) == 23) } - func testEachMultipleIsOnlyCountedOnce() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(100, inMultiples: [3, 5]), 2318) + @Test("each multiple is only counted once", .enabled(if: RUNALL)) + func testEachMultipleIsOnlyCountedOnce() { + #expect(toLimit(100, inMultiples: [3, 5]) == 2318) } - func testAMuchLargerLimit() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(1000, inMultiples: [3, 5]), 233168) + @Test("a much larger limit", .enabled(if: RUNALL)) + func testAMuchLargerLimit() { + #expect(toLimit(1000, inMultiples: [3, 5]) == 233168) } - func testThreeFactors() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(20, inMultiples: [7, 13, 17]), 51) + @Test("three factors", .enabled(if: RUNALL)) + func testThreeFactors() { + #expect(toLimit(20, inMultiples: [7, 13, 17]) == 51) } - func testFactorsNotRelativelyPrime() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(15, inMultiples: [4, 6]), 30) + @Test("factors not relatively prime", .enabled(if: RUNALL)) + func testFactorsNotRelativelyPrime() { + #expect(toLimit(15, inMultiples: [4, 6]) == 30) } - func testSomePairsOfFactorsRelativelyPrimeAndSomeNot() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(150, inMultiples: [5, 6, 8]), 4419) + @Test("some pairs of factors relatively prime and some not", .enabled(if: RUNALL)) + func testSomePairsOfFactorsRelativelyPrimeAndSomeNot() { + #expect(toLimit(150, inMultiples: [5, 6, 8]) == 4419) } - func testOneFactorIsAMultipleOfAnother() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(51, inMultiples: [5, 25]), 275) + @Test("one factor is a multiple of another", .enabled(if: RUNALL)) + func testOneFactorIsAMultipleOfAnother() { + #expect(toLimit(51, inMultiples: [5, 25]) == 275) } - func testMuchLargerFactors() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(10000, inMultiples: [43, 47]), 2_203_160) + @Test("much larger factors", .enabled(if: RUNALL)) + func testMuchLargerFactors() { + #expect(toLimit(10000, inMultiples: [43, 47]) == 2_203_160) } - func testAllNumbersAreMultiplesOf1() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(100, inMultiples: [1]), 4950) + @Test("all numbers are multiples of 1", .enabled(if: RUNALL)) + func testAllNumbersAreMultiplesOf1() { + #expect(toLimit(100, inMultiples: [1]) == 4950) } - func testNoFactorsMeansAnEmptySum() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(10000, inMultiples: []), 0) + @Test("no factors means an empty sum", .enabled(if: RUNALL)) + func testNoFactorsMeansAnEmptySum() { + #expect(toLimit(10000, inMultiples: []) == 0) } - func testTheOnlyMultipleOf0Is0() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(1, inMultiples: [0]), 0) + @Test("the only multiple of 0 is 0", .enabled(if: RUNALL)) + func testTheOnlyMultipleOf0Is0() { + #expect(toLimit(1, inMultiples: [0]) == 0) } - func testTheFactor0DoesNotAffectTheSumOfMultiplesOfOtherFactors() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(4, inMultiples: [3, 0]), 3) + @Test("the factor 0 does not affect the sum of multiples of other factors", .enabled(if: RUNALL)) + func testTheFactor0DoesNotAffectTheSumOfMultiplesOfOtherFactors() { + #expect(toLimit(4, inMultiples: [3, 0]) == 3) } - func testSolutionsUsingIncludeExcludeMustExtendToCardinalityGreaterThan3() throws { - try XCTSkipIf(true && !runAll) // change true to false to run this test - XCTAssertEqual(toLimit(10000, inMultiples: [2, 3, 5, 7, 11]), 39_614_537) + @Test( + "solutions using include-exclude must extend to cardinality greater than 3", + .enabled(if: RUNALL)) + func testSolutionsUsingIncludeExcludeMustExtendToCardinalityGreaterThan3() { + #expect(toLimit(10000, inMultiples: [2, 3, 5, 7, 11]) == 39_614_537) } } From f200f489fcab740f4cff0810144819aeee6e57e9 Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 4 Jan 2025 12:43:56 +0100 Subject: [PATCH 2/7] Add BirdWatcher exercise with initial implementation and documentation --- concepts/for-loops/about.md | 84 ------------------- exercises/concept/bird-watcher/.docs/hints.md | 19 +++++ .../bird-watcher/.docs/instructions.md | 51 +++++++++++ .../bird-watcher/.docs/introduction.md | 51 +++++++++++ exercises/concept/bird-watcher/.gitignore | 5 ++ .../BirdWatcher/BirdWatcherExemplar.swift | 25 ++++++ .../concept/bird-watcher/.meta/config.json | 20 +++++ .../concept/bird-watcher/.meta/design.md | 31 +++++++ exercises/concept/bird-watcher/Package.swift | 28 +++++++ .../Sources/BirdWatcher/BirdWatcher.swift | 11 +++ .../BirdWatcherTests/BirdWatcherTests.swift | 82 ++++++++++++++++++ 11 files changed, 323 insertions(+), 84 deletions(-) create mode 100644 exercises/concept/bird-watcher/.docs/hints.md create mode 100644 exercises/concept/bird-watcher/.docs/instructions.md create mode 100644 exercises/concept/bird-watcher/.docs/introduction.md create mode 100644 exercises/concept/bird-watcher/.gitignore create mode 100644 exercises/concept/bird-watcher/.meta/Sources/BirdWatcher/BirdWatcherExemplar.swift create mode 100644 exercises/concept/bird-watcher/.meta/config.json create mode 100644 exercises/concept/bird-watcher/.meta/design.md create mode 100644 exercises/concept/bird-watcher/Package.swift create mode 100644 exercises/concept/bird-watcher/Sources/BirdWatcher/BirdWatcher.swift create mode 100644 exercises/concept/bird-watcher/Tests/BirdWatcherTests/BirdWatcherTests.swift diff --git a/concepts/for-loops/about.md b/concepts/for-loops/about.md index 32c347589..e69de29bb 100644 --- a/concepts/for-loops/about.md +++ b/concepts/for-loops/about.md @@ -1,84 +0,0 @@ -# About - -[For-in loops][for-in-loops] are used to iterate over a sequence of values, taking each element in turn, binding it to a variable or constant name of the developer's choosing, then executes a block of code that may refer to the element. When every element of the sequence has been iterated over, the loop exits and execution begins with the first line following the body of the loop. - -```swift -let numbers = [3, 10, 7, 11] -let word = "Supercalifragilisticexpialidocious" - -for number in numbers { - print("\(number) / 2 = \(number / 2)") -} -print("Done with numbers") - -// prints: -// 3 / 2 = 1 -// 10 / 2 = 5 -// 7 / 2 = 3 -// 11 / 2 = 5 -// Done with numbers - - -for char in word { - if "aeiou".contains(char) { - print(char, terminator: "") - } -} -print(" - those are all the vowels") - -// prints: -// ueaiaiiieiaioiou - those are all the vowels - -``` - -If one needs to mutate the current element of the iteration, it can be declared as a variable in the for-in loop: - -```swift -for var x in [123, 900, 7] { - while x > 0 { - print(x % 10) - x /= 10 - } - print() -} - -// prints: -// 3 -// 2 -// 1 -// -// 0 -// 0 -// 9 -// -// 7 -``` - -If one wants to execute a loop a specified number of times, a for-in loop can be used with a range supplied for the sequence to iterate over: - -```swift -for i in 1...3 { - print("i: \(i)") -} - -// prints: -// i: 1 -// i: 2 -// i: 3 -``` - -If the body of the loop doesn't refer to the current element of the sequence, an underscore (`_`) can be supplied for the name: - -```swift -for _ in 1...3 { - print("Perhaps.") -} - -// prints: -// Perhaps. -// Perhaps. -// Perhaps. - -``` - -[for-in-loops]: https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html#ID121 diff --git a/exercises/concept/bird-watcher/.docs/hints.md b/exercises/concept/bird-watcher/.docs/hints.md new file mode 100644 index 000000000..f01ab28fb --- /dev/null +++ b/exercises/concept/bird-watcher/.docs/hints.md @@ -0,0 +1,19 @@ +# Hints + +## General + +- The syntax for defining a closure is `{ (parameter list) -> ReturnType in bodyOfClosure }` + +## 1. Write a closure to flip two wires + +- You can use tuple decomposition to name the individual components of a tuple + +## 2. Write a closure to rotate the wires + +- You can use tuple decomposition to name the individual components of a tuple + +## 3. Implement a wire shuffle generator + +- You can return the last bit of a number, `n`, by computing `n % 2` +- You can remove the last bit of a number, `n`, by computing `n / 2` +- You need to make variable copies of function and closure parameters if you want to mutate them. diff --git a/exercises/concept/bird-watcher/.docs/instructions.md b/exercises/concept/bird-watcher/.docs/instructions.md new file mode 100644 index 000000000..cbc4f842d --- /dev/null +++ b/exercises/concept/bird-watcher/.docs/instructions.md @@ -0,0 +1,51 @@ +# Instructions + +You are an avid bird watcher that keeps track of how many birds have visited your garden. +Usually you use a tally in a notebook to count the birds, but to better work with your data, you've digitalized the bird counts for the past weeks. + +## 1. Determine the total number of birds that you counted so far + +Let us start analyzing the data by getting a high level view. +Find out how many birds you counted in total since you started your logs. + +Implement a function `totalBirdCount` that accepts an array of `Int`s that contains the bird count per day. +It should return the total number of birds that you counted. + +```swift +let birdsPerDay = [2, 5, 0, 7, 4, 1, 3, 0, 2, 5, 0, 1, 3, 1] +totalBirdCount(birdsPerDay) +// returns 34 +``` + +## 2. Calculate the number of visiting birds in a specific week + +Now that you got a general feel for your bird count numbers, you want to make a more fine-grained analysis. + +Implement a function `birdsInWeek` that accepts an array of bird counts per day and a week number. + +It returns the total number of birds that you counted in that specific week. +You can assume weeks are always tracked completely. + +```swift +let birdsPerDay = [2, 5, 0, 7, 4, 1, 3, 0, 2, 5, 0, 1, 3, 1] +birdsInWeek(birdsPerDay, weekNumber: 2) +// returns 12 +``` + +## 3. Fix a counting mistake + +You realized that all the time you were trying to keep track of the birds, there was one bird that was hiding in a far corner of the garden. + +You figured out that this bird always spent every second day in your garden. + +You do not know exactly where it was in between those days but definitely not in your garden. + +Your bird watcher intuition also tells you that the bird was in your garden on the first day that you tracked in your list. + +Given this new information, write a function `fixBirdCountLog` that takes an array of birds counted per day as an argument and returns the array after correcting the counting mistake. + +```swift +var birdsPerDay = [2, 5, 0, 7, 4, 1] +fixBirdCountLog(birdsPerDay) +// returns [3, 5, 1, 7, 5, 1] +``` diff --git a/exercises/concept/bird-watcher/.docs/introduction.md b/exercises/concept/bird-watcher/.docs/introduction.md new file mode 100644 index 000000000..2f258f19b --- /dev/null +++ b/exercises/concept/bird-watcher/.docs/introduction.md @@ -0,0 +1,51 @@ +# Introduction + +Closures in Swift are self-contained blocks of code that can be passed parameters to trigger their computation and return values. Closures also capture values from their environment and use them in their computations. As they are self contained, they may be passed around in a program like other values or assigned to constants and variables. + +Closures may sound a lot like Swift functions; they do, and for good reason. Functions in swift are just special cases of closures which are required to have a name and are defined using a slightly different syntax. + +While functions in Swift are technically closures, when people refer to "closures" in Swift, they are referring to [closure expressions][closure-expressions]. Closure expressions are written as a parameter list followed by a return type and the keyword `in` followed by the body of the closure, all contained in a pair of curly braces: + +```swift +{ (a: Double, b: Double) -> Double in + return a + b / 2.0 +} +``` + +This defines a closure expression of type `(Double, Double) -> Double`, and it can be executed in a similar manner to a function of this type, by applying it to parameters of the appropriate type: + +```swift +{ (a: Double, b: Double) -> Double in + return a + b / 2.0 +}(10.0, 15.0) +// => 12.5 +``` + +As it's not very convenient to write out the full closure expression every time one wants to execute it, closures can be assigned to names, like any other value in Swift. They can then be called by applying the name to the parameters: + +```swift +let mean = { (a: Double, b: Double) -> Double in + a + b / 2.0 +} + +mean(13.0, 100.0) +// => 56.5 +``` + +As seen above, closures, like regular functions, may omit the `return` keyword if the body of the closure is a single expression. + +Most often, closures are used with higher-order functions, either passed in as parameters or returned as results: + +```swift +[11, 75, 3, 99, 53].contains(where: { (x: Int) -> Bool in x > 100 }) +// => false + +["apple", "ball", "carrot"].sorted(by: { (s1: String, s2: String) -> Bool in s1.count < s2.count }) +// => ["ball", "apple", "carrot"] + +func makeAdder(base: Int) -> (Int) -> Int { + { (x: Int) -> Int in base + x } +} +``` + +[closure-expressions]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Closure-Expressions diff --git a/exercises/concept/bird-watcher/.gitignore b/exercises/concept/bird-watcher/.gitignore new file mode 100644 index 000000000..95c432091 --- /dev/null +++ b/exercises/concept/bird-watcher/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ diff --git a/exercises/concept/bird-watcher/.meta/Sources/BirdWatcher/BirdWatcherExemplar.swift b/exercises/concept/bird-watcher/.meta/Sources/BirdWatcher/BirdWatcherExemplar.swift new file mode 100644 index 000000000..9b1f4ca9c --- /dev/null +++ b/exercises/concept/bird-watcher/.meta/Sources/BirdWatcher/BirdWatcherExemplar.swift @@ -0,0 +1,25 @@ +func totalBirdCount(_ birdsPerDay: [Int]) -> Int { + var count = 0 + for day in birdsPerDay { + count += day + } + return count +} + +func birdsInWeek(_ birdsPerDay: [Int], weekNumber: Int) -> Int { + var count = 0 + let startOfWeek = (weekNumber - 1) * 7 + let endOfWeek = startOfWeek + 7 + for i in startOfWeek.. [Int] { + var result = [Int](birdsPerDay) + for i in stride(from: 0, through: birdsPerDay.count - 1, by: 2) { + result[i] += 1 + } + return result +} \ No newline at end of file diff --git a/exercises/concept/bird-watcher/.meta/config.json b/exercises/concept/bird-watcher/.meta/config.json new file mode 100644 index 000000000..263cb427f --- /dev/null +++ b/exercises/concept/bird-watcher/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "meatball133" + ], + "files": { + "solution": [ + "Sources/BirdWatcher/BirdWatcher.swift" + ], + "test": [ + "Tests/BirdWatcherTests/BirdWatcherTests.swift" + ], + "exemplar": [ + ".meta/Sources/BirdWatcher/BirdWatcherExemplar.swift" + ] + }, + "blurb": "Count the birds in your garden with for loops.", + "forked_from": [ + "go/bird-watcher" + ] +} diff --git a/exercises/concept/bird-watcher/.meta/design.md b/exercises/concept/bird-watcher/.meta/design.md new file mode 100644 index 000000000..07f490d2f --- /dev/null +++ b/exercises/concept/bird-watcher/.meta/design.md @@ -0,0 +1,31 @@ +# Design + +This concept exercise should convey a basic understanding of how to work with closures in Swift. + +## Learning objectives + +- Defining closures +- Assigning closures to names +- Capturing the environment +- Shorthand argument names +- Trailing closure syntax + +## Out of scope + +- Capture lists +- Weak/Unowned +- Escaping closures +- Autoclosures + +## Concepts + +- `closures`: Know how to define closures; know how to assign closures to names +- `capturing`: know about environment capture +- `trailing-closures`: know about trailing closure syntax +- `shorthand-arguments`: know about shorthand closure syntax + +## Prerequisites + +- `functions` +- `higher-order-functions` +- `loops` diff --git a/exercises/concept/bird-watcher/Package.swift b/exercises/concept/bird-watcher/Package.swift new file mode 100644 index 000000000..25472ce29 --- /dev/null +++ b/exercises/concept/bird-watcher/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version:6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "BirdWatcher", + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "BirdWatcher", + targets: ["BirdWatcher"]) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "BirdWatcher", + dependencies: []), + .testTarget( + name: "BirdWatcherTests", + dependencies: ["BirdWatcher"]), + ] +) diff --git a/exercises/concept/bird-watcher/Sources/BirdWatcher/BirdWatcher.swift b/exercises/concept/bird-watcher/Sources/BirdWatcher/BirdWatcher.swift new file mode 100644 index 000000000..e84c68d84 --- /dev/null +++ b/exercises/concept/bird-watcher/Sources/BirdWatcher/BirdWatcher.swift @@ -0,0 +1,11 @@ +func totalBirdCount(_ birdsPerDay: [Int]) -> Int { + fatalError("Please implement the totalBirdCount(_:) function") +} + +func birdsInWeek(_ birdsPerDay: [Int], weekNumber: Int) -> Int { + fatalError("Please implement the birdsInWeek(_:weekNumber:) function") +} + +func fixBirdCountLog(_ birdsPerDay: [Int]) -> [Int] { + fatalError("Please implement the birdsInWeek(_:) function") +} diff --git a/exercises/concept/bird-watcher/Tests/BirdWatcherTests/BirdWatcherTests.swift b/exercises/concept/bird-watcher/Tests/BirdWatcherTests/BirdWatcherTests.swift new file mode 100644 index 000000000..aa2b68f08 --- /dev/null +++ b/exercises/concept/bird-watcher/Tests/BirdWatcherTests/BirdWatcherTests.swift @@ -0,0 +1,82 @@ +import Foundation +import Testing + +@testable import BirdWatcher + +let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false + +@Suite struct BirdWatcherTests { + @Test("calculates the correct total number of birds") + func testTotalBirdCount() { + let birdCount = [9, 0, 8, 4, 5, 1, 3] + let expected = 30 + #expect(totalBirdCount(birdCount) == expected) + } + + @Test("works for a short bird count list", .enabled(if: RUNALL)) + func testTotalBirdCountShort() { + let birdCount = [2] + let expected = 2 + #expect(totalBirdCount(birdCount) == expected) + } + + @Test("works for a long bird count list", .enabled(if: RUNALL)) + func testTotalBirdCountLong() { + let birdCount = [2, 8, 4, 1, 3, 5, 0, 4, 1, 6, 0, 3, 0, 1, 5, 4, 1, 1, 2, 6] + let expected = 57 + #expect(totalBirdCount(birdCount) == expected) + } + + @Test("calculates the number of birds in the first week", .enabled(if: RUNALL)) + func testBirdsInFirstWeek() { + let birdCount = [3, 0, 5, 1, 0, 4, 1, 0, 3, 4, 3, 0, 8, 0] + let weekNumber = 1 + let expected = 14 + #expect(birdsInWeek(birdCount, weekNumber: weekNumber) == expected) + } + + @Test("calculates the number of birds for a week in the middle of the log", .enabled(if: RUNALL)) + func testBirdsInMiddleWeek() { + let birdCount = [4, 7, 3, 2, 1, 1, 2, 0, 2, 3, 2, 7, 1, 3, 0, 6, 5, 3, 7, 2, 3] + let weekNumber = 2 + let expected = 18 + #expect(birdsInWeek(birdCount, weekNumber: weekNumber) == expected) + } + + @Test("calculates the number of birds for a week in the end of the log", .enabled(if: RUNALL)) + func testBirdsInLastWeek() { + let birdCount = [4, 7, 3, 2, 1, 1, 2, 0, 2, 3, 2, 7, 1, 3, 0, 6, 5, 3, 7, 2, 3] + let weekNumber = 3 + let expected = 26 + #expect(birdsInWeek(birdCount, weekNumber: weekNumber) == expected) + } + + @Test("works when there is only one week", .enabled(if: RUNALL)) + func testBirdsInOneWeek() { + let birdCount = [3, 0, 3, 3, 2, 1, 0] + let weekNumber = 1 + let expected = 12 + #expect(birdsInWeek(birdCount, weekNumber: weekNumber) == expected) + } + + @Test("returns a bird count list with the corrected values", .enabled(if: RUNALL)) + func testFixBirdCountLog() { + var birdCount = [3, 0, 5, 1, 0, 4, 1, 0, 3, 4, 3, 0] + let expected = [4, 0, 6, 1, 1, 4, 2, 0, 4, 4, 4, 0] + #expect(fixBirdCountLog(birdCount) == expected) + } + + @Test("works for a short bird count list", .enabled(if: RUNALL)) + func testFixBirdCountLogShort() { + var birdCount = [4, 2] + let expected = [5, 2] + #expect(fixBirdCountLog(birdCount) == expected) + } + + @Test("works for a long bird count list", .enabled(if: RUNALL)) + func testFixBirdCountLogLong() { + var birdCount = [2, 8, 4, 1, 3, 5, 0, 4, 1, 6, 0, 3, 0, 1, 5, 4, 1, 1, 2, 6] + let expected = [3, 8, 5, 1, 4, 5, 1, 4, 2, 6, 1, 3, 1, 1, 6, 4, 2, 1, 3, 6] + #expect(fixBirdCountLog(birdCount) == expected) + } +} From 37010261739039a15b3d74eac6aba5827b675ddf Mon Sep 17 00:00:00 2001 From: meatball Date: Mon, 6 Jan 2025 16:11:27 +0100 Subject: [PATCH 3/7] Update concept --- concepts/for-loops/.meta/config.json | 3 +- concepts/for-loops/about.md | 120 ++++++++++++++++++ concepts/for-loops/introduction.md | 103 ++++++++++++++- concepts/for-loops/links.json | 12 +- exercises/concept/bird-watcher/.docs/hints.md | 26 ++-- .../bird-watcher/.docs/introduction.md | 107 ++++++++++++---- 6 files changed, 325 insertions(+), 46 deletions(-) diff --git a/concepts/for-loops/.meta/config.json b/concepts/for-loops/.meta/config.json index d5cb6a257..1d255acef 100644 --- a/concepts/for-loops/.meta/config.json +++ b/concepts/for-loops/.meta/config.json @@ -1,7 +1,8 @@ { "blurb": "For loops can be used to iterate over a sequence of values.", "authors": [ - "wneumann" + "wneumann", + "meatball133" ], "contributors": [] } diff --git a/concepts/for-loops/about.md b/concepts/for-loops/about.md index e69de29bb..2dcdc3854 100644 --- a/concepts/for-loops/about.md +++ b/concepts/for-loops/about.md @@ -0,0 +1,120 @@ +# For loops + +Looping is a fundamental concept in programming that allows you to execute a block of code multiple times. +In Swift, there are two types of loops: [`for-in` loops][for-loops] and `while` loops. +In this chapter, you'll learn about `for-in` loops. + +For loops allows you to iterate over a sequence of values, taking each element in turn, binding it to a variable of your choosing. +Swift allows you to iterate over a variety of sequences, such as ranges, arrays, and strings (and more types which will be covered later). +When every element of the sequence has been iterated over, the loop exits. + +For loops are declared by using the `for` keyword, followed by a variable name, the `in` keyword, and a sequence of values to iterate over. +The variable given in the `for-in` loop is inmutable, meaning you can't change its value inside the loop. +Here's an example of a `for-in` loop that iterates over an array of numbers: + +```swift +let numbers = [3, 10, 7, 11] + +for number in numbers { + print(number) +} +print("Done with numbers") + +// prints: +// 3 +// 10 +// 7 +// 11 +// Done with numbers +``` + +~~~~exercism/note +The `number` variable is declared in the `for-in` loop and is only available within the loop's scope. + +```swift +let numbers = [3, 10, 7, 11] + +for number in numbers { + number + 1 +} +number + 1 // Error: Use of unresolved identifier 'number' +``` +~~~~ + +## Iterating over a range + +You can also iterate over a range of numbers using a `for-in` loop. +This allows you to execute a block of code a specific number of times, for example, the range `1...5` will iterate over the numbers 1, 2, 3, 4, and 5, so the loop will execute 5 times. +Sometimes you might want to iterate over indexes, in a datastructure like an array, then you can use a `0.. ReturnType in bodyOfClosure }` +- Refer to the exercise introduction for an example how to use a for loop to iterate over a slice. +- Use a helper variable to store the total count and increase that variable as you go through the slice. +- Think about the correct initial value for that helper variable. -## 1. Write a closure to flip two wires +## 2. Calculate the number of visiting birds in a specific week -- You can use tuple decomposition to name the individual components of a tuple +- This task is similar to the first one, you can copy your code as a starting point. +- Think about which indexes in the slice you would need to take into account for week number 1 and 2, respectively. +- Now find a general way to calculate the first and the last index that should be considered. +- With that you can set up the for loop to only iterate over the relevant section of the slice. -## 2. Write a closure to rotate the wires +## 3. Fix a counting mistake -- You can use tuple decomposition to name the individual components of a tuple - -## 3. Implement a wire shuffle generator - -- You can return the last bit of a number, `n`, by computing `n % 2` -- You can remove the last bit of a number, `n`, by computing `n / 2` -- You need to make variable copies of function and closure parameters if you want to mutate them. +- Again you need to set up a for loop to iterate over the whole bird count slice. +- This time you only need to visit every second entry in the slice. +- Change the step so the counter variable is increased accordingly after each iteration. +- In the body of the for loop you can use the increment operator to change the value of an element in a slice in-place. diff --git a/exercises/concept/bird-watcher/.docs/introduction.md b/exercises/concept/bird-watcher/.docs/introduction.md index 2f258f19b..07c6622e5 100644 --- a/exercises/concept/bird-watcher/.docs/introduction.md +++ b/exercises/concept/bird-watcher/.docs/introduction.md @@ -1,51 +1,108 @@ -# Introduction +# For loops -Closures in Swift are self-contained blocks of code that can be passed parameters to trigger their computation and return values. Closures also capture values from their environment and use them in their computations. As they are self contained, they may be passed around in a program like other values or assigned to constants and variables. +Looping is a fundamental concept in programming that allows you to execute a block of code multiple times. +In Swift, there are two types of loops: [`for-in` loops][for-loops] and `while` loops. +In this chapter, you'll learn about `for-in` loops. -Closures may sound a lot like Swift functions; they do, and for good reason. Functions in swift are just special cases of closures which are required to have a name and are defined using a slightly different syntax. +For loops allows you to iterate over a sequence of values, taking each element in turn, binding it to a variable of your choosing. +Swift allows you to iterate over a variety of sequences, such as ranges, arrays, and strings (and more types which will be covered later). +When every element of the sequence has been iterated over, the loop exits. -While functions in Swift are technically closures, when people refer to "closures" in Swift, they are referring to [closure expressions][closure-expressions]. Closure expressions are written as a parameter list followed by a return type and the keyword `in` followed by the body of the closure, all contained in a pair of curly braces: +For loops are declared by using the `for` keyword, followed by a variable name, the `in` keyword, and a sequence of values to iterate over. +The variable given in the `for-in` loop is inmutable, meaning you can't change its value inside the loop. +Here's an example of a `for-in` loop that iterates over an array of numbers: ```swift -{ (a: Double, b: Double) -> Double in - return a + b / 2.0 +let numbers = [3, 10, 7, 11] + +for number in numbers { + print(number) } +print("Done with numbers") + +// prints: +// 3 +// 10 +// 7 +// 11 +// Done with numbers ``` -This defines a closure expression of type `(Double, Double) -> Double`, and it can be executed in a similar manner to a function of this type, by applying it to parameters of the appropriate type: +~~~~exercism/note +The `number` variable is declared in the `for-in` loop and is only available within the loop's scope. ```swift -{ (a: Double, b: Double) -> Double in - return a + b / 2.0 -}(10.0, 15.0) -// => 12.5 +let numbers = [3, 10, 7, 11] + +for number in numbers { + number + 1 +} +number + 1 // Error: Use of unresolved identifier 'number' ``` +~~~~ -As it's not very convenient to write out the full closure expression every time one wants to execute it, closures can be assigned to names, like any other value in Swift. They can then be called by applying the name to the parameters: +## Iterating over a range + +You can also iterate over a range of numbers using a `for-in` loop. +This allows you to execute a block of code a specific number of times, for example, the range `1...5` will iterate over the numbers 1, 2, 3, 4, and 5, so the loop will execute 5 times. +Sometimes you might want to iterate over indexes, in a datastructure like an array, then you can use a `0.. Double in - a + b / 2.0 +let numbers = [3, 10, 7, 11] + +for i in 0.. 56.5 +// prints: +// 3 +// 10 +// 7 +// 11 ``` -As seen above, closures, like regular functions, may omit the `return` keyword if the body of the closure is a single expression. +## Iterating over a string -Most often, closures are used with higher-order functions, either passed in as parameters or returned as results: +You can also iterate over a string using a `for-in` loop. +This allows you to iterate over each character in the string, and note specifically that the type given in the loop is a `Character`. ```swift -[11, 75, 3, 99, 53].contains(where: { (x: Int) -> Bool in x > 100 }) -// => false +let message = "Hello!" + +for character in message { + print(character) +} + +// prints: +// H +// e +// l +// l +// o +// ! +``` + +## stride -["apple", "ball", "carrot"].sorted(by: { (s1: String, s2: String) -> Bool in s1.count < s2.count }) -// => ["ball", "apple", "carrot"] +Swift also provides a `stride` function that allows you to create a sequence over a range with a specific step. +Which can be then iterated over using a `for-in` loop. +`stride` is defined as [`stride(from:to:by:)`][stride-to] or [`stride(from:through:by:)`][stride-through], the first one is exclusive and the second one is inclusive. -func makeAdder(base: Int) -> (Int) -> Int { - { (x: Int) -> Int in base + x } +```swift +for i in stride(from: 0, to: 10, by: 2) { + print(i) } + +// prints: +// 0 +// 2 +// 4 +// 6 +// 8 ``` -[closure-expressions]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures/#Closure-Expressions +Note that the `to` parameter is exclusive, so the loop will iterate until the number before the `to` parameter, while the `through` parameter is inclusive, so in this case it would also include the `10`. + +[stride-to]: https://developer.apple.com/documentation/swift/stride(from:to:by:) +[stride-through]: https://developer.apple.com/documentation/swift/stride(from:through:by:) +[for-loops]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/controlflow/#For-In-Loops From 5daa40a9217284f0f51aa0f76fced50dbc262626 Mon Sep 17 00:00:00 2001 From: meatball Date: Mon, 6 Jan 2025 16:46:45 +0100 Subject: [PATCH 4/7] Update config --- config.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config.json b/config.json index 0376ea85c..01fba79ff 100644 --- a/config.json +++ b/config.json @@ -119,6 +119,18 @@ ], "status": "active" }, + { + "slug": "bird-watcher", + "name": "Bird Watcher", + "uuid": "a5efd630-8e6f-49f7-bed5-1576138d2f8a", + "concepts": [ + "for-loops" + ], + "prerequisites": [ + "ranges" + ], + "status": "active" + }, { "slug": "santas-helper", "name": "Santa's Swifty Helper", From 4fce93eddce045c54e351a28051fceb262811cd6 Mon Sep 17 00:00:00 2001 From: meatball Date: Fri, 11 Apr 2025 15:22:51 +0200 Subject: [PATCH 5/7] format config file --- exercises/concept/bird-watcher/.meta/config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/bird-watcher/.meta/config.json b/exercises/concept/bird-watcher/.meta/config.json index 263cb427f..bcf1eff1b 100644 --- a/exercises/concept/bird-watcher/.meta/config.json +++ b/exercises/concept/bird-watcher/.meta/config.json @@ -13,8 +13,8 @@ ".meta/Sources/BirdWatcher/BirdWatcherExemplar.swift" ] }, - "blurb": "Count the birds in your garden with for loops.", "forked_from": [ "go/bird-watcher" - ] + ], + "blurb": "Count the birds in your garden with for loops." } From ea84733d7f20c8156b373375a2463e7ee396abb4 Mon Sep 17 00:00:00 2001 From: meatball Date: Fri, 11 Apr 2025 15:24:36 +0200 Subject: [PATCH 6/7] Remove for loop concept mention --- config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/config.json b/config.json index 01fba79ff..2bc047fed 100644 --- a/config.json +++ b/config.json @@ -162,7 +162,6 @@ "uuid": "a11dc279-0c0a-4dca-89c6-f0abbac265c8", "concepts": [ "loops", - "for-loops", "while-loops", "repeat-while", "control-transfer" From 3b514107c368f199ad8fcbee29c3d9eaba72174a Mon Sep 17 00:00:00 2001 From: meatball Date: Sat, 26 Apr 2025 19:27:50 +0200 Subject: [PATCH 7/7] Changes based on feedback --- concepts/for-loops/about.md | 3 ++- concepts/for-loops/introduction.md | 3 ++- exercises/concept/bird-watcher/.docs/instructions.md | 6 +++--- exercises/concept/bird-watcher/.docs/introduction.md | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/concepts/for-loops/about.md b/concepts/for-loops/about.md index 2dcdc3854..b09ef265f 100644 --- a/concepts/for-loops/about.md +++ b/concepts/for-loops/about.md @@ -37,7 +37,8 @@ let numbers = [3, 10, 7, 11] for number in numbers { number + 1 } -number + 1 // Error: Use of unresolved identifier 'number' +number + 1 +// Error: Use of unresolved identifier 'number' ``` ~~~~ diff --git a/concepts/for-loops/introduction.md b/concepts/for-loops/introduction.md index 07c6622e5..7038a6730 100644 --- a/concepts/for-loops/introduction.md +++ b/concepts/for-loops/introduction.md @@ -37,7 +37,8 @@ let numbers = [3, 10, 7, 11] for number in numbers { number + 1 } -number + 1 // Error: Use of unresolved identifier 'number' +number + 1 +// Error: Use of unresolved identifier 'number' ``` ~~~~ diff --git a/exercises/concept/bird-watcher/.docs/instructions.md b/exercises/concept/bird-watcher/.docs/instructions.md index cbc4f842d..f2f754a5b 100644 --- a/exercises/concept/bird-watcher/.docs/instructions.md +++ b/exercises/concept/bird-watcher/.docs/instructions.md @@ -14,7 +14,7 @@ It should return the total number of birds that you counted. ```swift let birdsPerDay = [2, 5, 0, 7, 4, 1, 3, 0, 2, 5, 0, 1, 3, 1] totalBirdCount(birdsPerDay) -// returns 34 +// Returns 34 ``` ## 2. Calculate the number of visiting birds in a specific week @@ -29,7 +29,7 @@ You can assume weeks are always tracked completely. ```swift let birdsPerDay = [2, 5, 0, 7, 4, 1, 3, 0, 2, 5, 0, 1, 3, 1] birdsInWeek(birdsPerDay, weekNumber: 2) -// returns 12 +// Returns 12 ``` ## 3. Fix a counting mistake @@ -47,5 +47,5 @@ Given this new information, write a function `fixBirdCountLog` that takes an arr ```swift var birdsPerDay = [2, 5, 0, 7, 4, 1] fixBirdCountLog(birdsPerDay) -// returns [3, 5, 1, 7, 5, 1] +// Returns [3, 5, 1, 7, 5, 1] ``` diff --git a/exercises/concept/bird-watcher/.docs/introduction.md b/exercises/concept/bird-watcher/.docs/introduction.md index 07c6622e5..7038a6730 100644 --- a/exercises/concept/bird-watcher/.docs/introduction.md +++ b/exercises/concept/bird-watcher/.docs/introduction.md @@ -37,7 +37,8 @@ let numbers = [3, 10, 7, 11] for number in numbers { number + 1 } -number + 1 // Error: Use of unresolved identifier 'number' +number + 1 +// Error: Use of unresolved identifier 'number' ``` ~~~~