【プログラミング初心者】Swift練習問題~繰り返し2~ 回答例

練習問題

練習問題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.countInt型のため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 > unwrappedMaxtmpMaxに保持していた値と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に全ての素数が格納されるのでこれを戻り値とします。

コメント

タイトルとURLをコピーしました