Railsチュートリアル 第4章 - Rails風味のRubyを学ぶ …配列と範囲

# 配列

## `split`メソッド

`split`メソッドを使うと、文字列を自然に変換した配列を得ることができます。

```irb:
>> "foo bar    baz".split
=> ["foo", "bar", "baz"]

>> "foo\tbar\tbaz".split
=> ["foo", "bar", "baz"]
```

`split`メソッドにおいて、デフォルトの区切り文字としては空白文字が適用されます。`\t`も空白文字の一種ですね。区切り文字を指定したい場合は、引数として与えればOKです。以下は区切り文字として`'x'`を指定する場合の例です。

```irb:
>> "fooxbarxbaz".split('x')
=> ["foo", "bar", "baz"]
```

## 配列の添字

```irb:
>> a = [42, 8, 17]
=> [42, 8, 17]

>> a[0]
=> 42
>> a[1]
=> 8
>> a[2]
=> 17
>> a[-1]
=> 17
```

一部の要素には、メソッドを使ってアクセスすることも可能です。

* `first`…配列先頭の要素
* `second`…配列先頭から2番めの要素
* `third`…配列先頭から3番めの要素
* `fourth`…配列先頭から4番めの要素
* `fifth`…配列先頭から5番めの要素
* `forty_two`…配列先頭から42番めの要素

42というのは、無意味な整数リテラルとして使われる整数の代表です。`forty_two`というのは、~~Matz氏~~DHH氏[^0907_eratta_1]一流のジョークでしょう。

なお、`second`以降はRubyの言語仕様で定義されたものではなく、rails/activesupportで追加されたメソッドです。

[^0907_eratta_1]: この部分について、「Rubyの言語仕様ではなく、rails/activesupportの仕様である」旨、複数の方から指摘がありました。確認したところ、確かに`#second`以降は[rails/activesupport](https://github.com/rails/rails/blame/d5e87e3be0f7d90fe9ca02161a8ea4918edbc799/activesupport/lib/active_support/core_ext/array/access.rb#diff-e3d63442dcd5dc00aa09c82c7daa4934)側で定義されたメソッドでした。訂正いたします。

```irb:
>> a.first
=> 42
>> a.last
=> 17
>> a.second
=> 8
>> a.third
=> 17
>> a.last == a[-1]
# a.last.==(a[-1])
=> true
```

* 配列の各要素にアクセスするには、各カッコで囲んだ添字を付ける
* 配列の添字はゼロから始まる（ゼロオリジン）
* 配列の添字として負の整数nを与えた場合、配列末尾からn番目の要素を得ることができる

Rubyの配列の添字について、特に重要な特徴は上記の通りでしょうか。

配列も文字列同様、`length`メソッドに反応します。

```irb:
>> x = a.length
=> 3
>> x == 3
=> true
>> x == 1
=> false
>> x != 1
=> true
>> x >= 1
=> true
>> x < 1
=> false
```

## 配列そのもののメソッド

`length`以外にも、配列(`Array`クラス)にはさまざまなメソッドが定義されています。

```irb:
>> a
=> [42, 8, 17]
>> a.empty?
=> false
>> a.include?(42)
=> true
>> a.sort
=> [8, 17, 42]
>> a.reverse
=> [17, 8, 42]
>> a.shuffle
=> [42, 17, 8]
>> a
=> [42, 8, 17]
```

上述のいずれのメソッド、特に`sort`、`reverse`、`shuffle`の各メソッドが実行された後にも、`a`そのものの内容は変更されていません。Rubyの配列において、これは重要な事実です。

配列の内容を変更したい場合、破壊的メソッドを使います。破壊的メソッドの名前は、元のメソッド名の末尾に「!」をつけたものであることが慣例となっています。以下は`sort!`メソッドを使用した例です。

```irb:
>> a
=> [42, 8, 17]
>> a.sort!
=> [8, 17, 42]
>> a
=> [8, 17, 42]
```

`a`の中身そのものが変更されています。

## 配列に要素を追加する`push`メソッド

```irb:
>> a
=> [42, 8, 17]
>> a.push(6) # 6を配列に追加する
=> [42, 8, 17, 6]
>> a << 7 # 7を配列に追加する
=> [42, 8, 17, 6, 7]
>> a << "foo" << "bar" # 配列に連続的に追加する
=> [42, 8, 17, 6, 7, "foo", "bar"]
```

```irb:
>> [0, 1, 2, 3].push(4).push(5)
=> [0, 1, 2, 3, 4, 5]
>> [0, 1, 2, 3].push("foo").push("bar")
=> [0, 1, 2, 3, "foo", "bar"]
```

* 要素の追加をチェーン(chain)できる
  * `push.push`というチェーンも可能
* Rubyの配列では、異なる型の要素が共存できる

`push`に関係して重要なポイントは以上のようなところでしょうか。

## 余談:`pop`メソッド

なお、`push`というメソッドがあることから察しが付いた人もいるかもしれませんが、配列には`pop`というメソッドも存在します。

* 引数なしの場合、配列末尾にある一つの要素を削除し、削除した値を適切な形で返す
  * 配列としてではなく、数値型や文字列型として、という意味
* 0以上の正のInteger型値`n`を引数として取ることができる
  * 配列末尾にある`n`個の要素を削除し、削除した値を配列として返す
  * `pop(0)`とした場合、空の配列を返す
* `!`が付かないにもかかわらず、破壊的動作をするメソッドである

```irb:
>> b = (0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>> c = b.pop  # 引数なしのpop
=> 9
>> b
=> [0, 1, 2, 3, 4, 5, 6, 7, 8]
>> c
=> 9
>> b.pop(7)  # 引数付きのpop
=> [2, 3, 4, 5, 6, 7, 8]
>> b
=> [0, 1]
```

# 範囲

```irb:
>> 0..9
=> 0..9
>> 0..9.to_a
NoMethodError (undefined method `to_a' for 9:Integer)
>> (0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

`0..9.to_a`と表記すると、`0..(9.to_a)`と解釈されます。Integer型に`to_a`というメソッドはないため、このようなメソッド呼び出しはエラーとなります。`0..9`という範囲に`to_a`メソッドを適用するためには、対象が`0..9`であることを明示するために、`0..9`を括弧で囲う必要があります。

```irb:
>> ('a'..'e').to_a
=> ["a", "b", "c", "d", "e"]
```

上記のように、文字列に対しても範囲オブジェクトを扱えます。

# 演習 - 配列と範囲

## 1. 文字列「A man, a plan, a canal, Panama」を ", " で分割して配列にし、変数aに代入してみてください。

```irb:
>> a = "A man, a plan, a canal, Panama".split(", ")
=> ["A man", "a plan", "a canal", "Panama"]
```

## 2. 今度は、変数aの要素を連結した結果 (文字列) を、変数sに代入してみてください。

```irb:
>> s = a.join
=> "A mana plana canalPanama"
```

## 3.1. 変数sを半角スペースで分割した後、もう一度連結して文字列にしてください (ヒント: メソッドチェーンを使うと１行でもできます)。

```irb:
>> s = s.split.join
=> "AmanaplanacanalPanama"
```

## 3.2. リスト 4.10で使った回文をチェックするメソッドを使って、(現状ではまだ) 変数sが回文ではないことを確認してください。

```irb:
>> palindrome_tester(s)
It's not a palindrome.
=> nil

# s => "AmanaplanacanalPanama"
```

## 3.3.downcaseメソッドを使って、s.downcaseは回文であることを確認してください。

```irb:
>> palindrome_tester(s.downcase)
It's a palindrome!
=> nil

# s.downcase => "amanaplanacanalpanama"
```

## 4.1. aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出してみてください。

```irb:
>> ("a".."z").to_a[6]
=> "g"

# ("a".."z").to_a => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
```

Rubyはゼロオリジン(zero-based)なので、7番目の要素の添字は6となります。

## 4.2. 同様にして、後ろから７番目の要素を取り出してみてください。

```irb:
>> ("a".."z").to_a[-7]
=> "t"

# ("a".."z").to_a => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
```

一番後ろの要素の添字は-1となります。