【初心者向け】順番に意味がある配列はハッシュに置き換えよう

## はじめに：順番に意味のある配列を定義するのは避けよう
プログラミング初心者向けの簡単なTipsです。

何か複数のデータをまとめてひとつの変数に保持したい場合には配列が使えます。
しかし「複数のデータ」とひとことに言っても、配列にまとめて良いデータといけないデータがあります。

たとえば以下のようなデータは配列にまとめるのは望ましくありません。

```ruby 
user_data = [
  170, # 身長
  65,  # 体重
  "AB" # 血液型
]
```

上のような配列は順番が意味を持ってしまいます。すなわち、

- indexが0のデータは身長（0以外のindexを指定してはダメ）
- indexが1のデータは体重（1以外のindexを指定してはダメ）
- indexが2のデータは血液型（2以外のindexを指定してはダメ）

という暗黙のルールが生まれてしまうためです。
この配列を適切に扱うためには、index（添え字）を間違いなく指定する必要があります。

それゆえ、うっかり以下のようなコードを書いてしまうとバグになってしまいます。

```ruby 
# あっ、index=1は体重ですよ！！
puts "私の身長は#{user_data[1]}cmです"
#=> 私の身長は65cmです
```

プログラムが小さいうち（100行未満）はこういった配列を使っていてもバグを出さずにコードを書けるかもしれませんが、業務で扱うような巨大で複雑なプログラムになると、「暗黙のルール」が仇（あだ）になってバグの原因になります。

### 正しい配列かどうかをチェックする方法
「ループ処理でまったく同じ処理を書けるか？（indexによって条件分岐が発生しないか？）」、数値の配列であれば「単純にデータを合計（sum）できるか？（もしくはその結果に意味があるか？）」といった点を自問自答してみましょう。
その答えがNoなら、配列を使うのは適切でない可能性が高いです。

```ruby 
user_data.each_with_index do |value, index|
  # ループ処理してるのにindexに応じて条件分岐するのは何かおかしい
  case index
  when 0
    puts "身長 = #{value}cm"
  when 1
    puts "体重 = #{value}kg"
  when 2
    puts "血液型 = #{value}"
  end
end
#=> 身長 = 170cm
#   体重 = 65kg
#   血液型 = AB
```

```ruby 
# 単純にsumできないのは何かおかしい（配列に異なるデータ型が混在している）
user_data.sum
#=> `+': String can't be coerced into Integer (TypeError)

# 仮に最初の2要素だけをsumしても、出てきた数値に意味がない（235っていったい何？？）
user_data[0..1].sum
#=> 235
```

一方、クラス全員のテストの得点などは配列にまとめても構いません。

```ruby 
# クラス全員のテストの得点を配列にまとめるのは問題なし
points = [90, 43, 100]

# ループ処理でまったく同じ処理を書けるか？ = YES
points.each do |point|
  puts "得点 = #{point}点"
end
#=> 得点 = 90点
#   得点 = 43点
#   得点 = 100点

# 単純にデータを合計（sum）できるか？ = YES
points.sum #=> 233
```
では、「順番に意味のある、おかしな配列」はどう書き換えればいいのでしょうか？

## 修正方法：代わりにハッシュを使おう
こういうケースではハッシュを使うのが良いです。
冒頭で示した`user_data`はハッシュを使って次のように書き換えることができます。

```ruby 
user_data = {
  height: 170,
  weight: 65,
  blood_type: "AB"
}
```

こうすればindexではなく、キーで値を参照できるようになるため、不具合が発生する確率が減りますし、可読性も良くなります。

```ruby
# ハッシュであればキーで指定できるので、:height = 身長ということが見て明らか
puts "私の身長は#{user_data[:height]}cmです"
#=> 私の身長は170cmです
```

### 応用：クラスやStructを使うのもアリ
他にもクラスやStructを使う方法もあります。以下はクラスを定義するコード例です。

```ruby 
class UserData
  attr_reader :height, :weight, :blood_type

  def initialize(height, weight, blood_type)
    @height = height
    @weight = weight
    @blood_type = blood_type
  end
end

user_data = UserData.new(170, 65, "AB")
puts "私の身長は#{user_data.height}cmです"
#=> 私の身長は170cmです
```

ハッシュの場合、うっかりキーをtypoすると`nil`が返りますが、クラスであればエラーになるのでミスに気づきやすくなります。

```ruby 
# ハッシュでキーをtypoするとnilが返るので間違いに気づきにくい
puts "私の身長は#{user_data[:heihgt]}cmです"
#=> 私の身長はcmです

# クラスであればtypoするとエラーが発生するのですぐ気づける
puts "私の身長は#{user_data.heihgt}cmです"
#=> undefined method `heihgt' for #<UserData:...> (NoMethodError)
#   Did you mean?  height
```

「単純なデータの入れ物」としての用途しかないのであれば、クラスの代わりにStructを使う（Structでシンプルにクラス定義する）方法もあります。

```ruby 
UserData = Struct.new(:height, :weight, :blood_type)

user_data = UserData.new(170, 65, "AB")
puts "私の身長は#{user_data.height}cmです"
#=> 私の身長は170cmです
```

typoしたときにエラーが発生する点もクラスと同じです。

```ruby 
puts "私の身長は#{user_data.heihgt}cmです"
#=> undefined method `heihgt' for #<struct UserData ...> (NoMethodError)
#   Did you mean?  height
```

## 順番に意味がある配列＝タプル？

最初に示したような「順番に意味を持つ配列」はタプルと呼ばれるデータ型に近いもの、と考えることもできます。

https://ja.wikipedia.org/wiki/%E3%82%BF%E3%83%97%E3%83%AB

```ruby 
# 順番に意味がある配列はタプルに近い
user_data = [
  170, # 身長
  65,  # 体重
  "AB" # 血液型
]
```

たとえばPythonはデータ型としてタプルをサポートしています。

```python 
# Pythonでタプルを使うコード例
user_data = (
  170, # 身長
  65,  # 体重
  "AB" # 血液型
)
print(user_data[0]) #=> 170
print(user_data[1]) #=> 65
print(user_data[2]) #=> AB
```

ただし、タプルはイミュータブルなので、あとから要素を変更したり追加したりすることができません。

```python 
# Pythonのタプルはデータを変更できない
user_data[0] = 180
#=> TypeError: 'tuple' object does not support item assignment
```

Rubyは配列をタプルっぽく使うことはできても、イミュータブルではない（要素の変更や追加ができてしまう）ので、タプルの完全な代替品として使うことはできません。そういう理由もあって、Rubyの場合は「順番に意味を持つ配列」に依存するのはなるべく避けた方が良いと思います。

## まとめ
というわけで、この記事では順番に意味がある配列を避け、ハッシュ（またはクラス）に置き換える方法を紹介しました。
複数のデータをひとつにまとめたい、と思ったときは、毎回配列を使うのではなく、適切なデータ型を選択できるようになりましょう😉