5
\$\begingroup\$

I have this code:

func solution(_ num: Int) -> Int {
    let arrayOfTen = Array(0...num)
    var setOfmultiples =  Set<Int>()

    if num <= 0 { return 0 }
    var sum = 0
    
    for element in 0..<num {
        if element % 3 == 0 || element % 5 == 0 {
            setOfmultiples.insert(element)
            sum = setOfmultiples.reduce(0, +)
        }
    }
    return sum
}

Some tests from the website I got this exercise:

import XCTest
class SolutionTest: XCTestCase {
    static var allTests = [
        ("Test Solution", testSolution),
    ]

    func testSolution() {
        XCTAssertEqual(solution(10), 23)
        XCTAssertEqual(solution(20), 78)
        XCTAssertEqual(solution(200), 9168)
    }
}

XCTMain([
    testCase(SolutionTest.allTests)
])

from this exercise:

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Finish the solution so that it returns the sum of all the multiples of 3 or 5 below the number passed in. Additionally, if the number is negative, return 0 (for languages that do have them).

Note: If the number is a multiple of both 3 and 5, only count it once.

How do I refactor it to make it more swifty? I am not that good at using functional programming so I want to understand this topic better.

It seems to be working fine now.

Thanks.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$
    – Mast
    Commented Aug 26, 2022 at 4:29

3 Answers 3

5
\$\begingroup\$

Improving on your approach

Split up your tests

So they pass/fail independently:

import XCTest
import SumOfMultiples

class SumOfMultiplesTests: XCTestCase {
    static var allTests = [
        ("testInput10", testInput10),
        ("testInput20", testInput20),
        ("testInput200", testInput200),
    ]

    func testInput10() { XCTAssertEqual(solution(10), 23) }
    func testInput20() { XCTAssertEqual(solution(20), 78) }
    func testInput200() { XCTAssertEqual(solution(200), 9168) }
}

XCTMain([
    testCase(SolutionTest.allTests)
])

(And make solution a public func, so you don't need @testable on import SumOfMultiples)

arrayOfTen is unused

Delete it. Also, Xcode gives warnings for a reason. Don't ignore them.

Redundant sum calculation

If you just add a log on every calculation of sum, you'll see it's calculated 93 times. You only return the last value of the it, so that's 92 calculations too many:

Sum of [0] = 0
Sum of [0, 3] = 3
Sum of [0, 3, 5] = 8
Sum of [0, 3, 5, 6] = 14
Sum of [0, 3, 5, 6, 9] = 23
Sum of [0, 3, 5, 6, 9, 10] = 33
Sum of [0, 3, 5, 6, 9, 10, 12] = 45
Sum of [0, 3, 5, 6, 9, 10, 12, 15] = 60
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18] = 78
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20] = 98
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21] = 119
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24] = 143
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25] = 168
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27] = 195
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30] = 225
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33] = 258
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35] = 293
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36] = 329
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39] = 368
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40] = 408
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42] = 450
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45] = 495
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48] = 543
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50] = 593
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51] = 644
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54] = 698
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55] = 753
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57] = 810
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60] = 870
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63] = 933
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65] = 998
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66] = 1064
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69] = 1133
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70] = 1203
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72] = 1275
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75] = 1350
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78] = 1428
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80] = 1508
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81] = 1589
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84] = 1673
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85] = 1758
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87] = 1845
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90] = 1935
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93] = 2028
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95] = 2123
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96] = 2219
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99] = 2318
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100] = 2418
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102] = 2520
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105] = 2625
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108] = 2733
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110] = 2843
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111] = 2954
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114] = 3068
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115] = 3183
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117] = 3300
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120] = 3420
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123] = 3543
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125] = 3668
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126] = 3794
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129] = 3923
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130] = 4053
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132] = 4185
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135] = 4320
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138] = 4458
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140] = 4598
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141] = 4739
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144] = 4883
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145] = 5028
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147] = 5175
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150] = 5325
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153] = 5478
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155] = 5633
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156] = 5789
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159] = 5948
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160] = 6108
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162] = 6270
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165] = 6435
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168] = 6603
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170] = 6773
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171] = 6944
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174] = 7118
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175] = 7293
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177] = 7470
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180] = 7650
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180, 183] = 7833
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180, 183, 185] = 8018
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180, 183, 185, 186] = 8204
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180, 183, 185, 186, 189] = 8393
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180, 183, 185, 186, 189, 190] = 8583
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180, 183, 185, 186, 189, 190, 192] = 8775
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180, 183, 185, 186, 189, 190, 192, 195] = 8970
Sum of [0, 3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25, 27, 30, 33, 35, 36, 39, 40, 42, 45, 48, 50, 51, 54, 55, 57, 60, 63, 65, 66, 69, 70, 72, 75, 78, 80, 81, 84, 85, 87, 90, 93, 95, 96, 99, 100, 102, 105, 108, 110, 111, 114, 115, 117, 120, 123, 125, 126, 129, 130, 132, 135, 138, 140, 141, 144, 145, 147, 150, 153, 155, 156, 159, 160, 162, 165, 168, 170, 171, 174, 175, 177, 180, 183, 185, 186, 189, 190, 192, 195, 198] = 9168

This is trivial to fix, just move the calculation after the loop:

public func solution(_ num: Int) -> Int {
    var setOfmultiples =  Set<Int>()

    if num <= 0 { return 0 }
    
    for element in 0..<num {
        if element % 3 == 0 || element % 5 == 0 {
            setOfmultiples.insert(element)
        }
    }
    
    var sum = 0
    sum = setOfmultiples.reduce(0, +)
    return sum
}

At that point, you can remove the needless initial value (0) and just inline it to let sum = setOfmultiples.reduce(0, +).

Then sum becomes a bit superfluous, so you can just: return setOfmultiples.reduce(0, +)

sum alternative approach

Alternatively, you could just use sum += element in your if statement. Rather than regenerating the sum from all setOfmultiples, you'll just build it up using the value from the previous iteration, and simply adding the new value to it.

At that point, you'll notice that setOfmultiples is actually never used. You were never really interested in the set, you only really cared about their eventual sum.

Replace loop with filter

Now that the loop only has one effect (conditionally appending to a set), we can just replace that with a filter.

Since we're no longer building up setOfMultiples through mutation, we can not make this a let constant:

public func solution(_ num: Int) -> Int {
    if num <= 0 { return 0 }
    
    let setOfmultiples = (0..<num).filter { $0 % 3 == 0 || $0 % 5 == 0 }
    
    return setOfmultiples.reduce(0, +)
}

This happens to build up an Array<Int> instead of the previous Set<Int>. I think this is acceptable, because the de-duplication benefit of sets was never even being used

Use isMultiple(of:)

As a more readable alternative to the x % 3 == 0 idiom.

Seems trivial, but you'd be surprised just how many people get mixed up and write x % 3 != 1 (which is wrong for negative numbers). Just search for cs.github.com for /% \d+ != 1[^\d]/ and you'll see a list of bug after buger.

public func solution(_ num: Int) -> Int {
    if num <= 0 { return 0 }
    
    let setOfmultiples = (0..<num).filter { $0.isMultiple(of: 3) || $0.isMultiple(of: 5) }
    
    return setOfmultiples.reduce(0, +)
}

Inline setOfmultiples

At this point, it's not doing much:

public func solution(_ num: Int) -> Int {
    if num <= 0 { return 0 }
    
    return (0..<num)
        .filter { $0.isMultiple(of: 3) || $0.isMultiple(of: 5) }
        .reduce(0, +)
}

Make it lazy

We never actually need the full array that's the result of that call to filter. All we're really after is the sum. We can use a lazy sequence to just yield the elements as they're needed, and reducing them into the final sum without needing to store them in memory:

public func solution(_ num: Int) -> Int {
    if num <= 0 { return 0 }
    
    return (0..<num)
        .lazy
        .filter { $0.isMultiple(of: 3) || $0.isMultiple(of: 5) }
        .reduce(0, +)
}

This reduces the space complexity of this algorithm from O(num) (storing an array with some roughly-constant proportion of all numbers in 0..<num) to just O(1) (the constant space it takes for the stack frame, lazy data structures, reduce accumulator, etc.). Importantly, because it's now O(1), this algorithm will not take more memory when given a larger input num.

Remarks on this approach

It's testing every single number in the range 0..<num, which could be wasteful if the factors we were interested (3 and 5) were large. Suppose they were 9,999,999 and 9,999,998 instead. Only 1 in 10 million numbers would ever be a multiple of them.

\$\endgroup\$
6
  • \$\begingroup\$ wow, thanks, you shortened the code a lot \$\endgroup\$
    – Shum
    Commented Aug 25, 2022 at 9:15
  • \$\begingroup\$ @Shum Indeed! I think you just got lost in the sauce a little. Your focus was on how to build up setOfMultiples, even though your actual goal was to ultimately get sum. \$\endgroup\$
    – Alexander
    Commented Aug 25, 2022 at 9:20
  • 1
    \$\begingroup\$ Of course, with a little bit of mathematics, this can be computed directly, without loops, arrays, or sets. \$\endgroup\$
    – Martin R
    Commented Aug 25, 2022 at 13:42
  • \$\begingroup\$ @MartinR - How would you do that? \$\endgroup\$
    – Rob
    Commented Aug 25, 2022 at 14:20
  • 2
    \$\begingroup\$ @Rob: See for example codereview.stackexchange.com/a/280/35991 or projecteuler.net/overview=001. (For the latter, you may have to solve projecteuler.net/problem=1 first :) \$\endgroup\$
    – Martin R
    Commented Aug 25, 2022 at 14:24
4
\$\begingroup\$

Obligatory Math Solution

One of the most useful principles of enumeration in discrete probability and combinatorial theory is the celebrated principle of inclusion–exclusion. When skillfully applied, this principle has yielded the solution to many a combinatorial problem.

-- Gian-Carlo Rota

There is no need to use loops or any functional programming at all because this problem can be solved directly by inclusion-exclusion. The sum is (sum of multiples of 3) + (sum of multiples of 5) - (sum of multiples of 15) so that multiples of 15 aren't counted twice. This can be generalized to any set of multiples, but it is especially easy if the given numbers are coprime (what's counted twice, thrice, etc. is the LCM of the given numbers.)

For any given N, there are N/3 (floor division) multiples of 3, and

3 + 6 + 9 + ... + 3*(N/3) = 3 (1 + 2 + 3 + ... + N/3)

and the sum 1 + 2 + 3 + ... + k = (k choose 2) = k*(k+1)/2. Similar for 5 and 15, so this can be turned into a function like sumOfMultiplesBelowN(N,d).

\$\endgroup\$
3
\$\begingroup\$

This was my Swifty approach, using stride to create efficient sequences and Set to remove duplicates:

func sumMultiples(of m: Int, and n: Int, below threshold: Int) -> Int {
    Set(stride(from: m, to: threshold, by: m))
        .union(stride(from: n, to: threshold, by: n))
        .reduce(0, +)
}

It is not particularly space efficient (it builds sets of values), but it enjoys a certain simplicity.

If I wanted something more efficient, I would just loop through, incrementing two counters by their respective step values, adding/updating as appropriate:

func sumMultiples(of m: Int, and n: Int, below threshold: Int) -> Int {
    guard m > 0, n > 0 else {
       return 0
    }
    
    var nextM = m
    var nextN = n
    var sum = 0
    
    while nextN < threshold || nextM < threshold {
        if nextM < threshold, nextM < nextN {
            sum += nextM
            nextM += m
        } else if nextN < threshold, nextN < nextM {
            sum += nextN
            nextN += n
        } else if nextM < threshold, nextM == nextN {
            sum += nextM
            nextM += m
            nextN += n
        }
    }
    
    return sum
}

Alternatively, one might create a “sequence of multiples values below a certain threshold”:

struct Multiples: Sequence, IteratorProtocol {
    let m: Int
    let n: Int
    let threshold: Int
    
    private var nextM: Int
    private var nextN: Int
    
    init?(m: Int, n: Int, threshold: Int) {
        guard m > 0, n > 0 else { return nil }
        
        self.m = m
        self.n = n
        self.threshold = threshold
        self.nextM = m
        self.nextN = n
    }
    
    mutating func next() -> Int? {
        guard nextM < threshold || nextN < threshold else { return nil }
        
        if nextM < nextN {
            defer { nextM += m }
            return nextM
        } else if nextN < nextM {
            defer { nextN += n }
            return nextN
        } else { // nextM == nextN
            defer {
                nextM += m
                nextN += n
            }
            return nextN
        }
    }
}

Now that you have that sequence, you can use reduce to sum those values:

func sumMultiples(of m: Int, and n: Int, below threshold: Int) -> Int {
    Multiples(m: m, n: n, threshold: threshold)?
        .reduce(0, +) ?? 0
}

Underpinning all of these approaches is the observation that one should not iterate through every natural number below the threshold, but rather only step through the multiples. E.g., if looking for the sum of all multiples of 3,000,000 and 5,000,000 below 10,000,000, it should require four iterations, not 9,999,999 of them.


The above solution is specific to only having two multiples. One can generalize that to any number of multiples, e.g.

struct Multiples: Sequence, IteratorProtocol {
    let multiples: [Int]
    let threshold: Int

    private var nextValues: [Int]

    init?(multiples: [Int], threshold: Int) {
        self.multiples = multiples.filter { $0 > 0 }

        guard !multiples.isEmpty else { return nil }

        self.threshold = threshold
        self.nextValues = self.multiples
    }

    mutating func next() -> Int? {
        var lowest = threshold
        var indices: [Int] = []

        for (index, value) in nextValues.enumerated() {
            if value < lowest {
                lowest = value
                indices = [index]
            } else if value == lowest {
                indices.append(index)
            }
        }

        guard lowest < threshold else { return nil }

        for index in indices {
            nextValues[index] += multiples[index]
        }

        return lowest
    }
}

func sumMultiples(_ multiples: [Int], below threshold: Int) -> Int {
    Multiples(multiples: multiples, threshold: threshold)?
        .reduce(0, +) ?? 0
}

let result = sumMultiples([3_000_000, 5_000_000, 7_000_000], below: 10_000_000) // 30,000,000
\$\endgroup\$
4
  • 1
    \$\begingroup\$ The set union approach is an interesting space-time tradeoff. You don't check all the numbers, but you do store lots of them in memory. Ultimately, the smart iterator approach is 🏆 \$\endgroup\$
    – Alexander
    Commented Aug 25, 2022 at 15:14
  • \$\begingroup\$ Yeah, given that the OP was building an array/set, it seemed like a viable stepping stone to the eventual solution. \$\endgroup\$
    – Rob
    Commented Aug 25, 2022 at 16:02
  • 1
    \$\begingroup\$ Your work (in particular the now replaced “merge two sequences” approach) inspired me to write a generic solution for that purpose: codereview.stackexchange.com/q/279163/35991. Your feedback is welcome! \$\endgroup\$
    – Martin R
    Commented Aug 25, 2022 at 16:08
  • \$\begingroup\$ Yeah, I wasn't too happy with that rendition so I shifted away from it; right or wrong, I can't say. What I have attempted to do in this latest edit is to eliminate the hardcoded "only handle two multiples", and generalize it to handle a variable number of multiples. \$\endgroup\$
    – Rob
    Commented Aug 25, 2022 at 18:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.