「第24回オフラインリアルタイムどう書くの問題」を
Kotlin で解いてみた。
解答
下に行くほど実装の詳細になるようにした。
import kotlin.math.*
/**
* 「多段階選抜 2014.8.2 問題」
* http://nabetani.sakura.ne.jp/hena/ord24eliseq/
* の入力に対して出力を返す。
*
* @receiver 入力。
* @return 出力。
*/
fun String.resolved(): String =
naturalNumberSequence
.applyFilters(this)
.take(10)
.joinToString(",")
/** 自然数を小さい順に並べたシーケンス。 */
private val naturalNumberSequence: Sequence<Int>
get() = sequence {
var n = 1;
while (true) {
yield(n++)
}
}
/**
* 指定されたフィルターを掛けたシーケンスを返す。
*
* @receiver 元のシーケンス。
* @param filterSymbols フィルターを表す記号の列。
* @return フィルターを掛けたシーケンス。
*/
private fun Sequence<Int>.applyFilters(filterSymbols: String): Sequence<Int> =
filterSymbols.asSequence().map {
requireNotNull(FILTER_SYMBOL_TO_FILTER_MAP[it]) {
"フィルターを表す記号の列 $filterSymbols に不正な文字 $it が含まれています。"
}
}.fold(this) { intSequence, filter ->
intSequence.filter()
}
/** フィルターを表す記号をフィルターに変換するマップ。 */
private val FILTER_SYMBOL_TO_FILTER_MAP: Map<Char, Sequence<Int>.() -> Sequence<Int>> =
mutableMapOf<Char, Sequence<Int>.() -> Sequence<Int>>().also { map ->
(2..9).forEach {
map['0' + it] = { filterNotMultiplePosition(it) }
}
map['S'] = { filterNotNext(::isSquareNumber) }
map['s'] = { filterNotPrev(::isSquareNumber) }
map['C'] = { filterNotNext(::isCubicNumber) }
map['c'] = { filterNotPrev(::isCubicNumber) }
map['h'] = { drop(100) }
}.toMap()
/** [n] の倍数番目(1始まり)の要素を除いたシーケンスを返す。 */
private fun Sequence<Int>.filterNotMultiplePosition(n: Int): Sequence<Int> =
filterIndexed { index, _ -> (index + 1) % n != 0 }
/**
* [predicate] が true を返す要素の直前の要素を除いたシーケンスを返す。
*
* @param predicate 述語式。引数 number には要素の値が渡される。
*/
private fun Sequence<Int>.filterNotPrev(
predicate: (number: Int) -> Boolean
): Sequence<Int> = sequence {
val iterator = this@filterNotPrev.iterator()
if (iterator.hasNext().not()) {
return@sequence
}
var prev = iterator.next()
while (iterator.hasNext()) {
val current = iterator.next()
if (predicate(current).not()) {
yield(prev)
}
prev = current
}
}
/**
* [predicate] が true を返す要素の直後の要素を除いたシーケンスを返す。
*
* @param predicate 述語式。引数 number には要素の値が渡される。
*/
private fun Sequence<Int>.filterNotNext(
predicate: (number: Int) -> Boolean
): Sequence<Int> = sequence {
val iterator = this@filterNotNext.iterator()
if (iterator.hasNext().not()) {
return@sequence
}
var current = iterator.next()
yield(current)
while (iterator.hasNext()) {
val next = iterator.next()
if (predicate(current).not()) {
yield(next)
}
current = next
}
}
/** 平方数かどうかを返す。 */
private fun isSquareNumber(number: Int): Boolean =
sqrt(number.toDouble()).let { it == round(it) }
/** 立方数かどうかを返す。 */
private fun isCubicNumber(number: Int): Boolean =
number.toDouble().pow(1.0 / 3.0)
.roundToInt()
.let { it * it * it == number }
実行例
fun main() {
"ss6cc24S".resolved().also {
println(it) // > 1,9,21,30,33,37,42,44,49,56
}
}
テスト
fun main() {
/*0*/ test("ss6cc24S", "1,9,21,30,33,37,42,44,49,56");
/*1*/ test("h", "101,102,103,104,105,106,107,108,109,110");
/*2*/ test("hh", "201,202,203,204,205,206,207,208,209,210");
/*3*/ test("hhh", "301,302,303,304,305,306,307,308,309,310");
/*4*/ test("2", "1,3,5,7,9,11,13,15,17,19");
/*5*/ test("22", "1,5,9,13,17,21,25,29,33,37");
/*6*/ test("222", "1,9,17,25,33,41,49,57,65,73");
/*7*/ test("3", "1,2,4,5,7,8,10,11,13,14");
/*8*/ test("33", "1,2,5,7,10,11,14,16,19,20");
/*9*/ test("333", "1,2,7,10,14,16,20,23,28,29");
/*10*/ test("s", "1,2,4,5,6,7,9,10,11,12");
/*11*/ test("ss", "1,4,5,6,9,10,11,12,13,16");
/*12*/ test("sss", "4,5,9,10,11,12,16,17,18,19");
/*13*/ test("S", "1,3,4,6,7,8,9,11,12,13");
/*14*/ test("SS", "1,4,7,8,9,12,13,14,15,16");
/*15*/ test("SSS", "1,8,9,13,14,15,16,20,21,22");
/*16*/ test("c", "1,2,3,4,5,6,8,9,10,11");
/*17*/ test("cc", "1,2,3,4,5,8,9,10,11,12");
/*18*/ test("ccc", "1,2,3,4,8,9,10,11,12,13");
/*19*/ test("C", "1,3,4,5,6,7,8,10,11,12");
/*20*/ test("CC", "1,4,5,6,7,8,11,12,13,14");
/*21*/ test("CCC", "1,5,6,7,8,12,13,14,15,16");
/*22*/ test("23", "1,3,7,9,13,15,19,21,25,27");
/*23*/ test("32", "1,4,7,10,13,16,19,22,25,28");
/*24*/ test("2h", "201,203,205,207,209,211,213,215,217,219");
/*25*/ test("h2", "101,103,105,107,109,111,113,115,117,119");
/*26*/ test("sC", "1,4,5,6,7,9,10,11,12,13");
/*27*/ test("Cs", "1,4,5,6,7,8,10,11,12,13");
/*28*/ test("s468", "1,2,4,6,7,11,12,16,17,20");
/*29*/ test("S468", "1,3,4,7,8,12,13,16,18,21");
/*30*/ test("cc579", "1,2,3,4,8,9,11,13,15,16");
/*31*/ test("CC579", "1,4,5,6,8,11,13,15,17,18");
/*32*/ test("85", "1,2,3,4,6,7,9,10,12,13");
/*33*/ test("sh", "110,111,112,113,114,115,116,117,118,119");
/*34*/ test("94h", "150,151,154,155,156,158,159,160,163,164");
/*35*/ test("h9c8", "101,102,103,104,105,106,107,110,111,112");
/*36*/ test("Cc3s", "1,3,5,6,10,11,13,16,17,19");
/*37*/ test("cs4h6", "149,150,152,153,154,157,158,160,161,162");
/*38*/ test("84523c", "1,3,11,15,23,26,34,38,46,49");
/*39*/ test("54C78hS", "228,231,232,233,236,241,242,243,246,247");
/*40*/ test("65h7ccs", "151,152,153,154,157,158,160,163,164,165");
/*41*/ test("c95hSc2C", "145,147,151,153,156,159,162,164,168,171");
/*42*/ test("c5h3Ss794", "130,131,133,137,138,142,148,150,152,157");
/*43*/ test("7ShscC846", "129,130,131,134,135,139,141,142,146,148");
/*44*/ test("cshSCCS7ch", "253,254,256,259,260,261,263,264,265,266");
/*45*/ test("hhC7849Ss6C", "201,202,203,205,206,211,212,216,220,225");
/*46*/ test("hhsc3C987Ccs", "201,202,204,205,207,208,214,217,218,220");
/*47*/ test("SC7S8hc59ss2", "162,169,174,178,182,185,188,194,199,203");
/*48*/ test("s7S6c35C9CShc", "367,371,377,379,380,385,387,388,392,395");
/*49*/ test("4scC3hh982Cc5s", "422,426,430,434,447,451,459,463,471,479");
/*50*/ test("23h465Ssc9CchC", "1027,1033,1045,1047,1057,1069,1071,1075,1081,1093");
}
/**
* テストを実施する。
*
* @param input 入力。
* @param expectedOutput 期待される出力。
* @exception Exception テストに失敗した場合。
*/
private fun test(input: String, expectedOutput: String) {
input.resolved().also {
check(it == expectedOutput) {
"Test failed! input: $input, output: $it, expectedOutput: $expectedOutput"
}
}
}
標準入出力を用いた実行
標準入力から1行「入力」を行うと
標準出力に1行結果が「出力」される。
空行を入力すると終了する。
fun main() {
while (true) {
readLine().takeUnless { it.isNullOrEmpty() }
?.also {
runCatching { it.resolved() }
.onSuccess { println(it) }
.onFailure { println(it.message) }
}
?: break
}
}
/以上