:pぴー.sendせんど(:pぴー, :pぴー)

mokoaki
mokoriso@gmail.com

2017/07/22

String 文字列

Ruby技術者認定試験の書籍写経メモ

1.9よりStringオブジェクトはエンコーディング情報を保持するようになった。この為、Stringオブジェクトのメソッドは1文字を単位として動くように変わった

主な文字コード

UTF-8 主に使用されている文字コード
EUC-JP 古いUNIXシステムで利用されていたコード
JIS JISで策定された7bit文字のみを使った文字コード
Shift_JIS マルチバイト文字とASCII文字を切り替えることなく使用できるようにした文字コード
Windows-31J 主にWindowsで使われていたShift-JISの亜種
US-ASCII ASCII文字だけで構成されている文字コード

文字列のエンコーディングの取得

string#encoding

'abcde'.encoding
=> #<Encoding:UTF-8>

'ハゲ'.encoding
=> #<Encoding:UTF-8>

'abcde'.encode('EUC-JP').encoding
=> #<Encoding:EUC-JP>

'abcde'.encode('EUC-JP').encoding.name
=> "EUC-JP"

文字列のエンコーディングの変更

String#encode String#Encode!

'aaa'.encode('EUC-JP').encoding
=> #<Encoding:EUC-JP>

文字列のASCIIコードを取得

文字列の最初の一文字のコードポイント(1文字を表す整数のコード)を返す。ASCII範囲内ならASCIIコードが、色んな文字列ならそのエンコードに従った値になる

'abc'.ord
=> 97

'あいう'.ord
=> 12354

文字列の比較

==
>
>=
<
<=
<=> 件のUFO演算子 左が大きければ1
casecmp 文字の大小を無視してUFO演算子

文字列と異なるクラスのオブジェクトの比較

== メソッドのみ、異なるクラスのオブジェクトと比較できるが、型の自動変換は行われないので結果はfalseとなる

'100' == 100
=> false

'100' >= 100
ArgumentError: comparison of String with 100 failed

文字列の比較時のエンコーディング

'もこ' == 'もこ'
=> true

'もこ' == 'もこ'.encode('EUC-JP')
=> false

'ABC' == 'ABC'.encode('EUC-JP')
=> true

文字列の分割

'abcde'.split('c')
=> ["ab", "de"]

'abcde'.split('')
=> ["a", "b", "c", "d", "e"]

'ab1cd2ef3gh'.split(/\d/)
=> ["ab", "cd", "ef", "gh"]
'a_a_a'.split('_', 0)
=> ["a", "a", "a"]

'a_a_a'.split('_', 1)
=> ["a_a_a"]

'a_a_a'.split('_', 2)
=> ["a", "a_a"]

'a_a_a'.split('_', 3)
=> ["a", "a", "a"]

'a_a_a'.split('_', 4)
=> ["a", "a", "a"]
'a,b,,'.split(',')
=> ["a", "b"]

'a,b,,'.split(',', 0)
=> ["a", "b"]

'a,b,,'.split(',', 1)
=> ["a,b,,"]

'a,b,,'.split(',', 2)
=> ["a", "b,,"]

'a,b,,'.split(',', 3)
=> ["a", "b", ","]

'a,b,,'.split(',', 4)
=> ["a", "b", "", ""]

'a,b,,'.split(',', 5)
=> ["a", "b", "", ""]

'a,b,,'.split(',', -1)
=> ["a", "b", "", ""]

文字列の切り出し

'abcde'[2]
=> "c"

'abcde'.slice(2)
=> "c"

'abcde'[-2]
=> "d"

'abcde'.slice(-2)
=> "d"
'ab8de'[/\d/]
=> "8"

'ab8de'.slice(/\d/)
=> "8"
a = 'abcde'

a.slice!(2)
=> "c"

a
=> "abde"

範囲オブジェクトを渡すと該当する範囲が対象になる

'abcde'[1..3]
=> "bcd"

'abcde'.slice(1..3)
=> "bcd"

a = 'abcde'

a.slice!(1..3)
=> "bcd"

a
=> "ae"
'abcde'[1, 3]
=> "bcd"

'abcde'.slice(1, 3)
=> "bcd"

a = 'abcde'

a.slice!(1, 3)
=> "bcd"

a
=> "ae"
'abcdeabcde'['bc']
=> "bc"

'abcdeabcde'.slice('bc')
=> "bc"

a = 'abcdeabcde'

a.slice!('bc')
=> "bc"

a
=> "adeabcde"
'abcdeabcde'[/bc/]
=> "bc"

'abcdeabcde'.slice(/bc/)
=> "bc"

a = 'abcdeabcde'

a.slice!(/bc/)
=> "bc"

a
=> "adeabcde"

文字列の変更

a = 'abcdeabcde'
a[3] = '_hage_'
a
=> "abc_hage_eabcde"

a = 'abcdeabcde'
a[3, 2] = '_hage_'
a
=> "abc_hage_abcde"

a = 'abcdeabcde'
a[3..4] = '_hage_'
a
=> "abc_hage_abcde"

a = 'abcdeabcde'
a['bc'] = '_hage_'
a
=> "a_hage_deabcde"

a = 'abcdeabcde'
a[/bc/] = '_hage_'
a
=> "a_hage_deabcde"
a = 'abcdeabcde'
a.insert(3, '_hage_')
a
=> "abc_hage_deabcde"

文字列の一部を置換

sub sub!
gsub gsub!
tr tr!
tr_s tr_s!
delete delete!
squeeze squeeze!
replace  

sub sub! メソッド

指定したパターンに最初にマッチした箇所を指定した文字列に変更する

'abcabc'.sub('bc', '_hage_')
=> "a_hage_abc"

'abcabc'.sub(/bc/, '_hage_')
=> "a_hage_abc"

a = 'abcabc'
a.sub!('bc', '_hage_')
a
=> "a_hage_abc"

gsub gsub! メソッド

指定したパターンにマッチした箇所を全て指定した文字列に変更する

'abcabc'.gsub('bc', '_hage_')
=> "a_hage_a_hage_"

'abcabc'.gsub(/bc/, '_hage_')
=> "a_hage_a_hage_"

a = 'abcabc'
a.gsub!('bc', '_hage_')
a
=> "a_hage_a_hage_"

また、ブロックを取ることもでき、実行結果へと置換される

'abcabc'.sub('bc') do |match_data|
  "_#{match_data}_"
end
=> "a_bc_abc"
'1_2_3'.gsub(/\d/) do |match_data|
  match_data.to_i + 5
end
=> "6_7_8"

tr tr! tr_s tr_s! メソッド

'abcdef'.tr('b-d', 'B-D')
=> "aBCDef"

'abcdef'.tr('b-d', 'L-N')
=> "aLMNef"

'abcdef'.tr('bcd', 'BCD')
=> "aBCDef"

変換後の文字数が多い場合は無視される

'abcdef'.tr('bcd', 'BCD=')
=> "aBCDef"

'abcdef'.tr('b-d', 'B-D')
=> "aBCDDf"

変換前の文字数が多い場合は変換後の前の文字が使用されるっぽい

'abcdef'.tr('bcde', 'BCD')
=> "aBCDDf"

'abcdef'.tr('b-e', 'B-D')
=> "aBCDDf"

tr_sはこんな感じ

'aabbccddeeff'.tr_s('b-d', 'B-D')
=> "aaBCDeeff"

'aabbccddeeff'.tr_s('b-d', 'L-N')
=> "aaLMNeeff"

'aabbccddeeff'.tr_s('b-e', 'B-D')
=> "aaBCDff"

delete delete! メソッド

^ 先頭にある場合、その条件全て「ではない」事が条件になる
a-c a, b, c
da-ce d, a, b, c, e
a- a, -
13-57 1, 3, 4, 5, 7
-56 -, 5, 6
b-d-f b, c, d, -, f
ab-d-f a, b, c, d, -, f
'aabbccddee'.delete('b')
=> "aaccddee"

'aabbccddee'.delete('a-c')
=> "ddee"

'aabbccddee'.delete('a-c', 'b-d')
=> "aaddee"

squeeze squeeze! メソッド

'aabbccddaabbccdd'.squeeze('b')
=> "aabccddaabccdd"

'aabbccddaabbccdd'.squeeze('a-c')
=> "abcddabcdd"

'aabbccddaabbccdd'.squeeze('a-c', 'b-d')
=> "aabcddaabcdd"

'aabbccddaabbccdd'.squeeze
=> "abcdabcd"

replace メソッド

a = 'aabbcc'

a.object_id
=> 70253388501640

a.replace('hage')

a
=> "hage"

a.object_id
=> 70253388501640

文字列の連結

異なるエンコーディングの文字列を連結しようとした時にはエラー、異なるエンコーディングだがASCII文字列なら結合可能、なのはもう説明しなくてもいいですよね

+ メソッド

文字列を結合した新しいオブジェクトを生成する

'abc' + 'def'
=> "abcdef"

<< concat メソッド

元のオブジェクトを破壊的に追加する

a = 'abc'

a.concat('def')

a
=> "abcdef"
a = 'abc'

a << 'def'

a
=> "abcdef"

* メソッド

文字列を指定した数値の数だけ繰り返した文字列を生成する

'abc' * 5
=> "abcabcabcabcabc"

大文字、小文字への変換

メソッドの末尾に ! が付いているのは破壊的メソッドであり、レシーバ自体を変更するメソッド、なのはもう説明しなくてもいいですよね

capitalize capitalize!
upcase upcase!
downcase downcase!
swapcase swapcase!

capitalize capitalize! メソッド

文字列のアタマだけを大文字に、それ以外を小文字に変換する

'aBcDeFg'.capitalize
=> "Abcdefg"

upcase upcase! downcase downcase! メソッド

文字列を全て大文字、小文字に変換する

'aBcDeFg'.upcase
=> "ABCDEFG"

'aBcDeFg'.downcase
=> "abcdefg"

swapcase swapcase! メソッド

文字列の大文字を小文字に、小文字を大文字に変換する

'aBcDeFg'.swapcase
=> "AbCdEfG"

文字列の先頭や末尾にある空白文字を削除する

chomp chomp!
strip strip!
lstrip lstrip!
rstrip rstrip!
chop chop!

chomp chomp! メソッド

"abcabc\n".chomp
=> "abcabc"

'abcabc'.chomp('bc')
=> "abca"

'abcabc'.chomp('ab')
=> "abcabc"
"abcabc\n\n".chomp
=> "abcabc\n"

'abcabcc'.chomp('c')
=> "abcabc"

'abcabccc'.chomp('cc')
=> "abcabc"
"abcabc\n\n".chomp('')
=> "abcabc"

"abcabc\r\n\r\n".chomp('')
=> "abcabc"

"abcabc\r\n\r".chomp('')
=> "abcabc\r\n\r"

"abcabc\r\n\r\n\r\r\n".chomp('')
"abcabc\r\n\r\n\r"

正直、文字列の末尾の改行を消したいなら挙動の不審なchompは使うべきじゃなく、こう書くべきなんじゃないかと思うんだがどうだろうか

"abc \n abc \r\n abc \r\n\r\r\n".sub(/\R+\z/, '')
=> "abc \n abc \r\n abc "

strip strip! lstrip lstrip! rstrip rstrip! メソッド

先頭と末尾、先頭、末尾にある空白文字を取り除く

"\nabcabc\nabcabc\n".strip
=> "abcabc\nabcabc"

"\nabcabc\nabcabc\n".lstrip
=> "abcabc\nabcabc\n"

"\nabcabc\nabcabc\n".rstrip
=> "\nabcabc\nabcabc"

chop chop!

末尾の文字を取り除く

"abcd".chop
=> "abc"

"abcd".chop.chop
=> "ab"

\r\n だけは1つの改行として処理される

"Hage\r\n".chop
=> "Hage"

"Hage\n".chop
=> "Hage"

"Hage\r".chop
=> "Hage"

"Hage\n\r".chop
=> "Hage\n"

文字列を逆順にする

reverse reverse! メソッド

"abcd".reverse
=> "dcba"

文字列のサイズを取得する

length
size
count
empty?
bytesize

length size メソッド

文字数を返す (所謂)全角でも半角でも1文字は1と数える

"abcd".length
=> 4

"abcd".size
=> 4

count メソッド

指定した文字に該当する文字の数を返す

"ababa".count('a')
=> 3

"abcdeabcde".count('a-c')
=> 6

empty? メソッド

文字列が空かどうかを返す

''.empty?
=> true

' '.empty?
=> false

bytesize メソッド

文字列のバイトサイズを返す

'a'.bytesize
=> 1

'あ'.bytesize
=> 3

'𠀋'.bytesize # JIS X 0213の第3,4水準漢字の一部がUTF-8で4バイトとなる
=> 4

文字列の割付

center
ljust
rjust

正直、使ったことない

'moko'.center(20, '=')
=> "========moko========"

'moko'.ljust(20, '=')
=> "moko================"

'moko'.rjust(20, '=')
=> "================moko"

非表示文字列を変換する

dump メソッド

文字列の中にある改行コードやタブ文字などの非表示文字列をバックスラッシュ記法に置き換えた文字列を返す

a = "a\tb\tc\n"

puts a
a	b	c

puts a.dump
"a\tb\tc\n"

文字列をアンパックする

本当は以下の様になる筈なのだが、RubyのVerが違うからか、うまくアンパックできなかった。

'440r440T4408'.unpack('m')
=> ["\xE3\x83\xAB\xE3\x83\x93\xE3\x83\xBC"]

a = '440r440T4408'.unpack('m').first
=> "\xE3\x83\xAB\xE3\x83\x93\xE3\x83\xBC"

# アンパックされた文字列のエンコードはASCII-8BITになり、エンコーディングを設定してあげなければならない
a.force_encoding('UTF-8')
=> "ルビー"

文字列内での検索

include?
index
rindex
match
scan

include? メソッド

指定された文字列を含んでいるか

'abcabc'.include?('ca')
=> true

index メソッド

'abc1abc2abc'.index('abc')
=> 0

'abc1abc2abc'.index('abc', 5)
=> 8

'abc1abc2abc'.index(/\d/)
=> 3

rindex メソッド

'abc1abc2abc'.rindex('abc')
=> 8

'abc1abc2abc'.rindex('abc', 3)
=> 0

'abc1abc2abc'.rindex(/\d/)
=> 7

match メソッド

指定された正規表現によるマッチングを行い、MatchDataオブジェクトを返す

'abc1abc2abc'.match(/[a-c]/)
=> #<MatchData "a">

'abc1abc2abc'.match(/\d/)
=> #<MatchData "1">

scan メソッド

'abc1abc2abc'.scan(/[a-c]/)
=> ["a", "b", "c", "a", "b", "c", "a", "b", "c"]

'abc1abc2abc'.scan('a')
=> ["a", "a", "a"]

'abc1abc2abc'.scan(/\d/) do |match|
  p match.to_i + 5
end
6
7
=> "abc1abc2abc"

次の文字列

succ
next
pred は無い

数字のようにアルファベット順に桁上りが発生したら左の文字が変化する

'aa'.succ
=> "ab"

'az'.next
=> "ba"

文字列に対する繰り返し

each_line lines
each_byte bytes
each_char chars
upto downto は無い

each_line lines メソッド

"a\na".each_line { |a| p a }
=> "a\n"
=> "b"

"a\nbaba".lines('b') { |a| p a }
=> "a\nb"
=> "ab"
=> "a"

each_byte bytes メソッド

文字列をバイト毎に処理を繰り返す

'aあa'.each_byte { |a| p a }
=> 97
=> 227
=> 129
=> 130
=> 97

each_char char メソッド

文字列を1文字毎に処理を繰り返す

'aあa'.chars { |a| p a }
=> "a"
=> "あ"
=> "a"

upto メソッド

自分自身の文字列から指定された文字列まで、succメソッドで生成される次の文字列を使用して繰り返す

'a'.upto('d') { |a| p a }
=> "a"
=> "b"
=> "c"
=> "d"

他のクラスへの変換

hex  
oct  
to_f  
to_i  
to_s to_str
to_sym intern

hex メソッド

'f'.hex
=> 15

'0x10'.hex
=> 16

oct メソッド

'7'.oct
=> 7

'10'.oct
=> 8

'0b10'.oct # 2進数
=> 2

'0x10'.oct # 16進数
=> 16

to_i メソッド

'1'.to_i
=> 1

nil.to_i
=> 0

''.to_i
=> 0

'0b1'.to_i
=> 0

'0b1'.to_i(2)
=> 1

'0x10'.to_i
=> 0

'0x10'.to_i(16)
=> 16

to_f メソッド

'1.23'.to_f
=> 1.23

nil.to_f
=> 0.0

''.to_f
=> 0.0

'0b1'.to_f
=> 0.0

'0x10'.to_f
=> 0.0

to_s to_str メソッド

自分自身を返す

a = 'aa'

a.equal?(a.to_s)
=> true

a.equal?(a.to_str)
=> true

to_sym intern メソッド

'a'.to_sym
=> :a

''.to_sym
=> :""

"\n\t\v".intern
=> :"\n\t\v"