AtCoder に登録したら解くべき精選過去問 10 問を Ruby のメソッドチェーンで解いてみた

# はじめに

Rubyの1式のメソッドチェーンだけで以下の記事の10問を解いてみました。
https://qiita.com/drken/items/fd4e5e3630d0f5859067

問題を解くアルゴリズム自体には焦点を当てていないので、「どうしてそれが答えになるの？」となったら他の解説を読んでください。

# レギュレーション

特に厳密に定めている訳ではないのですが、大体以下のようなレギュレーションがあります。

### 一時変数を使ってはいけない

ブロック変数（`a.map{|x|x+1}`の`x`など）や特殊変数（`$<`など）を除いて、自分で一時変数を置いたりしてはいけません。

### 複数の式を並べてはいけない

改行やセミコロンを用いて複数の式を並べてはいけません。

# 全体のコード

最初に10問全体のコードを載せておきます。後からそれぞれについて軽く解説します。
全てが`p`や`puts`の出力式1つだけで成り立っていることがわかると思います。

```ruby
# [ABC086A - Product]
puts gets.split.map(&:to_i).none?(&:even?) ? "Odd" : "Even"

# [ABC081A - Placing Marbles]
p gets.chomp.count("1")

# [ABC081B - Shift Only]
p $<.to_a.last.split.map{|a|a.to_i.to_s(2).reverse.index("1")}.min

# [ABC087B - Coins]
p [*0..gets.to_i].product([*0..gets.to_i],[*0..gets.to_i],[gets.to_i]).count{|x|
  x[0]*500+x[1]*100+x[2]*50==x[3]
}

# [ABC083B - Some Sums]
p (1..gets.split.map(&:to_i).first).select{|x|
  x.to_s.chars.map(&:to_i).inject(:+).between?(*($_.split.map(&:to_i).drop(1)))
}.inject(:+)

# [ABC088B - Card Game for Two]
p $<.to_a.last.split.map(&:to_i).sort.reverse.map.with_index{|x,i|i%2==0?x:-x}.inject(:+)

# [ABC085B - Kagami Mochi]
p $<.to_a.drop(1).map(&:to_i).uniq.size

# [ABC085C - Otoshidama]
puts ([[-1,-1,-1]]+[*0..2000].product([*0..2000],[gets.split.map(&:to_i)]).select{|x|
  x[0]+x[1]<=x[2][0] && (x[2][1]-x[0]*10000-x[1]*5000)/1000==x[2][0]-x[0]-x[1]
}.map{|x|
  [x[0],x[1],x[2][0]-x[0]-x[1]]
}).last.join(" ")

# [ABC049C - Daydream]
puts gets.chomp.match(/^(dream|dreamer|erase|eraser)*$/) ? "YES" : "NO"

# [ABC086C - Traveling]
puts ([[0,0,0]]+$<.to_a.drop(1).map{|x|x.split.map(&:to_i)}).each_cons(2).all?{|x,y|
  (y[1]-x[1]).abs+(y[2]-x[2]).abs<=y[0]-x[0] && (y[1]-x[1]+y[2]-x[2]+y[0]-x[0]).even?
} ? "Yes" : "No"
```

## [ABC086A - Product](https://atcoder.jp/contests/abs/tasks/abc086_a)


```ruby
puts gets.split.map(&:to_i).none?(&:even?) ? "Odd" : "Even"
```


`gets.split.map(&:to_i)`で入力された整数の配列を得ることができます。
この中に偶数がひとつも含まれていなければ`Odd`、そうでなければ`Even`を出力します。


## [ABC081A - Placing Marbles](https://atcoder.jp/contests/abs/tasks/abc081_a)

```ruby
p gets.chomp.count("1")
```

入力された文字列に含まれる`1`の数を出力します。
ここでは`chomp`（末尾の改行を削除）はしなくてもACしますが、一応。

## [ABC081B - Shift Only](https://atcoder.jp/contests/abs/tasks/abc081_b)

```ruby
p $<.to_a.last.split.map{|a|a.to_i.to_s(2).reverse.index("1")}.min
```


`$<`は標準入力を改行区切りの一括で取得できるオブジェクトです。

これを`to_a`で配列に変換し、
1行目は不要のため`last`で2行目を取得し、
`split`で空白区切りの配列とし、
`map`でその各要素について以下の処理で置き換えます。
　`to_i`で整数に変換し、
　`to_s(2)`で2進数の文字列に変換し、
　`reverse`で反転し、
　`index("1")`で前から見て最初に`1`が現れる位置を得る。

結局こうすると、各整数について2進数で表した時に「後ろから0が何文字連続しているか」つまり「何回2で割れるか」を求めることができるため、その`min`を取って出力すればよいです。

## [ABC087B - Coins](https://atcoder.jp/contests/abs/tasks/abc087_b)
```ruby
p [*0..gets.to_i].product([*0..gets.to_i],[*0..gets.to_i],[gets.to_i]).count{|x|
  x[0]*500+x[1]*100+x[2]*50==x[3]
}
```

`product`は複数の配列の直積を得るメソッドです。
`[*0..A]`と`[*0..B]`と`[*0..C]`の直積について、さらに`X`の情報も付加し（一時変数を用いて`X`を保持することが出来ないため）、問題で求められている等式が成り立っているような要素がいくつあるかを出力すればよいです。

## [ABC083B - Some Sums](https://atcoder.jp/contests/abs/tasks/abc083_b)

```ruby
p (1..gets.split.map(&:to_i).first).select{|x|
  x.to_s.chars.map(&:to_i).inject(:+).between?(*($_.split.map(&:to_i).drop(1)))
}.inject(:+)
```

`(1..N)`について、以下の条件を満たすものを全て抽出します。
　`to_s`で文字列に変換し、
　`chars`で1文字ずつの配列に切り分け、
　`map(&:to_i)`で各要素を整数に変換し、
　`inject(:+)`でその総和を求め、その値が`A`以上`B`以下である。


`between?`の引数については、`$_`で最後に`gets`した結果の文字列を取得し（レギュレーションで禁止するか微妙な所）、それを整数の配列に変換し、最初の要素（`N`）を削ったもの（`[A, B]`）を`*`で配列から展開しています。


上のようにして得られた数列の総和を出力すればよいです。

## [ABC088B - Card Game for Two](https://atcoder.jp/contests/abs/tasks/abc083_b)

```ruby
p $<.to_a.last.split.map(&:to_i).sort.reverse.map.with_index{|x,i|i%2==0?x:-x}.inject(:+)
```


`$<.to_a.last.split.map(&:to_i)`で入力の2行目の整数の配列を得ます。それを
　`sort`で昇順ソートし、
　`reverse`で逆順（降順）に変え、
　`map.with_index`で各要素について先頭から偶数番目ならそのまま、奇数番目なら符号を反転した値へ置き換えます。
この総和を出力すればよいです。


## [ABC085B - Kagami Mochi](https://atcoder.jp/contests/abs/tasks/abc085_b)

```ruby
p $<.to_a.drop(1).map(&:to_i).uniq.size
```

入力の2行目以降の整数の配列について、ダブっているものを取り除いた後の個数を出力すればよいです。

## [ABC085C - Otoshidama](https://atcoder.jp/contests/abs/tasks/abc085_c)
```ruby
puts ([[-1,-1,-1]]+[*0..2000].product([*0..2000],[gets.split.map(&:to_i)]).select{|x|
  x[0]+x[1]<=x[2][0] && (x[2][1]-x[0]*10000-x[1]*5000)/1000==x[2][0]-x[0]-x[1]
}.map{|x|
  [x[0],x[1],x[2][0]-x[0]-x[1]]
}).last.join(" ")
```


難関です。雰囲気はCoinsに似ています。

とりあえず、使う10000円札と5000円札の枚数の候補に`[N,Y]`を付加した配列を`[*0..2000].product([*0..2000],[gets.split.map(&:to_i)])`で生成します。
そして、この中から問題の条件を満たすようなものを抽出します。
問題で求められているのは10000円札、5000円札、1000円札の各枚数なので、`map`でそのように置き換えます。

ただし、候補が1つも無い場合は`-1 -1 -1`を出力しなければなりません。
これを実現するため、先頭に`[-1,-1,-1]`というダミーの解を加えておき、最終的な結果の最後の要素を取得すると、候補が1つも無ければ`[-1,-1,-1]`が、1つ以上存在すればその有効な解の1つが得られます。
それを`join(" ")`で空白区切りの文字列に変換して出力すればよいです。

## [ABC049C - Daydream](https://atcoder.jp/contests/abs/tasks/arc065_a)

```ruby
puts gets.chomp.match(/^(dream|dreamer|erase|eraser)*$/) ? "YES" : "NO"
```

正規表現でポン。だけどちょっとずるいので正攻法の解を募集しています。中々しんどいと思います。

（2019/09/07追記）一応正攻法で通してみました。定数倍がきつく試行錯誤……

```ruby
puts gets.chomp.reverse.chars.inject(""){|s,x|
  ["maerd","remaerd","esare","resare"].include?(s+x)? "" : s+x
}.empty? ? "YES" : "NO"
```

## [ABC086C - Traveling](https://atcoder.jp/contests/abs/tasks/arc089_a)
```ruby
puts ([[0,0,0]]+$<.to_a.drop(1).map{|x|x.split.map(&:to_i)}).each_cons(2).all?{|x,y|
  (y[1]-x[1]).abs+(y[2]-x[2]).abs<=y[0]-x[0] && (y[1]-x[1]+y[2]-x[2]+y[0]-x[0]).even?
} ? "Yes" : "No"
```

`$<.to_a.drop(1).map{|x|x.split.map(&:to_i)}`で`N`個の`[t,x,y]`の配列が得られます。
この先頭に`[0,0,0]`（初期状態）を加えます。
`each_cons(2)`で隣り合う2状態ずつ（つまり求められる移動）を取得します。
`all?`を用いて、この全ての移動について可能であるかどうかを調べればよいです。


# おわりに

もっとスマートな解を募集しています。特にSome Sumsの`$_`を使わないやつとか、Otoshidamaの効率が良いやつとか。