LoginSignup
1
1

More than 3 years have passed since last update.

Ruby初心者向けプログラミング問題10選を解いてみた(上)

Last updated at Posted at 2021-01-22

何か投稿してみようということで、少し前に解いた練習問題を貼っていきたいと思います
高度なテクニックは全く使っていないので、初心者の方も安心してご覧ください

コード、本文ともに見づらいと思いますがご勘弁を…

素晴らしき元サイト様

アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた(全10問)

カレンダー作成問題

問題文

問題文(折りたたみ)

Date クラスを使って、今月の1日と月末の日付と曜日を求め、次のような形式でカレンダーを表示させてください

      April 2013
 Su Mo Tu We Th Fr Sa
     1  2  3  4  5  6
  7  8  9 10 11 12 13
 14 15 16 17 18 19 20
 21 22 23 24 25 26 27
 28 29 3

コード

require "date"

today = Date.today
first_date = Date.parse(today.strftime("%Y.%m.1"))
last_date = (first_date >> 1) - 1

puts today.strftime("%B %Y").center(21)
puts " Su Mo Tu We Th Fr Sa"
print "   " * first_day.wday

(1..last_day.day).each do |day|
  print day.to_s.rjust(3)
  print "\n" if (first_date.wday + day) % 7 == 0 || day == last_day.day
end

要点解説

today = Date.today
first_date = Date.parse(today.strftime("%Y.%m.1"))
last_date = (first_date >> 1) - 1

今日、月初、月末のDateオブジェクトを定義します

月初は、todayをフォーマットし、その月の一日の文字列を作ってparseしています
月末は来月のfirst_dateの1日前として今月の最終日を求めています

print "   " * first_day.wday

wdayで得られる値は、0から始まる曜日を表す数字です (日曜→0, 月曜→1・・・という感じ)
その回数空白を出力すれば、1日までの空白を再現できます

  print "\n" if (first_date.wday + day) % 7 == 0 || day == last_day.day

1日の位置+現在の日付が7の倍数になったら改行します

カラオケマシン問題

問題文

問題文(折りたたみ)

かえるのうたのメロディは以下のような文字列で表現されます。

"C D E F |E D C |E F G A |G F E |C C |C C |CCDDEEFF|E D C "

縦棒( | )は小節の区切りです。
半角スペースは休符です。
音符と休符が8つ集まると1小節になります。

このメロディ(文字列)を変更するKaraokeMachineというクラスを作ります。
このクラスはインスタンス作成時にメロディを受け取ります。
transposeというメソッドに変化させたいキーの量を渡すと、その分だけキーを上げたり下げたりしたメロディが返却されます。

コード

class KaraokeMachine
  SCALE = %w(C C# D D# E F F# G G# A A# B).freeze

  def initialize(melody)
    @melody = melody
  end

  def transpose(amount)
    @melody.gsub(/[A-G]#?/) { |sub| SCALE[(SCALE.index(sub) + amount) % 12] }
  end
end

要点解説

 @melody.gsub(/[A-G]#?/) { |sub| SCALE[(SCALE.index(sub) + amount) % 12] }

正規表現での置換を使います
まず「A-Gのいずれかと#が0か1個ある」文字列をブロックに渡します
ブロック内部ではSCALE(SCALE.index(sub) + amount) % 12の音階を返します

ビンゴカード作成問題

問題文

問題文(折りたたみ)

1から75までの数字をランダムに配置して、ビンゴカードを作成するプログラミング問題です。
ただし、実際のビンゴカードのルールに沿って、各列は以下のような仕様で数字を出力する必要があります。

  • B:1~15のどれか
  • I:16~30のどれか
  • N:31~45のどれか
  • G:46~60のどれか
  • O:61~75のどれか

出力例は以下のとおりです。

 B |  I |  N |  G |  O
13 | 22 | 32 | 48 | 61
 3 | 23 | 43 | 53 | 63
 4 | 19 |    | 60 | 65
12 | 16 | 44 | 50 | 75
 2 | 28 | 33 | 56 | 68

ビンゴカードを文字列として出力する、Bingo.generate_card メソッドを作成してください。
(中略)
Bingo.generate_card メソッドの出力結果は上で述べた数字のルールのほかに、以下の仕様を満たす必要があります。

毎回異なるカードを生成します。
各列はパイプ(|)で区切ります。
数字や"BINGO"の文字は右寄せで出力します。
真ん中(FREEになる場所)はスペースを出力します。

コード

*ウンコードです

class Bingo
  def self.generate_card
    %w(B I N G O).each_with_index do |chr, i|
      column = ["#{chr}"]
      iex = (i + 1) * 15

      5.times do |c|
        column << rand((iex + 1 - 15)..iex).to_s
        redo if column.uniq!

        if chr == "N" && c == 2
          column.pop
          column << " "
        end
      end

      instance_variable_set("@#{chr}", column)
    end

    @B.zip(@I, @N, @G, @O) do |row|
      puts row.map { |x| x.rjust(2) }.join(" | ")
    end
  end
end

要点解説

      5.times do |c|
        column << rand((iex + 1 - 15)..iex).to_s
        redo if column.uniq!

        if chr == "N" && c == 2
          column.pop
          column << " "
        end
      end

B,I,N,G,Oの文字列の入ったそれぞれの配列にランダムな数字を入れていきます

(iex + 1 - 15)..iexはごちゃごちゃしてますが、一応期待通りの範囲オブジェクトを作れます

uniq!は重複する値が無いとnilを返します
なので、if文の条件式にするとuniq!が適用されcolumnから重複した値が除かれた場合にredoします

     instance_variable_set("@#{chr}", column)

第1引数を変数名、第2引数を中身としてインスタンス変数を定義できるメソッドです
苦肉の策です…

ボーナスドリンク問題

問題文

問題文(折りたたみ)

ある駄菓子屋で飲み物を買うと、空き瓶3本で新しい飲み物を1本プレゼントしてくれる。

最初に購入した飲み物の本数から、トータルで飲める本数を算出するプログラムを作成せよ。

また、最初に100本購入した場合、トータルで何本飲めるか。

*新しい飲み物が3本揃えばまた一本貰えるというひっかけ問題(?)

コード

def bounus_drink(amount)
  all_ju = [amount]
  dividend = amount
  loop do
    result = dividend.divmod(3)
    all_ju << result[0]
    dividend = result.sum

    break if dividend < 3
  end
  all_ju.sum
end

要点解説

  loop do
    result = dividend.divmod(3)
    all_ju << result[0]
    dividend = result.sum

    break if dividend < 3
  end

交換できた本数(dividend / 3の商)と、交換で手に入れた新しい飲み物も含めた交換できてない本数(dividend /3の余り)の合計が3以下になるまで3で割り続けます
再帰処理?ナニソレオイシイノ?

電話帳作成問題

問題文

問題文(折りたたみ)

カタカナ文字列の配列を渡すと、ア段の音別にグループ分けした配列を返すプログラムを作成する問題です。

# INPUT
['キシモト', 'イトウ', 'ババ', 'カネダ', 'ワダ', 'ハマダ']
# OUTPUT 
[ ['ア', ['イトウ']], ['カ', ['カネダ', 'キシモト']], ['ハ', ['ハマダ', 'ババ']], ['ワ', ['ワダ']] ]

コード

class NameIndex
  KATAKANAS =[
    ('ア'..'オ').to_a << 'ヴ',
    ('カ'..'ゴ').to_a,
    ('サ'..'ゾ').to_a,
    ('タ'..'ド').to_a,
    ('ナ'..'ノ').to_a,
    ('ハ'..'ボ').to_a,
    ('マ'..'モ').to_a,
    ('ヤ'..'ヨ').to_a,
    ('ラ'..'ロ').to_a,
    ('ワ'..'ン').to_a
  ].freeze

  def self.create_index(names)
    name_index = []
    KATAKANAS.each do |katakana|
      matched = names.select { |name| katakana.include?(name.chr) }
      name_index << [katakana[0], matched.sort] unless matched == []
    end
    name_index
  end
end

要点解説

      matched = names.select { |name| katakana.include?(name.chr) }

namesから1文字目がkatakanaに含まれいるものを抜き出します
問題文の例でいうと、katakana('ア'..'オ').to_a << 'ヴ'だった場合、matched["イトウ"]になります

つづく

以上、前半の5問でした
Ruby初心者向けプログラミング問題10選を解いてみた(下)に続く

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1