プログラミング初めてでもできるJOI一次予選突破のための超やさしいチュートリアル
対象:プログラミング完全初心者
目的:RubyでJOI一次の問題が解けるレベルまで到達すること
注意:この記事は、一部AIを用いて書いています
0. はじめに
こんにちは。えふです。Ruby全く知らない状態からここ2日である程度勉強したので、そのまとめとしてこの記事を書きました。JOIからプログラミングに興味を持ってくださる方が少しでも多くなればいいなと思ってます。長くても3時間程度でサクッと読める記事になっております。
あと、別に記事に書いてあるコードは本番調べてコピペできるので覚えなくていいです。概念だけでOK!どんどん実装はコピペしていこう。
1. Rubyとは?&書いて動かすためのツール
Rubyは日本人のまつもとゆきひろ(Matz)さんが作った高級スクリプト言語です。
設計思想は「楽しく、自然に書ける」こと。英語に近い記述で、可読性が高いのが特徴です。
- 動的型付け:変数に型宣言が不要で、実行時に型が決まる。
- オブジェクト指向:ほとんどすべてがオブジェクト。
- 初心者に優しい:エラーメッセージがわかりやすい傾向があります。
競技プログラミング(JOI、AtCoder等)では、小〜中規模の問題は Ruby で解けます。ただし、超大規模入力や計算量が厳しい問題は C++ の方が有利です。
勉強始める前にすること
・JOI参加登録
・これに登録(本番のジャッジシステムになれる&問題解ける)
・paiza.io(ここにコードを書いて、ジャッジシステムのコード提出欄にコピペしたほうがよい)
左上の緑の枠が、Python3とかC++になってるはずなので、そこにマウスを当ててRubyを選ぶ。
2. 出力(画面に文字を出す)
puts
puts "Hello, world!"
#Hello, world!\n (\nは改行の意とする。以降必要に応じて省略する)
-
putsは文字列を表示して末尾に改行を付けます。 - また、AtCoder等では答えの出力の後に改行が求められることが多いので、できるだけ出力はprintではなくこのputsを使いましょう。
print
print "Hello"
print "World"
# => HelloWorld
-
printは改行を付けないで表示します。 - 「A B C」 のように、空白区切りで値を出力するときに使います。
p
p [1, 2, 3]
# => [1, 2, 3]
-
pはオブジェクトの表現をそのまま出力する。(デバッグ向けだしJOIではあまり使わない)
3. 変数の概念と代入
変数とは?
- 変数は「値を入れておく名前付きの入れ物」です。
- 例:
x = 5は「x に 5 を入れる」の意。まったくもって等式ではない。
a = 10 # a に 10 を代入
b = a + 5 # b に 15 を代入
4. データ型と型変換(キャスト) — なぜ必要か
主なデータ型
-
Integer(整数) -
Float(小数) -
String(文字列) -
Array(配列) -
Hash(連想配列) -
NilClass(nil)
なぜ型変換が必要か?
-
getsは文字列を返すため、数値計算をするには文字列を数値に変換(キャスト)する必要があります。 - 例:
"123"は計算できないので123にする。
※ダブルクオーテーション(" ")でくくられた時、またその時に限り文字列を表すと考えてよい。(厳密にはそうじゃないかも)
主な変換の構文
-
to_iなんか → Integer(例:"123".to_i → 123) -
to_fなんか → Float(例:"12.3".to_f → 12.3) -
to_sなんか → String(例:123.to_s → "123")
注意点:"abc".to_i は 0 になります。(先頭に数字がなければ 0)
5. 算術演算と演算の優先順位
| 演算子 | 意味 |
|---|---|
+ |
足し算 |
- |
引き算 |
* |
掛け算 |
/ |
割り算 |
% |
剰余(余り) |
** |
累乗(べき乗) |
優先順位
-
**が最優先、次に* / %、次に+ -。括弧()で順番を明示するのが安全。
puts 2 + 3 * 4 # => 14
puts (2 + 3) * 4 # => 20
練習問題
変数bにあなたの名前を代入し、Hello!!あなたの名前となるようにせよ。
名前がEffなら、Hello!!Effを出力する。
a = "Hello!!" # ハッシュタグ?(#)を使えば、「コメント」という、プログラムのメモ機能が使える
b = "あなたの名前" #ただし、行ごと対応なので、改行すると基本もう一度"#"がいる
#プログラムを追加!!
6. 配列(Array)
作り方
arr = [1, 2, 3]
empty = []
参照と添字
arr[0] # 1
arr[-1] # 一番最後の要素
- 添え字は0から始まるので注意すること
主なメソッド(計算を楽にするツール的なもの)
-
arr.sum,arr.max,arr.min,arr.sort,arr.size,arr.push(x),arr.pop
ここでは紹介だけして詳細を省略するので、ググってください。
ちな割とつかう。
便利な生成
Array.new(5, 0) # => [0,0,0,0,0]
(1..5).to_a # => [1,2,3,4,5](to_aは1-5の数列(1..5)を配列型にキャストしたものと考える)
7. 文字列(String)操作の基礎
※文字列を表すときは、ダブルクオーテーション(" ")でくくる。
基本
s = "Hello"
puts s.length #結果 5
puts s[0] #結果 H
#文字列内に"ell"を含むか
puts s.include?("ell") #結果 true
分割と結合
puts "1 2 3".split #結果 1\n 2\n 3\n
puts ["a","b"].join("-") #結果 a-b
部分文字列のカウント
puts "JOIOIOIJOI".scan("JOI").size #結果 2
8. 入力(gets)の仕組み
gets の動作
- キーボードや標準入力から 1 行分の文字列 を取得します。
- 取得される文字列は末尾に改行
\nが含まれます(例:"123\n")。
chomp の役割
-
chompは文字列の末尾にある改行\nを取り除きます。
s = gets.chomp
to_i の役割(なぜ使うか)
-
getsは文字列を返すため、数値として計算したい場合はto_iで整数に変換する必要があります。
n = gets.to_i
この一行は、
-
getsで入力(文字列)を受け取り、 -
to_iで整数に変換して、 - 変数
nに代入する、という意味です。
例えば文字列を受け取りたかったら、
s = gets.chomp.to_s
というようにやります。
競技プログラミングでは、文字列として入力した後にキャストをして、
/# 各行に対して一つの変数のみ入力
---入力例--------------------
A
B
-----------------------------
#/
a = gets.to_s.to_i
/# 入力が a bのように空白区切りになっている
---入力例--------------------
A B
-----------------------------
#/
a, b = gets.to_s.chomp.split.map{|t| t.to_i }
/# 配列を入力
---入力例--------------------
A_0 A_1 A_2 ・・・ A_n
-----------------------------
#/
a = gets.to_s.chomp.split.map{|t| t.to_i }
t = gets.to_s #文字列を入力
という風にするのが基本ですね。
これがわかれば、AIが勝手に作った9章は飛ばしていいですよ。
9. 複数入力の取り扱い(split と map)
1 行に複数の数がある場合
a, b, c = gets.split.map(&:to_i)
-
getsが"3 5 7\n"を返す -
splitが["3","5","7"]にする -
map(&:to_i)で[3,5,7]に変換 - 左辺でそれぞれの変数に割り当てる
複数行に分かれている場合(N 行)
n = gets.to_i
arr = n.times.map { gets.to_i }
-
n.times.mapは n 回ブロックを実行して配列を返す
10. 条件分岐(if / elsif / else)とbool
情報Ⅰでやる疑似コードにかなり似てる。初心者向けにこの言語を選んだ決め手でもある。
x = gets.to_i
if x > 10
puts "big"
elsif x == 10
puts "equal"
else
puts "small"
end
- 比較演算子:
==,!=,>,<,>=,<= - 論理演算子:
&&(and),||(or),!(not)
ここまででJOI一次予選の前半二問が解けます!
練習問題(AtCoderに登録してネ♡)
リンク先の問題を解こう!!
・JOI2025一次予選第一回A
・JOI2025一次予選第二回A
・JOI2025一次予選第一回B
・JOI2025一次予選第二回B
11. 繰り返し(each / for / times)
each(配列に対する処理)
[1, 2, 3].each do |x|
puts x * 2
end
for(汎用的な繰り返し処理)
for i in 1..5
puts i
end
times(定数回くり返す)
この時変数iは、0, 1, 2, 3, 4というようになる。5にはならない。
5.times do |i|
puts i
end
12. 考え方のテンプレ
- 問題を日本語で一文に要約する(何を求める?)
- 入力形式を確認(1 行?複数行?)
- 例を手で計算してみる(小さなケース)
- 計算の 順番(アルゴリズム) を決める(ループ or シミュレーション)
- まずは 正しいが遅い実装 を書いて、動作を確認
- 必要なら 最適化(計算量を下げる)する
13. JOI 一次 A / B レベル 例題
A1 — 3 つの数の平均(整数)
入力(1 行)
a b c
考え方:3 つを足して 3 で割るだけ
解答
a, b, c = gets.split.map(&:to_i)
puts (a + b + c) / 3
注意:もし小数で答える必要がある場合は to_f を使う。
やってみればわかるように、整数を整数で割ったら、答えに切り捨ての値がでてきます。
B1 — 平均より上の人数
入力
N
x1 x2 ... xN
考え方:平均を計算して、それより大きい要素を数える
解答
n = gets.to_i
arr = gets.split.map(&:to_i)
avg = arr.sum / n.to_f
puts arr.count { |x| x > avg }
B2 — 文字列中の "JOI" の数
入力
S
解答
s = gets.chomp
puts s.scan("JOI").size
14. JOI 一次 C / D レベル 例題
C1 — 隣り合う 2 個の和の最大
入力
N
a1 a2 ... aN
戦略:連続するペアを順に比較し最大を取る (線形時間 O(N))
解答
n = gets.to_i
arr = gets.split.map(&:to_i)
max_sum = -1 << 60
(0...(n-1)).each do |i|
max_sum = [max_sum, arr[i] + arr[i+1]].max
end
puts max_sum
15. 高速入出力(大量データの処理)
大量の入力がある場合、gets を何度も呼ぶより STDIN.read で一括読み込みが速いです。
nums = STDIN.read.split.map!(&:to_i)
i = 0
n = nums[i]; i += 1
arr = nums[i, n]; i += n
# 残りの処理を nums[i] から利用する
注意:全データをメモリに読み込むので、非常に大きい入力では注意が必要。
ここまででJOI一次予選の後半二問が解けます!
練習問題(AtCoderに登録してネ♡)
リンク先の問題を解こう!!
・JOI2025一次予選第一回C
・JOI2025一次予選第二回C
・JOI2025一次予選第一回D(かなり難しい。入力はeachを使いながら)
・JOI2025一次予選第二回D(ちょっと難しい)
まぁJOI一次予選はA~Cが解ければ通るので、最悪D解けなくてもいい。
16. デバッグの基本テクニック
-
pを使って変数の中身を表示(提出前に消す) - 小さな入力例(手計算できるサイズ)で動作確認
- エラーメッセージをよく読む(Ruby のエラーは比較的親切)
17. よくある落とし穴と対処法
-
getsを忘れてnilを操作してしまう → 入力の読み忘れに注意 -
gets.to_iが 0 になってしまう → 入力が数値か確認(chompやstripの活用) - 配列のインデックスを 1 始まりで考えてしまう → Ruby は 0 始まり
-
splitの使い方(改行や空白を想定どおりに処理できない場合) → 例を手で試す
18. おすすめ勉強法
- JOI 公式サイト(過去問)
- AtCoder(ABC) — Ruby での練習に最適