[メモ]数値を1から順番に、ただしある数値で割り切れる時のみ特定の文字列を出力するプログラム(Kotlin)


※2020/2/17 追記しました

以下のような課題に触れる機会があったので、備忘録として解決方法を残しておきます。

課題: 
指定した数値まで1から順番に出力するプログラムを以下の条件を守った上で作成せよ。

① 3で割り切れる数字の時は文字列「A」を出力せよ
② 5で割り切れる数字の時は文字列「B」を出力せよ
③ 3と5で割り切れる数字の時は文字列「AB」を出力せよ

咄嗟に作成したプログラム


fun outNum(maxNum: Int) {
    var result = ""
    for(i in 1..maxNum) {
        if(i % 3 == 0 && i % 5 != 0) {
            result += "A\n"
        } else if(i % 5 == 0 && i % 3 != 0) {
            result += "B\n"
        } else if(i % 3 == 0 && i % 5 == 0) {
            result += "AB\n"
        } else {
            result += i
        }
    }
    println(result)
}

う〜ん、これはひどい!

我ながらひどい。
このままでは

「7で割り切れる時と11で割り切れる時も別の文字列を出力するよう改修せよ」

とか言われた時に目も当てられないif文になってしまいます。

それを踏まえた上で冷静に作成し直したプログラム


fun outNum(maxNum: Int) {
    var result = ""
    for(i in 1..maxNum) {
        if(getDisivleNumber(i).isEmpty()) {
            result += i
        } else {
            getDisivleNumber(i).forEach { disivle ->
               result += when(disivle) {
                    3 -> "A"
                    5 -> "B"
                    else -> ""
                }
            }
        }
        result += "\n"
    }

    println(result)
}

/**
* 指定した数値の約数をリスト化して返す
*/
fun getDisivleNumber(num: Int): MutableList<Int> {    
    val list = listOf(3,5)
    val arrays:MutableList<Int> = mutableListOf()
    list.forEach { i ->
        if(num % i == 0) {
            arrays.add(i)
        }
    }
    return arrays
}

こんな感じです。
これなら後々文字列出力する約数が追加された時でも楽に対応できます。

2020/2/17 追記

頂いたコメントから、上記のコードではまだ問題があることがわかりました。

例:約数をgetDisivleNumber関数とoutNum関数の中でそれぞれ指定しているので、追加や修正があった場合、両方を修正しないとエラーになる 等

ほとんど頂いたコメントのソースコードの転載になりますが、問題を修正したプログラムを以下に記載いたします。


fun main(args: Array<String>) {
    printlnDivisorNum(100)
}

fun printlnDivisorNum(maxCount: Int) {
    val divisorList = listOf(
        3 to "A",
        5 to "B",
        7 to "C",
        11 to "D"
    )
    // 拡張関数を使い文字列を生成し、順番に出力する
    for(num in 1..maxCount) {
        println(num.toDivisorText(divisorList))
    }
}

/**
* Intの拡張関数
* 引数の数値リストに自身を割り切れる数値が含まれている場合は
* それに対応する文字列を、(※複数含まれる場合は文字列を連結する)
* 含まれない場合は自身を文字列として返す
*/
fun Int.toDivisorText(divisorList: List<Pair<Int, String>>): String = 
    buildString {
        divisorList.forEach { (num, text) ->
            if(this@toDivisorText % num == 0) {
                append(text)
            }
        }
    }.takeIf { it.isNotEmpty() }
        ?: toString()

※約数に7と11を追加しています

上記コードの出力例

1
2
A
4
B
A
C
8
A
B
D
A
13
C
AB
16
17
A
19
B
AC
D
23
A
B
26
A
C
29
AB
31
32
AD
34
BC
A
37
38
A
B
41
AC
43
D
AB
46
47
A
C
B
A
52
53
A
BD
C
A
58
59
AB
61
62
AC
64
B
AD
67
68
A
BC
71
A
73
74
AB
76
CD
A
79
B
A
82
83
AC
B
86
A
D
89
AB
C
92
A
94
B
A
97
C
AD
B

拡張関数やbuildString, takeIfの使い方などとても勉強になりました。
ありがとうございます。