競技プログラミングで可読性を高めたい
- 評価
- クリップ 1
- VIEW 2,032

退会済みユーザー
前提・実現したいこと
競技系プログラミングで提出するコードの量を減らして可読性を高めたい(以下兢プロ)。
言語はRuby、趣味で兢プロをやっています。
競プロでは一度に複数の値を扱うことがよくあります。
例えばn行の数字がスペース区切りで10個与えられ、それらを条件によって違う処理をする時などです。
n = gets.chomp.to_i
number = []
n.times do
number << gets.chomp.split(/\n/).map(&:to_i)
end
のように多重配列として受けて
条件が1つや2つなら
number.each_slice(1) do |num|
if 略
end
end
のように行っています。
しかし、これが上記のifの結果を使用して、そしてその結果もまた使用する。といった事があります。
そのような場合は
上で得た結果を配列に入れ(仮にsample_array)
sample_array.each_slice(1) do |num|
.
.
.
end
や、ものによってはeachを2重3重したコードを書いています。
さらにまたその結果を・・・と合計で4回ほど行う時があり、辟易してしまいます。
知りたいこと
このやりかたですと、毎回num[0]と何々をやったり、文字と数字が混ざったものが与えられ、文字が◯なら文字以下の数字をどの条件に従って計算する。といったものが出るのでその都度deleteしてから数字にして計算。そして結果は都度配列に、といったふうに冗長なコードになってしまい美しくありません。
n = gets.chomp.to_i
n.times do
gets.chomp.split(/\n/).map(&:to_i)
できたらココの時点で
def ~~(, , ,)
のようなメソッドで数値が入力されるごとにすぐ計算。そしてその結果は配列に入れておくなりして次の数字が入力される頃には引数が初期値に戻り、繰り返し。
のような再帰関数のような処理で行えないものかと思い質問させていただきました。
もしかしたらifなどのネストでやるべきかとも思いますが方法がありましたらぜひお願いします。
理解に努めますが、Ruby以外分からないのでその際は言葉で説明して頂けると助かります。
よろしくお願いいたします。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+3
もし、単純に「コード量を減らす」事が目的であれば、いわゆるコードゴルフというもので、いろいろなテクニックがあります。yukicoderでは正解(AC)になった回答を言語別にコードの長さ順で並べ替えられるので、他の人のテクニックを見ると良いでしょう。1バイト縮めるだけに全く違うアプローチを使うとか結構面白いです。ただ、一概にこうすると良いというのがないというのがあります。
そうではなく、可読性を上げたいというのであれば、次のように書くことをオススメします。
- 始めに入力部分だけで完結させる。
- 入力値から出力値を求める。
- 最後は単に出力する。
例えば先ほどのyukicoderのNo.13 囲みたい!の問題を見てみましょう。入力の規則は下記のようになっています。
1行目には 横を表すW (1≤W≤100)と縦を表すH (1≤H≤100)数値が半角スペース区切りで与えられる。
2行目以降には、各行i列目j行目を表す数値Mij (1≤Mij≤1000)Mijが半角スペース区切りで与えられる。
w, h = gets.split.map(&:to_i)
m = h.times.map { gets.split.map(&:to_i) }
これで、Mijがm[j][i]
としてアクセス可能です。このあと、ゆっくり処理を考えます。
競プロでは入力に対して、処理の方が大変大きいです。その場合、入力をまとめて始めにやった方がわかりやすいですし、速度が不利になると言うことはありません。大量のログ解析やWebアクセスなどデータが大量にあり、入力に待ちが発生しやすい場合は、マルチスレッドにして各行を並列に読み込んで処理して、いらなくなったデータをどんどん捨てていく方が有利になりますが、入力が小さいときは無視できるような違いしかありません(かといって、別の所では必要になるテクニックではありますが)。
例題の場合はこうなります。
※ 2.4.0で追加されたArray#sum
を使っているので、2.4.0以降でないと動きません。
# frozen_string_literal: true
# 入力処理
n = gets.to_i
students = Array.new(n) { gets.split }
.map { |field, *scores| [-field, scores.map(&:to_i)] }
# 結果作成
results = students.map do |field, scores|
next false if scores.sum < 350
case field
when 'so'
scores[0] + scores[4] >= 150
when 'sc'
scores[1] + scores[3] >= 150
end
end
# 出力処理
students.zip(results).each do |student, result|
if result
case student.first
when 'so'
puts '文系クラス合格'
when 'sc'
puts '理系クラス合格'
end
else
puts '残念でした'
end
end
入力の時点で文系か理系かのところと各スコアを分離します。各スコアは数値に変換しておきます。文系か理系かはシンボルにしても良いかもしれません(でも、freezeしているから速度は違わないかな)。結果を求める処理では合格判定だけを結果として得ます。何を出力するかはその後です。最後に結果に基づき出力します。
競プロからは離れてしまいますが、本格的にやるにはクラスを作った方がRubyらしくなります。競プロはアルゴリズムしか重要視されませんが、本当に可読性を高めようと思うなら、細かくクラスやメソッドにわけることが重要になります。もうひとつ重要な考えとしてオブジェクトをmutableとimmutableのどちらで扱うかです。上記コードはimmutableで書いていますが、オブジェクトの生成にはコストがのしかかってくるため、処理時間がシビアな問題ではきつくなる場合があります。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
一般論としては、1行分の読み込み文字列ごとに各フィールドを集計加工するための関数を用意するとよいでしょう。そして、その集計は関数の中で行うか、インスタンス変数を介して関数を呼び出した後で行うか、分かりやすい方を選べばよいでしょう。
また、順番に処理する必要がないなら、条件ごとにフィルタリングし処理しつつ複数回データを読み込む方法もあります。処理速度は余計かかりますが、特別な処理だけを先に済ませた方が後のプログラムがすっきりすることがあります。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.22%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
ozwk
2017/03/04 10:27
適当な(公開して構わない)問題と改善したい回答を例として出すとより状況がつかめると思います
Zuishin
2017/03/04 10:28
今ひとつイメージが掴めません。お題次第で方法は変わると思います。一つ具体的なお題を出して、それを解いてもらったらいかがでしょう?
退会済みユーザー
2017/03/04 19:14
ご意見ありがとうございます。可読性を高めたいと思ったものがpaizaというサイトにいくつかあり、こちらは公開してはいけないものなので他のサイトで見つかれば追記します。
Zuishin
2017/03/04 19:33
競技するわけじゃないんで、実際に使われたものでなくてもいいんじゃないですか? 例えばスペース区切りで数字が並んだ文字列が複数提供される時、各行の数字を行毎に積算したものの総和を求めよう、とか。
Zuishin
2017/03/04 19:34
あるいは生徒の各教科の得点が入力されるとき、特定の生徒の偏差値を求めよう、とか
Zuishin
2017/03/05 13:37
問題を追記して頂きましたが、どこかで見たようなと思ったら paiza ですね。https://teratail.com/questions/68027 規約違反ですので、私が言うのも何ですが削除お願いします。
退会済みユーザー
2017/03/05 21:08
ご指摘ありがとうございました。