質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.97%

競技プログラミングで可読性を高めたい

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,457
退会済みユーザー

退会済みユーザー

前提・実現したいこと

競技系プログラミングで提出するコードの量を減らして可読性を高めたい(以下兢プロ)。
言語は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|
 ifend 
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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • Zuishin

    2017/03/04 19:34

    あるいは生徒の各教科の得点が入力されるとき、特定の生徒の偏差値を求めよう、とか

    キャンセル

  • Zuishin

    2017/03/05 13:37

    問題を追記して頂きましたが、どこかで見たようなと思ったら paiza ですね。https://teratail.com/questions/68027 規約違反ですので、私が言うのも何ですが削除お願いします。

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2017/03/05 21:08

    ご指摘ありがとうございました。

    キャンセル

回答 2

checkベストアンサー

+3

もし、単純に「コード量を減らす」事が目的であれば、いわゆるコードゴルフというもので、いろいろなテクニックがあります。yukicoderでは正解(AC)になった回答を言語別にコードの長さ順で並べ替えられるので、他の人のテクニックを見ると良いでしょう。1バイト縮めるだけに全く違うアプローチを使うとか結構面白いです。ただ、一概にこうすると良いというのがないというのがあります。

そうではなく、可読性を上げたいというのであれば、次のように書くことをオススメします。

  1. 始めに入力部分だけで完結させる。
  2. 入力値から出力値を求める。
  3. 最後は単に出力する。

例えば先ほどの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で書いていますが、オブジェクトの生成にはコストがのしかかってくるため、処理時間がシビアな問題ではきつくなる場合があります。 

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/04 11:54

    タイトルの「競技プログラミングで記述量を減らしたい」を見て、ゴルフ的なものを連想したのですが、どうやら違ったみたいですね。

    キャンセル

  • 2017/03/04 19:19

    ご回答ありがとうございます。
    勘違いさせてしまい申し訳ありません。

    応用性の高いテクニックだと思いますので今後解いていくときに積極的に使っていこうと思います。

    ありがとうございます。

    キャンセル

  • 2017/03/05 21:28

    ありがとうございます。
    私の環境は2.3.3ですのでArray.sumについてはinjectを用いたら動くようになりました。

    オブジェクトを変更可能にするか否かの考えは、無意識にやっていましたので今後意識しながら考えていきたいと思います。

    アルゴリズムや速度も大切だとは思いますが、読みやすさを求めていたので、教えてくださったやり方がとてもためになります。

    ありがとうございました。

    キャンセル

0

一般論としては、1行分の読み込み文字列ごとに各フィールドを集計加工するための関数を用意するとよいでしょう。そして、その集計は関数の中で行うか、インスタンス変数を介して関数を呼び出した後で行うか、分かりやすい方を選べばよいでしょう。
また、順番に処理する必要がないなら、条件ごとにフィルタリングし処理しつつ複数回データを読み込む方法もあります。処理速度は余計かかりますが、特別な処理だけを先に済ませた方が後のプログラムがすっきりすることがあります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.97%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る