はじめに
Rubyの1式のメソッドチェーンだけで以下の記事の10問を解いてみました。
https://qiita.com/drken/items/fd4e5e3630d0f5859067
問題を解くアルゴリズム自体には焦点を当てていないので、「どうしてそれが答えになるの?」となったら他の解説を読んでください。
レギュレーション
特に厳密に定めている訳ではないのですが、大体以下のようなレギュレーションがあります。
一時変数を使ってはいけない
ブロック変数(a.map{|x|x+1}
のx
など)や特殊変数($<
など)を除いて、自分で一時変数を置いたりしてはいけません。
複数の式を並べてはいけない
改行やセミコロンを用いて複数の式を並べてはいけません。
全体のコード
最初に10問全体のコードを載せておきます。後からそれぞれについて軽く解説します。
全てがp
やputs
の出力式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の効率が良いやつとか。