I was solving a problem 20, Factorial Digit Sum, on Project Euler.
Factorial Digits Sum
\$n!\$ means \$n \cdot (n − 1) \cdot \ldots \cdot 3 \cdot 2 \cdot 1\$
For example, \$10! = 10 \cdot 9 \cdot \ldots \cdot 3 \cdot 2 \cdot 1 = 3628800\$, and the sum of the digits in the number \$10!\$ is \$3 + 6 + 2 + 8 + 8 + 0 + 0 = 27\$.
Find the sum of the digits in the number \$100!\$.
While doing this problem, I was working with really big numbers (\$100!\$ or \$9.33 \cdot 10^{157}\$). So at first, I decided to try to use Float80
and format it properly.
Since I have no I idea and could not find anything to properly explain to me how to do this with Float80
and String(format:_:)
or NumberFormatter
, I decided to make a function takes in two String
parameters and returns their values multiplied as a String
. With my multiplyLong (_:_:)
function, I can multiply two numbers of any size. I wrote this function using concepts and principals from long multiplication from elementary school.
I wanted to find out if there is a better way to make a function that can multiply numbers of any size. Also, although my multiplyLong(_:_:)
function works perfectly, it is quite long and I would like dramatically refine it, shorten it, and make it more efficient.
Note: I am using Swift 4.1 and Xcode 9.4
My multiplyLong(_:_:)
Function
func multiplyLong(_ x: String, _ y: String) -> String {
precondition(!x.isEmpty && !y.isEmpty, "Error, valid numbers are required for multiplication")
var prefix = ""
var containsInvalidCharacters : Bool {
switch (x, y) {
case let (s1, s2) where s1.first! == "-" && s2.first! == "-":
return !((s1.dropFirst() + s2.dropFirst()).contains { ["0","1","2","3","4","5","6","7","8","9"].contains($0) ? false : true })
case let (s1, s2) where s2.first! == "-":
prefix = "-"
return !((s1 + s2.dropFirst()).contains { ["0","1","2","3","4","5","6","7","8","9"].contains($0) ? false : true })
case let (s1, s2) where s1.first! == "-":
prefix = "-"
return !((s1.dropFirst() + s2).contains { ["0","1","2","3","4","5","6","7","8","9"].contains($0) ? false : true })
case let (s1, s2):
return !((s1 + s2).contains { ["0","1","2","3","4","5","6","7","8","9"].contains($0) ? false : true })
}
}
precondition(containsInvalidCharacters, "Error, multiplicand contains invalid non-numeric characters")
let s1 = x.replacingOccurrences(of: "-", with: "").map { Int(String($0))! }
let s2 = y.replacingOccurrences(of: "-", with: "").map { Int(String($0))! }
var a = [Int]()
var b = [Int]()
switch (s1, s2) {
case let (arr1, arr2) where arr1.count > arr2.count || arr1.first! >= arr2.first!:
a = Array(arr1.reversed())
b = Array(arr2.reversed())
case let (arr1, arr2):
a = Array(arr2.reversed())
b = Array(arr1.reversed())
}
var lines = [[Int]]()
for (bi, bn) in b.enumerated() {
var line = Array(repeating: 0, count: bi)
var carriedNumber = 0
for an in a {
let v = (bn * an) + carriedNumber
carriedNumber = (v - (v % 10)) / 10
line.insert(v % 10, at: 0)
}
if carriedNumber != 0 {
if carriedNumber >= 10 {
line.insert(carriedNumber % 10, at: 0)
line.insert((carriedNumber - (carriedNumber % 10)) / 10, at: 0)
} else {
line.insert(carriedNumber, at: 0)
}
}
lines.append(line)
}
let maxIndex = lines.max { $0.count < $1.count }!.count
var result = [Int]()
var addNum = 0
for i in 0...maxIndex {
var v = addNum
for line in lines {
v += line.indices.contains(i) ? line.reversed()[i] : 0
}
if v >= 10 {
addNum = Int("\("\(v)".dropLast())")!
result.insert(Int("\("\(v)".last!)")!, at: 0)
} else {
addNum = 0
result.insert(v, at: 0)
}
}
if addNum != 0 {
for i in "\(addNum)".reversed() {
result.insert(Int("\(i)")!, at: 0)
}
}
result = Array(result.drop { $0 == 0 })
let str = result.reduce("") { $0 + "\($1)" }
return prefix + str
}
My Solution
func factorialDigitSum() -> Int {
let f = Array(1...100).map{ String($0) }.reduce("1",multiplyLong)
return f.map { Int("\($0)") ?? 0 }.reduce(0, +)
}
print(factorialDigitSum()) //Prints "648"
Note: 648 is indeed the right answer.