はじめに
この記事はinfix
(中置記法)で遊んでる謎記事です。
なんだこれって思いながら読んでください。
ある日
Aさん「競プロはC++がおすすめだよー」
僕「へー!でもKotlin使いたい・・・でもAtCoderでKotlin使いづらいしな・・・」1
Aさん「C++こんな事できるんだよ!」
int a,b,c;
cin >> a >> b >> c;
cout << "あ" << "い" << "う" << endl;
/*
結果↓
あいう
*/
僕「なんて直感的な書き方なんだ!すげー!C++最高!」
でもやっぱりKotlinが好き
僕「競プロでC++使ってるけど、Kotlin信者としてはKotlin使いたいな・・・」
僕「ハッ!閃いた!Kotlinでこんなの(cin >>)とかこういうの(cout <<)できるようにすればいいんだ!」
cout
を実装する
C++のcoutの機能を挙げてみます(C++初心者なので不正確)
-
cout << a << "b"
で文字列型変数や文字列リテラルをつなげて出力できる。 -
endl
で改行できる。
こんな感じですかね。他にもcoutの特徴があったら教えてください。
では、実装していきましょーー!!
//大文字じゃないとQiitaでエラー出るなぁ・・・
object cout {
fun add(charSequence: CharSequence) {
print(charSequence)
}
fun add(value: Any?) {
print(value)
}
const val endl = "\n"
}
infix fun cout.shl(charSequence: CharSequence): cout {
add(charSequence)
return this
}
infix fun cout.shl(value: Any?): cout {
add(value)
return this
}
infix
は中置記法というやつで、a until b
と同じように書ける関数を定義できます。
中置記法の説明(Docsより抜粋)
infix
キーワードでマークされた関数は、中置記法を使用して呼び出すこともできます。 (呼び出しのドットと括弧を省略します)
中置関数は、次の要件を満たす必要があります。
- それらはメンバー関数または拡張関数でなければなりません。
- パラメーターは 1 つでなければなりません。
- このパラメーターは、可変数の引数を受け入れてはならず、デフォルト値を持っていてはなりません。
cout
の使用例です。
import cout.endl
fun main(){
cout shl "あ" shl "a" shl endl
cout shl "い" shl "i" shl endl
}
結果
あa
いi
プロセスは終了コード 0 で終了しました
おおおお!C++っぽいですね!!
†ULTIMATE STRING†
命名はてきとーです
ローカル変数ではリフレクションが使えないので後述のcin
で値の変更ができるようにCharSequence
を継承したクラスを作ります。
class UltimateString<T>(var value: T, private val mapper: (String.() -> T)? = null) : CharSequence {
private var string: String = ""
internal fun set(charSequence: CharSequence) {
string = charSequence as String
value = mapper?.let { string.it() } ?: string as T
}
operator fun plus(other: Any?): String {
return string + other
}
override val length: Int = string.length
override fun get(index: Int): Char {
return string[index]
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return string.subSequence(startIndex, endIndex)
}
override fun toString(): String {
return string
}
fun get() = value
}
operator fun String.invoke(): us {
return us(this)
}
operator fun <T> T.invoke(mapper: (String.() -> T)? = null): UltimateString<T> {
return UltimateString(this, mapper)
}
//Ultimate Stringの略
typealias us = sus<String>
//Super Ultimate String ඞ
typealias sus<T> = UltimateString<T>
このクラスのmapper
にキャスト用の関数を書くことでInt型などにも変換できるようになっています。
短い文でUltimateString
を宣言できるようにinvoke()
を無理矢理使っています。
val sus = sus(0){ toInt() }
val us = 0{ toInt() }
val us = us("")
上のように書くことができます。(他にも書き方はありますが)
面倒くさいのでcompareTo
とかは実装してません。
cin
を実装する
cin
は代入しないといけないのでcout
よりも実装がめんどくさそうです。
cinの機能はこんな感じですかね
- 入力をスペースで区切って変数に代入
- 数値とかにキャストしてくれる
ここで先程のUltimate Stringが活躍します。
import kotlin.reflect.KMutableProperty
class cin {
private val readArray: Array<String> = readln().split(" ").toTypedArray()
private val iterator = readArray.iterator()
fun nextRead(): String = iterator.next()
}
infix fun <T> cin.shr(value: UltimateString<T>): cin {
value.set(this.nextRead())
return this
}
infix fun <T:CharSequence> cin.shr(value: KMutableProperty<T>): cin {
value.setter.call(this.nextRead() as T)
return this
}
ほとんどcout
と同じですが、UltimateString.set()
経由で値を設定しています。
プロパティに関してはリフレクションを使って設定します。
使用例
import cout.endl
var a = ""
fun main() {
val b = 0{toInt()}
cin() shr ::a shr b
cout shl a shl "+" shl b shl "=" shl a.toInt() + b.get() shl endl
}
結果
(入力)1 2
1+2=3
プロセスは終了コード 0 で終了しました
おおお!できましたね!!!
valで宣言してもUltimateString
だと書き換え可能ですがまあいいでしょう!!
おわりに
しょうもない記事ですが最後まで読んでくださりありがとうございます!!
良いKotlin&競プロライフを!!!
-
AtCoderではKotlin 1.3しか使えず、readlnとかめんどくさいです。 ↩