【Kotlin】仕様変更に強い FizzBuzz


ネタ元:「fizzbuzzについて思うこと」
TypeScript版:【TypeScript】仕様変更に強い FizzBuzz
更に仕様変更に強くした版:【Kotlin】更に仕様変更に強い FizzBuzz

FizzBuzz の、割る数、出力する文字列、変換規則の数や順番が変わっても
最小限のコード変更で済むようにする。

/**
 * FizzBuzz を行うためのクラス。
 * 
 * [invoke] 関数に数値を与えると対応する文字列を返す。
 */
class FizzBuzz private constructor(
    private val fizzBuzzMap: List<Pair<Int, String>>
) {
    /**
     * @param fizzBuzzMap 割る数と、
     *  それで割り切れたときに出力する文字列の組の列。
     */
    constructor(
        fizzBuzzMap: Iterable<Pair<Int, String>>
    ) : this(fizzBuzzMap.toList())

    /**
     * @param fizzBuzzMap 割る数と、
     *  それで割り切れたときに出力する文字列の組の列。
     */
    constructor (
        vararg fizzBuzzMap: Pair<Int, String>
    ) : this(fizzBuzzMap.toList())

    /** 与えられた数値に対応する文字列を返す。 */
    operator fun invoke(num: Int): String =
        fizzBuzzMap.asSequence()
            .mapNotNull { (divisor, str) ->
                if (num % divisor == 0) str
                else null
            }
            .joinToString("")
            .takeUnless { it.isEmpty() }
            ?: num.toString()
}

普通の FizzBuzz

/** 変換規則の定義。 */
val fizzBuzz = FizzBuzz(
    3 to "Fizz",
    5 to "Buzz"
)

fun main() {
    (1..15).asSequence()
        .map {
            fizzBuzz(it)
        }.forEach {
            println(it)
        }
}

出力

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

割る数と出力する文字列を変更

/** 変換規則の定義。 */
val fizzBuzz = FizzBuzz(
    7 to "Foo",
    2 to "Bar"
)

// main 関数は同じなので省略

出力

1
Bar
3
Bar
5
Bar
Foo
Bar
9
Bar
11
Bar
13
FooBar
15

こんなことも

val fizzBuzz = FizzBuzz(
    1 to "<",
    3 to "Fizz",
    5 to "Buzz",
    1 to ">"
)

// main 関数は同じなので省略

出力

<>
<>
<Fizz>
<>
<Buzz>
<Fizz>
<>
<>
<Fizz>
<Buzz>
<>
<Fizz>
<>
<>
<FizzBuzz>

/以上