【Ruby】2つの文字列の一致・不一致問題

今日のRubyドリルでの自分の脳内処理を残したくなったので、記事を書いてみることにしました。

練習問題

2つの文字列の末尾の文字を比較して、一致する場合はTrue、一致しない場合はFalseを出力するプログラムの実装。
【条件】
・メソッドの引数に、任意の2つの文字列を指定する。
・引数に指定された2つの文字列のうち、どちらかがもう一方の文字列の末尾にある場合は、Trueを出力する
・上記を満たせていない場合は、Falseを出力する
・入力された文字が大文字でも小文字でも、同一の文字として処理を行う

私の考え

①文字を大文字か小文字どちから一方に揃える

まず条件を見て、大文字でも小文字でも同一の文字として処理するために、文字を大文字か小文字どちから一方に揃える必要があると考えました。
今回が2つの文字列を全て小文字に揃えることとし、downcaseメソッドを使用しました。

downcaseメソッド

文字列に含まれる大文字を小文字に変換したいときに使用するメソッド。

使い方

文字列.downcase

str = "Hello World"
puts str.downcase

出力結果
=> hello world

ちなみにupcaseメソッドで大文字に揃える方法でもよいのかなと思いました。(使い方はdowncaseメソッドと同じ)

2つの文字列を a,bとし、小文字に変換した文字列を下記のように変数に代入しました。

a_down = a.downcase 
b_down = b.downcase

②どちらか一方がもう一方の文字列の末尾に含まれるかを確認する

次に、どちから一方がどちらか一方の文字列の末尾に含まれるかを確認するために、
2つの文字列の長さを比較して、長い方の末尾から短い方の文字数分文字を切り取り、
その切り取った末尾の文字列と、短い方の文字列が一致するかの判定をしたらいいのかなと思いました。

※初めはinclude?メソッドを使おうかと思ったのですが、
含まれることを確認できても、「末尾に含まれる」という部分が難しくなるのかなと思い却下。

文字数の比較

2つの文字列がそれぞれ何文字あるのか数えるためにlengthメソッドを使用しました。

lengthメソッド

配列に対して使用すると、配列の要素の数を返す。
文字列対して使用すると、文字の数を返す。

使い方

配列or文字列.length

array  = ["red","blue","yellow"]
str = "たろぽよた"

array.length
=> 3

str.length
=> 5

2つの文字列を a,bとし、それぞれの文字数を下記のように変数に代入しました。

a_length = a.length
b_length = b.length

2つの文字列の長さを比較して条件分離を作ってみました。

if a_length >= b_length
 
else
  
end

a >= b、それ以外はa<bって感じです。
a = bはどっちに含めてもいいかなと思い、1つ目の条件式に入れてみました。

文字の末尾を切り取る

文字列の末尾を切り取るために、sliceメソッドを使いました。

sliceメソッド

配列や文字列から指定した要素を取り出すことができるメソッド。
要素を指定する際は数値で指定。
⚠️先頭は0からはじまる!

使い方

配列or文字列.slice(指定したい要素の番号)

array = [0, 1, 2, 3, 4, 5]
array.slice(1)

出力結果
=>1

sliceは範囲演算子(..)で取り出したい範囲を指定することができます。

今回の問題の場合、
a_length >= b_length の場合は、aの末尾からbの文字数分切り取りたいので、

a_down.slice(-(b_length)..-1)

それ以外(a_length < b_length)の場合は、bの末尾からaの文字数分切りたいので、

b_down.slice(-(a_length)..-1)

と、記載してみました。

長い方の文字列の末尾から切り取った文字列と、短い方の文字列が一致するかの判定をする

先ほどの条件式に、上記に書いたものをあてはめてみました。

if a_length >= b_length
    b_down == (a_down.slice(-(b_length)..-1))
  else
    a_down == (b_down.slice(-(a_length)..-1))
  end

完成形

def end_other(a, b)
  # 処理を記述
  a_down = a.downcase
  b_down = b.downcase
  a_length = a.length
  b_length = b.length

  if a_length >= b_length
    puts b_down == (a_down.slice(-(b_length)..-1))
  else
    puts a_down == (b_down.slice(-(a_length)..-1))
  end
end

# 呼び出し例
end_other('POYOTA', 'poyo') 
end_other('TaRo', 'poYotAro') 
end_other("KoromAn", "KoRo") 

出力結果
=>
false
true
false

想定の結果がでました。

でも細かいことを言うと、問題文は「True」「False」の表記になっているため、
指示通り忠実に。ということを考慮し、下記のように付け加えてみました。

def end_other(a, b)
  a_down = a.downcase
  b_down = b.downcase
  a_length = a.length
  b_length = b.length

  if a_length >= b_length
    result = b_down == (a_down.slice(-(b_length)..-1))
  else
    result = a_down == (b_down.slice(-(a_length)..-1))
  end

  if result
    puts "True"
  else
    puts "False"
  end
end

# 呼び出し例
end_other('POYOTA', 'poyo') 
end_other('TaRo', 'poYotAro') 
end_other("KoromAn", "KoRo") 

出力結果
=>
False
True
False

大事なことは、問題文の意図をしっかり理解することだとは思うで、
判定出せたらそれでよいのかなと思いつつも...
問題をよく読む練習として実装してみました。

模範回答

def end_other(a, b)
  a_down = a.downcase
  b_down = b.downcase
  a_len = a_down.length
  b_len = b_down.length
  if b_down.slice(-(a_len)..- 1) == a_down || a_down.slice(-(b_len)..- 1) == b_down
    puts "True"
  else
    puts "False"
  end
end

とってもシンプル!! またもや自分のコードはややこしいと思いました。

自分のコードのいいところ1個でもないかな... と考えてみました。
ちょっと強引かもしれませんが、
if文の中に||演算子を使用すると、真偽判定のために左右両方の処理をしなければいけないのに対し、
if文の中を比較演算子のみにすることで、不必要な計算がされず、処理が速くなるのかな ?と思いました。

おまけ

本日キャッチアップゼミに参加し、
他の受講者さんの貴重なご意見をたくさん聞けて、モチベーションが上がりました!
私も積極的にコミュニティを活用して、皆様のアウトプットをもっと吸収したいと思いました。