LoginSignup
1
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-09-07

はじめに

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

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

レギュレーション

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

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

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

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

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

全体のコード

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

# [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

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

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

ABC081A - Placing Marbles

p gets.chomp.count("1")

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

ABC081B - Shift Only

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

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

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

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

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

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

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(" ")

難関です。雰囲気は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

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

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

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

puts gets.chomp.reverse.chars.inject(""){|s,x|
  ["maerd","remaerd","esare","resare"].include?(s+x)? "" : s+x
}.empty? ? "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"

$<.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の効率が良いやつとか。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0