paiza プログラミング

[Ruby]paiza Bランクレベルアップメニュー 文字と整数の組のソート2 (paizaランク C 相当)の解説

B-lvup_文字と整数の組のソート2

こんにちは!じゃいごテックのあつしです。

今回はpaiza Bランクレベルアップメニュー から文字と整数の組のソート2という問題を解説します。

この問題集は項目と値がペアになっているデータの集計とソートに関する6個のSTEP問題(D級)FINAL問題(C級)で構成されていて、STEP問題を解いて行けばFINAL問題も解けるはず!という構成となっています。

STEP問題を解いてみる

STEP問題は基本的なメソッドについての問題です。
簡単な解説は付けていますが、難しいと感じたら下記の記事も参考にしてみて下さい。

※過去に出題された小問題が出てくることがありますが、チケットに余裕があれば復習のつもりで取り組んでみましょう。

STEP1: インクリメント (paizaランク D 相当)

STEP1 は受け取った整数をインクリメント(+1)して出力する問題です。

解答例

<< EOS

入力例1
-10
出力例1
-9
入力例2
0
出力例2
1
入力例3
1
出力例3
2

EOS

n = gets.to_i
puts n + 1

解答例: 標準入力で受けとった文字列を整数型に変換、+1 して出力しています。


STEP2: 重複の判定 (paizaランク D 相当)

STEP2 は指定された配列["HND", "NRT", "KIX", "NGO", "NGO"]の要素が重複しているかを調べる問題です。

解答例

ary = ["HND", "NRT", "KIX", "NGO", "NGO"]

# [解答例1]
len = ary.length
duplicate = false
(0..len - 1).each do |i|
  (i + 1..len - 1).each do |j|
    if ary[i] == ary[j]
      duplicate = true
      break
    end
  end
  break if duplicate
end

if duplicate
  puts "true"
else
  puts "false"
end

# [解答例2]
hash = ary.group_by(&:itself).map { |key, val|
  [key, val.length]
}.to_h
puts hash.values.max > 1 ? "true" : "false"

# [解答例3]
puts ary.tally.values.max > 1 ? "true" : "false"

解答例1: 配列の先頭要素から順に、続く要素と同じものがあるかを判定して、同じものがあればループを抜ける処理を行なっています。

解答例2: group_byメソッドで要素ごとに配列をまとめたハッシュを作成し、値の配列の中で最大値を調べています。(解答例3と合わせるために要素を集計したハッシュに戻しています。)

解答例3: tallyメソッドで要素ごと集計したハッシュを作成し、値の配列の中で最大値を調べています。


STEP3: 配列(リスト)の重複カウント (paizaランク D 相当)

STEP3 は指定された配列["HND", "NRT", "KIX", "NGO", "NGO", "NGO", "NGO", "NGO"]の重複している要素を数える問題です。
※ 重複している要素は1種類であることがわかっています。

解答例

# [解答例1]
len = ary.length
count = 1
(0..len - 1).each do |i|
  (i + 1..len - 1).each do |j|
    if ary[i] == ary[j]
      count += 1
    end
  end
  break if count > 1
end

puts count

# [解答例2]
sorted_ary = ary.group_by(&:itself).map { |k, v|
  [k, v.length]
}.sort_by { |k, v| v }.reverse
p sorted_ary[0][1]

# [解答例3]
sorted_ary = ary.tally.sort_by { |k, v| v }.reverse
puts sorted_ary[0][1]

解答例1: 配列の先頭要素から順に、続く要素と同じものがあるかを判定して、同じものがあればカウントアップしていき、カウントアップ終了後ループを抜けて出力しています。

解答例2: group_byメソッドmapメソッドで要素の出現回数を集計した配列を作って、sort_byメソッドreverseメソッドで出現回数が多い順(降順)で並び替えて先頭の2番目の要素(最大の出現回数)を出力しています。

解答例3: tallyメソッドで要素ごと集計したハッシュを作成し、sort_byメソッドreverseメソッドで出現回数が多い順(降順)で並び替えて先頭の2番目の要素(最大の出現回数)を出力しています。


STEP4: 配列のソート (paizaランク D 相当)

STEP4 は指定された配列[1, 3, 5, 6, 3, 2, 5, 23, 2]を昇順に並び替えて出力する問題です。

解答例

ary = [1, 3, 5, 6, 3, 2, 5, 23, 2]

# [解答例1]
sorted_ary = []
ary.each do |t_n|
  inserted = false
  sorted_ary.each_with_index do |a_n, idx|
    if t_n < a_n
      sorted_ary.insert(idx, t_n)
      inserted = true
      break
    end
  end
  sorted_ary << t_n if not inserted
end
puts sorted_ary

# [解答例2]
puts ary.sort

解答例1: 配列aryの先頭から順に値を取り出し、sorted_aryの値と比較して昇順になるようにsorted_aryに挿入しています。
※ 解説のために自前でソートを実装しましたが、通常はsortメソッドを使った方が楽なうえに処理も早いです。

解答例2: sortメソッドで昇順の配列を生成して出力しています。


STEP5: 数字のみの出力 (paizaランク D 相当)

STEP5 は複数の半角スペース区切りの文字と整数のペアが渡され、整数だけを先頭から順に出力する問題です。

解答例

<<"EOS"

4
S 1
F 2
E 5
Y 6

出力例1
1
2
5
6

入力例2
3
S -3
G -5
R -35

出力例2
-3
-5
-35

入力例3
6
A 12
E 42
G -8
R -7
Y 56
E 22

出力例3
12
42
-8
-7
56
22

EOS

# [解答例1]
n = gets.to_i
ary = []
n.times do
  s, d = gets.split
  ary.push([s, d.to_i])
end
ary.each do |s, d|
  puts d
end

# [解答例2]
n = gets.to_i
hash = {}
n.times do
  s, d = gets.split
  hash[s] = d.to_i
end
puts hash.values

解答例1: 文字と数字のペアを配列で受け取って、二次元配列の先頭から順に2番目の要素を出力しています。

解答例2: 文字と数字のペアを配列で受け取り、文字をキー、数字を値にしたハッシュに格納して、valuesメソッドで値の配列を取得して出力しています。


STEP6: 昇順ソート出力 (paizaランク D 相当)

STEP6 は半角スペース区切りの整数を受け取り、昇順に並べ替えて出力する問題です。

解答例

<<"EOS"

入力例1
8
90 777 8888 121 333 4 29 2

出力例1
2
4
29
90
121
333
777
8888

入力例2
10
10 9 8 7 6 5 4 3 2 1

出力例2
1
2
3
4
5
6
7
8
9
10

入力例3
1
3

出力例3
3

EOS

# [解答例1]
n = gets.to_i
ary = gets.split.map(&:to_i)

sorted_ary = []
ary.each do |t_n|
  inserted = false
  sorted_ary.each_with_index do |a_n, idx|
    if t_n < a_n
      sorted_ary.insert(idx, t_n)
      inserted = true
      break
    end
  end
  sorted_ary << t_n if not inserted
end
puts sorted_ary

# [解答例2]
n = gets.to_i
ary = gets.split.map(&:to_i)

puts ary.sort

解答例1: 配列aryの先頭から順に値を取り出し、sorted_aryの値と比較して昇順になるようにsorted_aryに挿入しています。
※ 解説のために自前でソートを実装しましたが、通常はsortメソッドを使った方が楽なうえに処理も早いです。

解答例2: sortメソッドで昇順の配列を生成して出力しています。


文字と整数の組のソート2 (paizaランク C 相当)を解いてみる

※ paiza Bランクレベルアップメニューより

問題

1行目に行数を表す整数 n、続く n 行の各行で「文字」と「整数」の組が空白区切りで入力されます。
n 個の組を、「整数」の値で昇順に並べ変え、「文字」を出力してください。

入力される値

入力は以下のフォーマットで与えられます。

n
S_1 D_1
S_2 D_2
...
S_i D_i
...
S_n D_n

S_i は「文字」で、D_i は「整数」です。
入力される文字 S_i は、同じ文字が複数入力されることはなく、整数 D_i も同様です。
入力値最終行の末尾に改行が1つ入ります。

期待する出力

n 個の組を、「整数」の値で昇順に並べ変え、「文字列」を1行ずつ出力してください。

条件

すべてのテストケースにおいて、以下の条件をみたします。

  • 1 ≦ n ≦ 100
  • -10,000 ≦ D_i ≦ 10,000
  • S_iは半角英文字1文字
  • iとjが異なるなら、D_iとD_jは異なる
  • iとjが異なるなら、S_iとS_jは異なる
入力例1
2
A 1
B 2
出力例1
A
B
入力例2
3
G 0
S 3
E -2
出力例2
E
G
S
入力例3
5
H -2
R 0
W -5
A -1
E -20
出力例3
E
W
H
A
R
攻略ポイント

ポイント

  • 未登録の文字列(キー)なら値とセットで保持し、登録済みの文字(キー)なら整数の値を足し合わせる
  • sort_byメソッドで二次元配列(又はハッシュ)の降順ソートを行う
  • 二次元配列(又はハッシュ)の要素を半角スペースで連結して出力する

デバッグを楽にするためにメソッドを作成します。メソッドについては下記の記事も参考にしてみて下さい。

問題を解く流れ

入出力例をコピペしてヒアドキュメントで変数に代入し、データを確認します。正しく受け取れていれば確認用コードは削除します。

INPUT1 = <<~"EOS"
  7
  A 1
  D 6
  C 2
  G 4
  B 70
  A 10
  B 5
EOS
OUTPUT1 = <<~"EOS"
  B 75
  A 11
  D 6
  G 4
  C 2
EOS

INPUT2 = <<~"EOS"
  3
  G 0
  S 3
  E -2
EOS
OUTPUT2 = <<~"EOS"
  S 3
  G 0
  E -2
EOS

INPUT3 = <<~"EOS"
  5
  A -2
  E 0
  W -5
  A -1
  E -20
EOS
OUTPUT3 = <<~"EOS"
  A -3
  W -5
  E -20
EOS

p INPUT1
# > "7\nA 1\nD 6\nC 2\nG 4\nB 70\nA 10\nB 5\n"
p OUTPUT1
# > "B 75\nA 11\nD 6\nG 4\nC 2\n"
p INPUT2
# > "3\nG 0\nS 3\nE -2\n"
p OUTPUT2
# > "S 3\nG 0\nE -2\n"
p INPUT3
# > "5\nA -2\nE 0\nW -5\nA -1\nE -20\n"
p OUTPUT3
# > "A -3\nW -5\nE -20\n"

続いて問題を解くメソッド( solve メソッドとします)を変数の下に定義します。

まず、入力データを受け取る処理を書きます。

def solve(input_lines)
  # 入力データ受け取り
  _, *lines = input_lines.split("\n")

  # 確認用コード
  lines
end

p solve(INPUT1)
# > ["A 1", "D 6", "C 2", "G 4", "B 70", "A 10", "B 5"]
p solve(INPUT2)
# > ["G 0", "S 3", "E -2"]
p solve(INPUT3)
# > ["A -2", "E 0", "W -5", "A -1", "E -20"]

データが正しく受け取れていれば、solve メソッドに文字と数字のペアを集計して数字で降順ソートする処理を追加していきます。

  • 文字と整数のペアを配列に格納する(二次元配列になる)
  • 既出の文字ならペアの整数を足し合わせる
  • ペアの整数で降順に並び替える
def solve(input_lines)
  # 入力データ受け取り
  _, *lines = input_lines.split("\n")

  # 文字と数字のペアを集計する
  ary = []
  lines.each do |line|
    s, d = line.split
    idx = ary.index { |x| x[0] == s }
    if idx
      ary[idx][1] += d.to_i
    else
      ary.push([s, d.to_i])
    end
  end

  # 数字で降順ソートする
  sorted_ary = ary.sort_by { |x| x[1] }.reverse

  # 確認用コード
  sorted_ary
end

p solve(INPUT1)
# > [["B", 75], ["A", 11], ["D", 6], ["G", 4], ["C", 2]]
p solve(INPUT1) == OUTPUT1
# > false
p solve(INPUT2)
# > [["S", 3], ["G", 0], ["E", -2]]
p solve(INPUT2) == OUTPUT2
# > false
p solve(INPUT3)
# > [["A", -3], ["W", -5], ["E", -20]]
p solve(INPUT3) == OUTPUT3
# > false

あとは出力形式を整えれば完成です。

p solve(INPUT1) == OUTPUT1 -> true を確認するために、文字と整数を半角スペースで連結した要素を改行区切りの文字列に変換して、末尾にも改行を加えます。

def solve(input_lines)
  # 入力データ受け取り
  _, *lines = input_lines.split("\n")

  # 文字と数字のペアを集計する
  ary = []
  lines.each do |line|
    s, d = line.split
    idx = ary.index { |x| x[0] == s }
    if idx
      ary[idx][1] += d.to_i
    else
      ary.push([s, d.to_i])
    end
  end

  # 数字で降順ソートする
  sorted_ary = ary.sort_by { |x| x[1] }.reverse

  # 文字と整数を半角スペースで連結した要素を改行区切りの文字列に変換して末尾に改行を加える
  sorted_ary.map { |item| item.join(" ") }.join("\n") << "\n"
end

p solve(INPUT1)
# > "B 75\nA 11\nD 6\nG 4\nC 2\n"
p solve(INPUT1) == OUTPUT1
# > true
p solve(INPUT2)
# > "S 3\nG 0\nE -2\n"
p solve(INPUT2) == OUTPUT2
# > true
p solve(INPUT3)
# > "A -3\nW -5\nE -20\n"
p solve(INPUT3) == OUTPUT3
# > true

確認用コードを標準入力からのデータ受け取りに変更して、動作確認をしたら提出します。
複数行のデータ受け取りなので STDIN.read を使います。(入力終了は Ctrl+D 又は Ctrl+Z)

解答コード
def solve(input_lines)
  # 入力データ受け取り
  _, *lines = input_lines.split("\n")

  # 文字と数字のペアを集計する
  ary = []
  lines.each do |line|
    s, d = line.split
    idx = ary.index { |x| x[0] == s }
    if idx
      ary[idx][1] += d.to_i
    else
      ary.push([s, d.to_i])
    end
  end

  # 数字で降順ソートする
  sorted_ary = ary.sort_by { |x| x[1] }.reverse

  # 文字と整数を半角スペースで連結した要素を
  # 改行区切りの文字列に変換して末尾に改行を加える
  sorted_ary.map { |item| item.join(" ") }.join("\n") << "\n"
end

puts solve(STDIN.read)
他の解答例
  • 文字と整数のペアをハッシュで集計し、sort_byメソッドreverseメソッドで値の降順に並び替える
def solve(input_lines)
  # 入力データ受け取り
  _, *lines = input_lines.split("\n")

  # 文字と数字のペアを集計する
  hash = {}
  lines.each do |line|
    s, d = line.split
    if hash[s]
      hash[s] += d.to_i
    else
      hash[s] = d.to_i
    end
  end

  # 数字で降順ソートする
  sorted_hash = hash.sort_by { |k, v| v }.reverse.to_h

  # 文字と整数を半角スペースで連結した要素を
  # 改行区切りの文字列に変換して末尾に改行を加える
  sorted_hash.map { |item| item.join(" ") }.join("\n") << "\n"
end

puts solve(STDIN.read)

今回のまとめ

  • 文字と整数のペアを、二次元配列(又はハッシュ)を使って集計を行いました
  • sort_byメソッドreverseメソッドで使って二次元配列(又はハッシュ)の指定された要素の降順ソートを行いました
  • 二次元配列(又はハッシュ)の文字と整数のペアを半角スペースで連結しました

二次元配列やハッシュの指定要素での昇順・降順ソートは良く使うので慣れておきましょう!



【PR】アルゴリズム学習でお世話になった本


アルゴリズム関連の技術書は大抵C/C++で書かれていますが、ある程度プログラムが出来れば、処理の流れは理解することが出来ます。






通称: 螺旋本。C++で解説されています。AOJ(Aizu Online Judge)を運営している会津大学の渡辺准教授が書いた本です。データ構造や計算量の内容から丁寧に書いてありますのでアルゴリズム学習の最初の参考書としてオススメです。







通称: 蟻本。C++で解説されています。競技プログラミング中級者の定番書と言われていて、競技プログラミングで利用できる色々なアルゴリズムを学ぶことが出来ます。かなり高度な内容も含まれているので1冊分を完全に理解するのにはかなりの時間がかかりそうですが、手元に置いて何度も読み返しています。







通称: チーター本。C#, C++, JAVAでTopcoderの問題を解説しています。初心者~中級者向けに書かれているので解説が非常に丁寧です。







Python3でアルゴリズムを解説している本です。講義スタイルの本で、図やフローチャートを使ってアルゴリズムとデータ構造についてしっかりと説明されています。代わりにコードは少なめです。Pythonコードが読めなくても十分理解できると思います。


-paiza, プログラミング
-, , , , , , ,

© 2024 じゃいごテック