以下で投稿した問題の回答例です。
#練習問題
##練習問題1−1
###回答例
func sum(of values: [Double]) -> Double {
var sum = 0.0
for value in values {
sum += value
}
return sum
}
###解説
for
でループを回し全ての値を足し合わせます。
##練習問題1−2
###回答例
func average(of values: [Double]) -> Double? {
let sumOfValues = sum(of: values)
if values.isEmpty {
return nil
}
return sumOfValues / Double(values.count)
}
func sum(of values: [Double]) -> Double {
var sum = 0.0
for value in values {
sum += value
}
return sum
}
###解説
【練習問題1−1】で作成した関数を使いまわしています。
作った関数はどんどん使いまわしていきましょう。
結果として[合計 / 要素数]とし平均値を返しています。
values.count
はInt
型のためDouble
型に変換して計算しています。
values.isEmpty
では要素数が0かどうか判定します。
配列.isEmpty
は配列の要素が0の場合trueを返します。
配列が空の場合values.count
は0となり、分母が0で計算できません。
そのため空のときは計算できないという意味でnilを返しています。
##練習問題2
###回答例
func max(of values: [Int]) -> Int? {
if values.isEmpty {
return nil
}
var tmpMax: Int? = nil
for value in values {
if let unwrappedMax = tmpMax {
if value > unwrappedMax {
tmpMax = value
}
} else {
tmpMax = value
}
}
return tmpMax
}
###解説
まずvalues
が空の場合は最大値となる数値が存在しないためnilを返します。
var tmpMax: Int? = nil
として最大値となる値を格納する変数を定義します。
型はInt?
とオプショナル型で定義しています。
最初は入れるべき値が不明なためnilとするためです。
例えば初期値に0をいれてしまうとvalues
の値が全て負の数の場合、最大値として0を返してしまうためそれは不正解です。
この時点では要素数は1つ以上ということが確定しているのでvar tmpMax = values[0]
とするのはOKです。
その場合for
内のアンラップ処理が不要となるのでもう少しすっきりした書き方ができます。
for
のアンラップ処理でtmpMax
がnilの場合比較対象がないためvalueをそのまま入れています。
ここでvalues[0]
が格納されます。
if value > unwrappedMax
でtmpMax
に保持していた値とvalue
を比較し大きい方を新しいtmpMax
として更新します。
配列の値全てを検証し終えた後tmpMax
に格納されている値が最大値となるので戻り値として返しています。
解説はしませんがシンプルな書き方として別解を載せます。
func max(of values: [Int]) -> Int? {
guard var tmpMax = values.first else {
return nil
}
for value in values {
tmpMax = value > tmpMax ? value : tmpMax
}
return tmpMax
}
#応用問題
##応用問題1
###回答例
func twoSums(from values: [Int], target: Int) -> [Int] {
var currentIndex = 0
for currentValue in values {
let def = target - currentValue
var defIndex = 0
for defValue in values {
if def == defValue && currentIndex != defIndex {
return [currentValue, defValue]
}
defIndex += 1
}
curentIndex += 1
}
return []
}
###解説
まずvalues
に対してループを二重に回し、それぞれ判定しています。
currentIndex
は外側のループ回数、defIndex
は内側のループ回数です。
外側のループのlet def = target - currentValue
ではcurrentValue
にいくつ足せばtarget
の値になるか計算します。
さらに内側でvalues
に対しループを回し、値の中でdef
と等しい数字があるかを検索します。
if def == defValue && currentIndex != defIndex
という条件を切っています。
def == defValue
は条件に合致する値かを判定しています。
currentIndex != defIndex
では外側のcurrentValue
とは異なる要素番号なのかを判定しています。
例えばvalues
が[5, 3, 1]、target
が10だったと仮定します。
これは足して10となる組がないので空配列が返ることを期待します。
ですがcurrentIndex != defIndex
の条件がなれば、外側と内側ループがそれぞれ0番目の要素に「5」という値を見つけてしまい[5, 5]という配列を戻り値として返してしまいます。
そのための条件です。
ではcurrentValue != defValue
としても同じかと思う方もいるかもしれませんが、その場合[5, 3, 1, 5]に対応できなくなるのでNGです。
条件に合致する値の組を見つけた場合はfor
の途中で戻り値を返し処理を終了します。
for
の検索が終わっても戻り値を返していない場合は見つからなかったとして最後に空配列を返します。
###別解
この処理は配列の要素数の2乗分ループが回りますが無駄な処理が多いです。
例えばcurrentIndex
が2のとき、すでに要素番号0, 1の検索は終わっています。
(currentIndex, defIndex)
として(0, 2)
、(1, 2)
では条件を満たせなかったということは(2, 0)
、(2, 1)
も条件満たさないことがわかるため、無駄なループです。
また(currentIndex, defIndex)=(2, 2)
も仕様として許可していないのでdefIndex
は3以降で検索すればよくなります。
この無駄を省いた処理が以下になります。
func twoSums(from values: [Int], target: Int) -> [Int] {
var curentIndex = 0
for currentValue in values {
let def = target - currentValue
for defIndex in (curentIndex + 1)..<values.count {
let defValue = values[defIndex]
if def == defValue {
return [currentValue, defValue]
}
}
curentIndex += 1
}
return []
}
##応用問題2
###回答例
func getPrimeNumbers(limit: Int) -> [Int] {
guard limit > 1 else {
return []
}
var primeNumbers: [Int] = []
for currentNumber in 2...limit {
if checkPrimeNumber(number: currentNumber) {
primeNumbers.append(currentNumber)
}
}
return primeNumbers
}
func checkPrimeNumber(number: Int) -> Bool {
guard number > 1 else {
return false
}
for currentNumber in 2..<number {
if number % currentNumber == 0 {
return false
}
}
return true
}
###解説
素数かどうかを判定する関数としてcheckPrimeNumber
を作成しています。
checkPrimeNumber
を作成せず全ての処理をgetPrimeNumbers
に書いていても問題ありません。
####checkPrimeNumber
与えた数字が素数かどうかを判定し、true/falseを戻り値として返す関数として定義しています。
guard number > 1 else
で1より小さい数字は素数ではないので先に処理してガードしています。
for
で2〜number-1まで総当りで判定します。
どれかひとつでも割り切れたら素数ではないので、割り切れた時点でfalseを返します。
最後までループが回ると割り切れなかったということなので、numberは素数と判定でき、trueを返します。
####getPrimeNumbers
limit
が1より小さい場合、素数は存在しないためガードし、先に空配列を返します。
guard
文で値を2以上に絞ったためループの条件を2〜limitとし、2から順に総当りで判定します。
checkPrimeNumber
で素数かどうかを判定し、素数の場合は配列に追加します。
最後までループを回しきればprimeNumbers
に全ての素数が格納されるのでこれを戻り値とします。