Perl ハッシュ入門と多次元ハッシュ(3次元以上)の扱い

# 対象読者
- Perl入門者
- Perlを使ってみたい方
- ハッシュについて知りたい方
- Perlでハッシュの概要はわかるけど3次元以上の実装方法がわからない方

# この記事の目的・背景
目的としては、タイトルの通りですがPerlにおけるハッシュの概要と3次元以上の多次元ハッシュの扱いがわかるようになることです。
背景は、2次元ハッシュの書き方の記事は調べたところたくさんありましたが3次元以上のハッシュの書き方が私の調べた限りでは載っていなかったため、この記事を通してアウトプットしたいと思ったということです。

# そもそもハッシュとは？
簡単にいうと、ハッシュとは以下の表のようにキーに対応する値を格納できるデータ構造のことをいいます。以下の表は、キーの文字列に対し、その文字列の出現回数を値として格納している例です。
<table>
    <tread>
        <tr>
            <th>キー</th> <th>値</th>
        </tr>
    </tread>
    <tr>
        <td>電池</td> <td>496</td>
    </tr>
    <tr>
        <td>パソコン</td> <td>234</td>
    </tr>
    <tr>
        <td>エアコン</td> <td>52</td>
    </tr>
    <tr>
        <td>テレビ</td> <td>1023</td>
    </tr>
    <tr>
        <td>冷蔵庫</td> <td>603</td>
    </tr>
</table>

## ハッシュの宣言
また、Perlではハッシュ変数の宣言に%を使用します。以下がハッシュ変数の宣言の書き方です。

```perl:ハッシュ変数の宣言
my %hash;
```
## ハッシュ変数へのデータの格納
ハッシュ変数へのデータの格納には変数名に$を使用します。

以下はハッシュ変数hashに{"キー", 値}={{"スマートフォン",2350}, {"タブレット", 833}}の2つのキーと値のデータを格納する際の例です。

```perl:ハッシュ変数へのデータの格納
my %hash;

my $key1   = "スマートフォン"
my $value1 = 2350;
my $key2   = "タブレット";
my $value2 = 833;

# データ格納
$hash{$key1} = $value1;
$hash{$key2} = $value2;
```
## ハッシュ変数のデータへのアクセス
ハッシュ変数のデータへのアクセスにはハッシュ変数に$をつけます。

上記のハッシュ変数hashに格納したデータ「スマートフォン」に対応する値2350にアクセスする場合は以下のようにします。

```perl:ハッシュ変数のデータへのアクセス
$key = "スマートフォン"
$h = $hash{$key};
```
また、ハッシュ変数のキーを1つずつ繰り返してアクセスするにはforeach文を使用するのが有用です。

```perl:ハッシュ変数のキーを1つずつ繰り返してアクセス
sub main
{
    my %hash;

    my $key1 = "スマートフォン";
    my $key2 = "タブレット";

    # データを格納
    $hash{$key1} = 2350;
    $hash{$key2} = 833;

    foreach my $key (keys %hash)
    {
    	my $value = $hash{$key};
    	print encode_utf8("$key　→　$value\n");
    }
}
```
# 3次元ハッシュの扱い・実装
上記のハッシュの概要を用いて3次元ハッシュを実装していきます。

実装するものとして、以下のCSVファイルを読み取り、分割属性(例「天気」,「風」)と対応する属性地(「曇」,「有」)、外出(o,x)の頻度を求めるプログラムを作成します。

```csv:test.csv
天気	温度	湿度	風	外出
曇	暑	高	無	x
晴	暑	高	有	x
曇	暑	高	無	o
雨	暖	高	無	o
雨	涼	普通	無	o
雨	涼	普通	有	x
曇	涼	普通	有	o
晴	暖	高	無	x
晴	涼	普通	無	o
晴	暖	高	有	o
晴	暖	普通	有	x
曇	暖	高	有	o
曇	暑	普通	無	o
雨	暖	高	有	x
```
まず、ハッシュ変数を宣言します。

今回はDataHashに格納するハッシュを関数内でまとめて処理するためハッシュを返す関すmkDataHash()を宣言します。引数には$input_fileというtest.csvのパスを格納している変数を取ります。

:::note warn
注意
$input_fileに格納するファイルパスには絶対パスを使用してください。m1 Macの環境下では相対パスは読み取れないみたいです。
[参照記事](https://qiita.com/yShell/items/6217c452c19f56514b00 "Perlでファイルからの読み込み時に起こったバグ for M1Mac")
:::

```perl:
my $input_file = "ファイルのパス"
my %DataHash = mkDataHash($input_file);
```
次にmkDataHash()の中身を記述していきます。
なお、今回は3次元ハッシュを取り扱うことが目的なのでそれ以外の処理は最後にまとめて載せておきます。

まず、返り値とするハッシュ変数を宣言します。後ほど分割属性を扱いやすいように配列tag_listも宣言しておきます。

```perl
my %trainData;
my @tag_list;
```
次にwhile文で1行ずつファイルを読み込んでいきます。
その際に1行目は分割属性なので先ほど宣言したtag_listに格納します。

```perl
if ($zero_line)
{
    for (my $i = 0; $i <= $#file_line; $i++)
    {
        $tag_list[$i] = $file_line[$i];
    }
    $zero_line = 0;
}
```
1行目以外は3次元ハッシュに(天気)→(晴)→(o)のような分割属性に対応する属性値の外出頻度を格納していきます。

多次元ハッシュは`ハッシュ{キー1}->{キー2}->{キー3}`のようにキーに対応する値にアクセスできます。今回は1ずつ頻度を増やしていくのでインクリメント演算子を使用します。

また->は省略でき、次のように表現することもできます。`ハッシュ{キー1}{キー2}{キー3}`
4次元以上のハッシュを扱うことがあっても同じ原理です。

```perl
else
{
    my $go_out = pop(@file_line);
    for (my $i = 0; $i < $#tag_list; $i++)
    {
        my $tag = $tag_list[$i];
        my $data = $file_line[$i];

        $TrainData{$tag}->{$data}->{$go_out}++;
    }
}
```
# まとめ
このようにハッシュを書くことで3次元ハッシュを実装することができます。

Perlではこの3次元ハッシュを使用してデータの格納・アクセスを簡単に書くことができます。これを応用して、新しいデータを与えた際に外出がoかxかを予測をナイーブベイズ学習や階層クラスタリングなどの自然言語処理をPerlで実装することが可能になります。

ここでは取り扱いませんが、別の記事で書いてみたいと思いますので自然言語処理やPerlに興味のある方はぜひフォローいただけたら幸いです！！

# コード全体の記載
以下に今回実装したコードをまとめて記載しておきます。

```perl
#!/usr/bin/perl

use strict;
use Encode;
use utf8;

main();

sub main
{
    my $input_file = "/Users/yanagisawakai/college2023_1/data_mining/college_perl_project/test.csv";
    my %DataHash = mkDataHash($input_file);

    foreach my $tag (keys %DataHash)
    {
        foreach my $data (keys %{$DataHash{$tag}})
        {
            foreach my $det (keys %{$DataHash{$tag}{$data}})
            {
                my $td = $DataHash{$tag}->{$data}->{$det};
                print encode_utf8("$tag -> $data -> $det = $td\n");
            }
        }
    }
}

sub mkDataHash
{
    my $input_file = $_[0];
    open(my $IN, $input_file) or die "cannot open $input_file\n";

    my %TrainData;
    my @tag_list;
    my $zero_line = 1;
    while (my $line = <$IN>)
    {
        chomp($line);
        my $str =  decode_utf8($line);
        my @file_line = split(/\t/, $str);

        if ($zero_line)
        {
            for (my $i = 0; $i <= $#file_line; $i++)
            {
                $tag_list[$i] = $file_line[$i];
            }
            $zero_line = 0;
        }
        else
        {
            my $golf_play = pop(@file_line);
            for (my $i = 0; $i < $#tag_list; $i++)
            {
                my $tag = $tag_list[$i];
                my $data = $file_line[$i];

                $TrainData{$tag}->{$data}->{$golf_play}++;
            }
        }
    }

    return %TrainData;    
}
```