読者です 読者をやめる 読者になる 読者になる

htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

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では標準出力用の関数としてprintprintlnの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 }

このa1a2は同じ結果が入ります。

こんな感じでコードを短くしていたんですが、私の頭ではこれ以上無理でした。
ランキングでは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

*1:コンパイラの最適化具合にもよります