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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

解決済

2回答

2950閲覧

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

退会済みユーザー

退会済みユーザー

総合スコア0

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

1クリップ

投稿2017/03/04 01:12

編集2017/03/05 12:04

###前提・実現したいこと
競技系プログラミングで提出するコードの量を減らして可読性を高めたい(以下兢プロ)。
言語はRuby、趣味で兢プロをやっています。

競プロでは一度に複数の値を扱うことがよくあります。
例えばn行の数字がスペース区切りで10個与えられ、それらを条件によって違う処理をする時などです。

Ruby

1n = gets.chomp.to_i 2number = [] 3n.times do 4 number << gets.chomp.split(/\n/).map(&:to_i) 5end

のように多重配列として受けて

条件が1つや2つなら

Ruby

1number.each_slice(1) do |num| 2 if3end 4end

のように行っています。
しかし、これが上記のifの結果を使用して、そしてその結果もまた使用する。といった事があります。

#そのような場合は

Ruby

1上で得た結果を配列に入れ(仮にsample_array) 2 3sample_array.each_slice(1) do |num| 4. 5. 6. 7end

や、ものによってはeachを2重3重したコードを書いています。
さらにまたその結果を・・・と合計で4回ほど行う時があり、辟易してしまいます。

###知りたいこと
このやりかたですと、毎回num[0]と何々をやったり、文字と数字が混ざったものが与えられ、文字が◯なら文字以下の数字をどの条件に従って計算する。といったものが出るのでその都度deleteしてから数字にして計算。そして結果は都度配列に、といったふうに冗長なコードになってしまい美しくありません。

Ruby

1n = gets.chomp.to_i 2n.times do 3 gets.chomp.split(/\n/).map(&:to_i) 4できたらココの時点で 5def ~~(, , ,) 6``` 7のようなメソッドで数値が入力されるごとにすぐ計算。そしてその結果は配列に入れておくなりして次の数字が入力される頃には引数が初期値に戻り、繰り返し。 8のような再帰関数のような処理で行えないものかと思い質問させていただきました。 9 10もしかしたらifなどのネストでやるべきかとも思いますが方法がありましたらぜひお願いします。 11 12理解に努めますが、Ruby以外分からないのでその際は言葉で説明して頂けると助かります。 13 14よろしくお願いいたします。 15 16# 17#

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ozwk

2017/03/04 01:27

適当な(公開して構わない)問題と改善したい回答を例として出すとより状況がつかめると思います
Zuishin

2017/03/04 01:28

今ひとつイメージが掴めません。お題次第で方法は変わると思います。一つ具体的なお題を出して、それを解いてもらったらいかがでしょう?
退会済みユーザー

退会済みユーザー

2017/03/04 10:14

ご意見ありがとうございます。可読性を高めたいと思ったものがpaizaというサイトにいくつかあり、こちらは公開してはいけないものなので他のサイトで見つかれば追記します。
Zuishin

2017/03/04 10:33

競技するわけじゃないんで、実際に使われたものでなくてもいいんじゃないですか? 例えばスペース区切りで数字が並んだ文字列が複数提供される時、各行の数字を行毎に積算したものの総和を求めよう、とか。
Zuishin

2017/03/04 10:34

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

2017/03/05 04:37

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

退会済みユーザー

2017/03/05 12:08

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

回答2

0

ベストアンサー

もし、単純に「コード量を減らす」事が目的であれば、いわゆるコードゴルフというもので、いろいろなテクニックがあります。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が半角スペース区切りで与えられる。

Ruby

1w, h = gets.split.map(&:to_i) 2m = h.times.map { gets.split.map(&:to_i) }

これで、Mijがm[j][i]としてアクセス可能です。このあと、ゆっくり処理を考えます。

競プロでは入力に対して、処理の方が大変大きいです。その場合、入力をまとめて始めにやった方がわかりやすいですし、速度が不利になると言うことはありません。大量のログ解析やWebアクセスなどデータが大量にあり、入力に待ちが発生しやすい場合は、マルチスレッドにして各行を並列に読み込んで処理して、いらなくなったデータをどんどん捨てていく方が有利になりますが、入力が小さいときは無視できるような違いしかありません(かといって、別の所では必要になるテクニックではありますが)。


例題の場合はこうなります。
※ 2.4.0で追加されたArray#sumを使っているので、2.4.0以降でないと動きません。

Ruby

1# frozen_string_literal: true 2# 入力処理 3n = gets.to_i 4students = Array.new(n) { gets.split } 5 .map { |field, *scores| [-field, scores.map(&:to_i)] } 6# 結果作成 7results = students.map do |field, scores| 8 next false if scores.sum < 350 9 case field 10 when 'so' 11 scores[0] + scores[4] >= 150 12 when 'sc' 13 scores[1] + scores[3] >= 150 14 end 15end 16# 出力処理 17students.zip(results).each do |student, result| 18 if result 19 case student.first 20 when 'so' 21 puts '文系クラス合格' 22 when 'sc' 23 puts '理系クラス合格' 24 end 25 else 26 puts '残念でした' 27 end 28end

入力の時点で文系か理系かのところと各スコアを分離します。各スコアは数値に変換しておきます。文系か理系かはシンボルにしても良いかもしれません(でも、freezeしているから速度は違わないかな)。結果を求める処理では合格判定だけを結果として得ます。何を出力するかはその後です。最後に結果に基づき出力します。

競プロからは離れてしまいますが、本格的にやるにはクラスを作った方がRubyらしくなります。競プロはアルゴリズムしか重要視されませんが、本当に可読性を高めようと思うなら、細かくクラスやメソッドにわけることが重要になります。もうひとつ重要な考えとしてオブジェクトをmutableとimmutableのどちらで扱うかです。上記コードはimmutableで書いていますが、オブジェクトの生成にはコストがのしかかってくるため、処理時間がシビアな問題ではきつくなる場合があります。 

投稿2017/03/04 02:03

編集2017/03/04 22:26
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2017/03/04 02:54

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

退会済みユーザー

2017/03/04 10:19

ご回答ありがとうございます。 勘違いさせてしまい申し訳ありません。 応用性の高いテクニックだと思いますので今後解いていくときに積極的に使っていこうと思います。 ありがとうございます。
退会済みユーザー

退会済みユーザー

2017/03/05 12:28

ありがとうございます。 私の環境は2.3.3ですのでArray.sumについてはinjectを用いたら動くようになりました。 オブジェクトを変更可能にするか否かの考えは、無意識にやっていましたので今後意識しながら考えていきたいと思います。 アルゴリズムや速度も大切だとは思いますが、読みやすさを求めていたので、教えてくださったやり方がとてもためになります。 ありがとうございました。
guest

0

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

投稿2017/03/04 01:39

seastar3

総合スコア2285

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問