POH7の問題でSwiftを初めて書いた
Paizaさんは相変わらず変態ですね(褒め言葉)
今回のPOHも例に漏れずぶっ飛んだ設定になっています。
さて、縞ニーソの問題(この文だけですでに病気だ…)ですが、始めは無難にRubyで書いていたのですが、POH7からはSwiftコンパイラも採用されたということでSwiftでも書いてみることにしました。
ちなみにgolfもやっていたようなので、なるべく短く書くようにしました。
Swiftについては今までそこかしこでObjective-Cとの対比で「短く書ける」と紹介されていたエントリをいくつか読んだ程度で、実際に書いたことはなく、また構文も特に勉強していなかったため全く書けない状態での挑戦です。
知っていることと言ったら
var s = "variable string" // var で変数宣言 let c = "const string" // let で定数宣言 // func で関数宣言 func f() { // do something }
くらいでした。
本題
前置きはこの程度にして、設問ですが、
赤と白の等間隔の縞模様をあなたは作ろうとしています。 各色の幅 n 、縞模様全体の長さ m が改行区切りで入力されます。 赤を 'R' 、白を 'W' とした赤から始まる縞模様を出力して下さい。 例えば3 10のような入力の場合RRRWWWRRRWのように出力してください。
というものです。
1回目
最初に書いたコードはこちらです。
// 98bytes var n=Int(readLine()!)!,m=Int(readLine()!),i=0;for;i<m;++i{print(i/n&1==0 ?"R":"W",terminator:"")} // 展開すると var n = Int(readLine()!)!, m = Int(readLine()!)! for var i = 0; i < m; ++i { print(i / n & 1 == 0 ? "R" : "W", terminator: "") }
軽く解説しますと、readLine()
が標準入力から1行読み取る関数で、返り値の型はString?
です。
String?
はOptional<String>
と同じ意味を表し、つまりOptional
型の一つです。
Optional
型は型引数に取る型の値以外にnil
も持てる型です。
SwiftではOptional
型を純粋な型に戻す時!
を後ろに付けます。
そしてInt()
はキャスト用の関数です。返り値の型はなぜかInt?
です。
なのでvar n = Int(readLine()!)!
は標準入力から一行読み取り、読み取った文字列を数字としてn
に代入する、という操作になります。
i / n & 1 == 0
の部分はi / n
が奇数なのか偶数なのかの判断をしています。
素直に考えれば奇数か偶数かの判断はi / n % 2 == 0
、つまり「2で割って余りが0になるかどうか」になるかと思いますが、なぜこれで奇数と偶数を判断できるのか。
&
演算子はビット演算子と呼ばれるものの一つで、AND演算を表します。
細かい説明は省きますが、AND演算では与えられた二つの二進数の同じ桁を比較し、両方のビットが立っている時に1とします。
0000 & 0001 = 0000 // 0 & 1 = 0 0001 & 0001 = 0001 // 1 & 1 = 1 0010 & 0001 = 0000 // 2 & 1 = 0 0011 & 0001 = 0001 // 3 & 1 = 1 0100 & 0001 = 0000 // 4 & 1 = 0 0101 & 0001 = 0001 // 5 & 1 = 1 0110 & 0001 = 0000 // 6 & 1 = 0 ......
…とまぁ、こういうわけです。
別にi / n % 2 == 0
でも問題ないどころか可読性はこちらの方が高いのですが、ビット演算の方がより高速に処理できます*1。
始めはn回ごとに'R'と'W'を切り替える処理をゴリゴリ書いていたのですが、
i = 0 s = "R" m.times do if i == n then s = s == "R" ? "W" : "R" i = 0 end print s i += 1 end
一度このコードで提出してから、整数型を整数型で割ると返ってくる値は小数点以下を切り捨てた整数であることに気づきました。
n回ごとに'R'と'W'が切り替わるのであれば、n*2回ごとに1周するはず。
つまり0からmまでのループ変数i
を取るとき、i / n
が奇数なのか偶数なのかで'R'を出力するのか'W'を出力するのかが一意に決まります。
そういうわけでここでは奇数偶数判定を用いています。
さて、残るは出力ですが、Swift 1.xでは標準出力用の関数としてprint
とprintln
の2つを用意し、print
では末尾に改行を入れず、println
では末尾に改行を入れる、という差を設けていたらしいです。
しかしSwift 2.0になった時にprintln
を廃止し、print
に一本化したようです。
そして改行なしの出力をしたい場合は、第二引数のオプションにterminator: ""
を付けろ、ということになったみたいです。
長かったけどこれで解説とします。全然軽くなかった。
2回目
二度目に提出したのが以下です。
// 88bytes var n=Int(readLine()!)!,s="";(0..<Int(readLine()!)!).map{s+=$0/n&1==0 ?"R":"W"};print(s) // 展開すると var n = Int(readLine()!)!, m = Int(readLine()!)!, s = "" (0..<m).map { s += $0 / n & 1 == 0 ? "R" : "W" } print(s)
基本的には変わってないのですが、Range
を利用しています。
let m = 0, n = 10 // mからnまでの範囲を表す let r1 = m...n // mからnまで(nを含まない)の範囲を表す let r2 = m..<n var a1 = [Int](), a2 = [Int]() for var i in r1 { a1.append(i) } for var i in r2 { a2.append(i) } print(a1) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(a2) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ちなみにforEach
メソッドだと長いのでmap
メソッドを使っています。
ブロック内部ではただs
への再代入をしているだけなので返り値がなく、そのことについてコンパイラが警告を出してきますが、コードを短くするためには已む無しなので無視します。
最初はmap
で'R'か'W'を持つ配列にして、最後にjoin
しようと考えていたのですが、
let arr = ["a", "b", "c"] print("".join(arr)) // "abc"
とできたのは、Swift 1.xの頃の話で、Swift 2.0からは
let arr = ["a", "b", "c"] print(arr.joinWithSeparator("")) // "abc"
となってしまい、全然コード量の節約にならなかったため、泣く泣く文字列に追加する方式を取っています。
ちなみにmap
メソッド内部に書かれた$0
はブロックに渡される0番目の仮引数です。
let r = 0...10 let a1 = r.map{ i in i * 2 } let a2 = r.map{ $0 * 2 }
このa1
とa2
は同じ結果が入ります。
こんな感じでコードを短くしていたんですが、私の頭ではこれ以上無理でした。
ランキングでは71bytesが最短らしく、私よりも17bytesも短いです。
もう少し勉強してより短い糖衣構文をいろいろ習得する必要がありそうです。
ところでRubyでは最終的に以下のようなコードを書きました。
# 48bytes n=gets.to_i;gets.to_i.times{|i|print"RW"[i/n&1]} # 展開すると n = gets.to_i m = gets.to_i m.times do |i| print "RW"[i / n & 1] end
Rubyでは文字列に添え字をつけるとslice
メソッドと等価になります。
s = "abcdefghi" p s[2..5] # "cdef" p s.slice(2..5) # "cdef" p s[1, 2] # "bc" p s.slice(1, 2) # "bc"
1をAND演算すると結果が0か1のどちらかのみに絞られることについては上で説明した通りです。
なので"RW"
という2文字の0番目、または1番目を取り出して出力、というコンパクトなコードになります。
【追記】一文字減らせた
# 47bytes n=gets.to_i;gets.to_i.times{|i|print:RW[i/n&1]}
文字列型"RW"
の代わりに、シンボル型:RW
を使いました。
【さらに追記】さらに一文字減らせた
# 46bytes n,m=$<.map(&:to_i);m.times{|i|print:RW[i/n&1]} # パーレンを省略するとさらに一文字 # 45bytes n,m=$<.map &:to_i;m.times{|i|print:RW[i/n&1]}
Rubyの特殊変数$<
を使っています。
これはARGF
のエイリアスで、こいつにEnumerableのメソッドを付けてやると標準入力を行ごとに取ってくるらしいです(よく理解していない)
参照: AtCoderで見かけたRubyショートコーディング術 - Qiita
あと、縞ニーソ問題のSwiftの最短コード数が69bytesになってますね(12月18日23時前現在)。変態どもめ…。
試してみたい方へ
Swiftはここで書いて遊べます。Xcode持ってるならそちらでもいいけど。
SwiftStub: Online Swift Compiler
Rubyはこちらがお勧めです。別にIdeoneでもいいんですが、こちらの方が手軽です。
compile ruby online