02. リストデータの組み合わせ

以下のようなデータを扱うとしたらどうするか、という話を書こうと思います。

  • テレビのメーカー一覧、を指定すると、メーカー名のリストが得られる
  • メーカー名、を指定すると、そのメーカーのテレビ名のリストが得られる
  • メーカー名とテレビ名、を指定すると、そのテレビの説明テキストが得られる

そんな管理には、ハッシュ(連想配列)が使えると便利です。そんな例を示したいと思います。

例で扱うデータ構造:

  • テレビメーカーリストを定義(a,b,c)
    • a,b,cそれぞれに型番リストと、その説明(型番商品の説明)を定義する

各スクリプトでは、以下のような実装方法を用います。

  • tcsh
    • リスト変数のみ使用
  • Perl
    • 配列とハッシュを使用
  • Ruby, Python, C#
    • 配列とハッシュとクラスを使用

--- tcsh

tcshで使えるのは、リスト変数のみです。種類ごとに異なる変数名をつけることで、複数種類のデータリストを扱うことができますが、あまり複雑なデータを扱うのは得意ではありません。

「メーカー名とテレビ名を指定」してテレビの説明を取り出す部分は、条件記述が複雑化してしまうため、説明用の配列にただ並べたものを表示させるだけにしています。

次の手順により、実装してみたいと思います。

  1. 変数定義
  2. 実行
    1. 各メーカーの情報を格納した変数を、条件分岐で共通変数へ格納し、以降の処理で共通変数を使用する
#!/usr/bin/tcsh -f
### Input Data ######################
# メーカー名リスト
set mTvMakerList = ( "TOSHIBA" "SONY" "PANASONIC" )
# メーカーごとの型番リスト
set mTsbTvList = ( "T1" "T2" "T3" )
set mSnyTvList = ( "S1" "S2" "S3" )
set mPnaTvList = ( "P1" "P2" "P3" )
# 各型番の説明
set mT1Desc = "Toshiba T1 TV desu"
set mT2Desc = "Toshiba T2 TV desu"
set mT3Desc = "Toshiba T3 TV desu"
set mS1Desc = "Sony S1 TV desu"
set mS2Desc = "Sony S2 TV desu"
set mS3Desc = "Sony S3 TV desu"
set mP1Desc = "Panasonic P1 TV desu"
set mP2Desc = "Panasonic P2 TV desu"
set mP3Desc = "Panasonic P3 TV desu"
### run ##########################
foreach maker ( $mTvMakerList )
  echo "--- $maker"
  if ( $maker == "TOSHIBA" ) then
    set mTvList = ( $mTsbTvList )
    set mDesc   = ( "$mT1Desc" "$mT2Desc" "$mT3Desc" )
  else if ( $maker == "SONY" ) then
    set mTvList = ( $mSnyTvList )
    set mDesc   = ( "$mS1Desc" "$mS2Desc" "$mS3Desc" )
  else if ( $maker == "PANASONIC" ) then
    set mTvList = ( $mPnaTvList )
    set mDesc   = ( "$mP1Desc" "$mP2Desc" "$mP3Desc" )
  endif
  @ index = 1
  foreach name ( $mTvList )
    echo " +-$name"
    echo " | +-$mDesc[$index]"
    @ index ++
  end
end

---------------------------------

実行結果:

--- TOSHIBA
 +-T1
 | +-Toshiba T1 TV desu
 +-T2
 | +-Toshiba T2 TV desu
 +-T3
 | +-Toshiba T3 TV desu
--- SONY
 +-S1
 | +-Sony S1 TV desu
 +-S2
 | +-Sony S2 TV desu
 +-S3
 | +-Sony S3 TV desu
--- PANASONIC
 +-P1
 | +-Panasonic P1 TV desu
 +-P2
 | +-Panasonic P2 TV desu
 +-P3
 | +-Panasonic P3 TV desu

--- Perl

プログラムは3つのブロックに分かれています。

  1. 入力ブロック
  2. データベース生成ブロック(ハッシュ生成)
  3. データベース読み出しブロック

ハッシュ %mTvDB を定義しますが、ハッシュに配列を格納するためには、配列のリファレンス(\@array、バックスラッシュをつける)を代入する必要があり、また取り出すときはデリファレンス(@{$mTvDB{key}})の形を使う必要があります。変数値(xxDesc)の格納は、リファレンスにする必要はありません。

ハッシュへのデータ登録時、キーをカンマ区切りで行っているところがありますが、これは実際には、ただ2つの文字列の間にカンマ文字列が入るに過ぎません。

#!/usr/bin/perl
use warnings;
### Input Data ###########################
# メーカー名の配列
@mTvMakerList = ("TOSHIBA","SONY","PANASONIC");
# 各メーカーの機種名配列
@mTsbTvList = ("T1","T2","T3");
@mSnyTvList = ("S1","S2","S3");
@mPnaTvList = ("P1","P2","P3");
# 各機種の説明
$mT1Desc = "Toshiba T1 TV desu";
$mT2Desc = "Toshiba T2 TV desu";
$mT3Desc = "Toshiba T3 TV desu";
$mS1Desc = "Sony S1 TV desu";
$mS2Desc = "Sony S2 TV desu";
$mS3Desc = "Sony S3 TV desu";
$mP1Desc = "Panasonic P1 TV desu";
$mP2Desc = "Panasonic P2 TV desu";
$mP3Desc = "Panasonic P3 TV desu";
### Create DB #############################
# ハッシュ定義。makerというキーに、メーカー名配列のリファレンスを格納
$mTvDB{maker} = \@mTvMakerList;
# 各メーカー名のキーに、機種名配列のリファレンスを格納
$mTvDB{$mTvMakerList[0]} = \@mTsbTvList;
$mTvDB{$mTvMakerList[1]} = \@mSnyTvList;
$mTvDB{$mTvMakerList[2]} = \@mPnaTvList;
# メーカー名+機種名のキーに、説明値を格納
$mTvDB{$mTvMakerList[0],$mTsbTvList[0]} = $mT1Desc;
$mTvDB{$mTvMakerList[0],$mTsbTvList[1]} = $mT2Desc;
$mTvDB{$mTvMakerList[0],$mTsbTvList[2]} = $mT3Desc;
$mTvDB{$mTvMakerList[1],$mSnyTvList[0]} = $mS1Desc;
$mTvDB{$mTvMakerList[1],$mSnyTvList[1]} = $mS2Desc;
$mTvDB{$mTvMakerList[1],$mSnyTvList[2]} = $mS3Desc;
$mTvDB{$mTvMakerList[2],$mPnaTvList[0]} = $mP1Desc;
$mTvDB{$mTvMakerList[2],$mPnaTvList[1]} = $mP2Desc;
$mTvDB{$mTvMakerList[2],$mPnaTvList[2]} = $mP3Desc;
### run
foreach $name0( @{$mTvDB{maker}} ){
  print "--- $name0\n";
  foreach $name1( @{$mTvDB{$name0}} ){
    print " +-$name1\n";
    print " | +-$mTvDB{$name0,$name1}\n";
  }
}

実行結果はtcshと同じなので省略します。

--- Ruby

rubyではclassを使って実装してみます。技術的なポイントとしては、ハッシュに配列やclassを格納しているが、Perlのようにポインタ渡し(リファレンス)する必要がないところです。

#!/usr/bin/ruby -w
### class
class TvInfo
  def initialize(name,desc)
    @name = name
    @desc = desc
  end
  attr_accessor :name, :desc
end
### Input Data
# メーカー名の配列を定義
mTvMakerList = %W[TOSHIBA SONY PANASONIC]
# クラスを生成し、機種名と説明を格納する
t1TvInfo = TvInfo.new("T1", "Toshiba T1 TV desu")
t2TvInfo = TvInfo.new("T2", "Toshiba T2 TV desu")
t3TvInfo = TvInfo.new("T3", "Toshiba T3 TV desu")
s1TvInfo = TvInfo.new("S1", "Sony S1 TV desu")
s2TvInfo = TvInfo.new("S2", "Sony S2 TV desu")
s3TvInfo = TvInfo.new("S3", "Sony S3 TV desu")
p1TvInfo = TvInfo.new("P1", "Panasonic P1 TV desu")
p2TvInfo = TvInfo.new("P2", "Panasonic P2 TV desu")
p3TvInfo = TvInfo.new("P3", "Panasonic P3 TV desu")
### Create DB
mTvDB = Hash.new #initialize
# ハッシュに配列を登録
mTvDB.store("maker", mTvMakerList)
# classの配列を定義
mTsbTvList = [t1TvInfo, t2TvInfo, t3TvInfo]
mSnyTvList = [s1TvInfo, s2TvInfo, s3TvInfo]
mPnaTvList = [p1TvInfo, p2TvInfo, p3TvInfo]
# ハッシュに配列を登録
mTvDB.store(mTvMakerList[0], mTsbTvList)
mTvDB.store(mTvMakerList[1], mSnyTvList)
mTvDB.store(mTvMakerList[2], mPnaTvList)
### run
mTvMakerList.each { |name0|
  puts "--- #{name0}"
  mTvDB.fetch("#{name0}").each { |item|
    puts " +-" + item.name
    puts " | +-" + item.desc
  }
}

実行結果はtcshと同じなので省略します。

--- Python

処理構造はRubyと同等です。

#!/usr/bin/python
### class
class TvInfo:
    def __init__(self, name, desc):
        self.name = name
        self.desc = desc
### Input Data
# メーカー名の配列を定義
mTvMakerList = ['TOSHIBA', 'SONY', 'PANASONIC']
# クラスを生成し、機種名と説明を格納する
t1TvInfo = TvInfo("T1", "Toshiba T1 TV desu")
t2TvInfo = TvInfo("T2", "Toshiba T2 TV desu")
t3TvInfo = TvInfo("T3", "Toshiba T3 TV desu")
s1TvInfo = TvInfo("S1", "Sony S1 TV desu")
s2TvInfo = TvInfo("S2", "Sony S2 TV desu")
s3TvInfo = TvInfo("S3", "Sony S3 TV desu")
p1TvInfo = TvInfo("P1", "Panasonic P1 TV desu")
p2TvInfo = TvInfo("P2", "Panasonic P2 TV desu")
p3TvInfo = TvInfo("P3", "Panasonic P3 TV desu")
### Create DB
mTvDB = {} #hash init
# ハッシュに配列を格納
mTvDB['maker'] = mTvMakerList
# classの配列を定義
mTsbTvList = [t1TvInfo, t2TvInfo, t3TvInfo]
mSnyTvList = [s1TvInfo, s2TvInfo, s3TvInfo]
mPnaTvList = [p1TvInfo, p2TvInfo, p3TvInfo]
# ハッシュに配列を格納
mTvDB[mTvMakerList[0]] = mTsbTvList
mTvDB[mTvMakerList[1]] = mSnyTvList
mTvDB[mTvMakerList[2]] = mPnaTvList
for maker in mTvDB['maker']:
    print "--- %s" % (maker)
    for item in mTvDB[maker]:
        print " +-%s" % (item.name)
        print " | +-%s" % (item.desc)

実行結果はtcshと同じなので省略します。

---C#

C#でも、Ruby, Pythonと同様に自作クラスを使用しています。

下記コード中、①を他の言語同様に記述していますが、使用していません。RubyやPythonでは、foreachを記述する際に変数展開元のHashに"maker"を指定して配列を取得し、その配列をforeachで回しているのですが、C#ではそれができず(コンパイラに怒られました)、仕方がないので一度変数に取り出してからforeachを回すという処理をしています。C#は実行速度が高速ですが、こういうケースではちょっと面倒でした。

using System;
using System.Collections;
class TvInfo {
    string name, desc;
    public TvInfo(string name, string desc){
        this.name = name;
        this.desc = desc;
    }
    public string getName(){
        return this.name;
    }
    public string getDesc(){
        return this.desc;
    }
}
class Test {
    public static void Main(string[] args){
        // メーカー名の配列を定義
        string[] mTvMakerList = new string[] {"TOSHIBA", "SONY", "PANASONIC"};
        // クラスを生成し、機種名を説明を格納する
        TvInfo t1TvInfo = new TvInfo("T1", "Toshiba T1 TV desu");
        TvInfo t2TvInfo = new TvInfo("T2", "Toshiba T2 TV desu");
        TvInfo t3TvInfo = new TvInfo("T3", "Toshiba T3 TV desu");
        TvInfo s1TvInfo = new TvInfo("S1", "Sony S1 TV desu");
        TvInfo s2TvInfo = new TvInfo("S2", "Sony S2 TV desu");
        TvInfo s3TvInfo = new TvInfo("S3", "Sony S3 TV desu");
        TvInfo p1TvInfo = new TvInfo("P1", "Panasonic P1 TV desu");
        TvInfo p2TvInfo = new TvInfo("P2", "Panasonic P2 TV desu");
        TvInfo p3TvInfo = new TvInfo("P3", "Panasonic P3 TV desu");
        // Create DB
        Hashtable mTvDB = new Hashtable();
        mTvDB["maker"] = mTvMakerList;                            // ①
        TvInfo[] mTsbTvList = new TvInfo[] {t1TvInfo, t2TvInfo, t3TvInfo};
        TvInfo[] mSnyTvList = new TvInfo[] {s1TvInfo, s2TvInfo, s3TvInfo};
        TvInfo[] mPnaTvList = new TvInfo[] {p1TvInfo, p2TvInfo, p3TvInfo};
        mTvDB[mTvMakerList[0]] = mTsbTvList;
        mTvDB[mTvMakerList[1]] = mSnyTvList;
        mTvDB[mTvMakerList[2]] = mPnaTvList;
        foreach(string maker in mTvMakerList){
            Console.WriteLine("--- {0}", maker);
            TvInfo[] infoList = (TvInfo[]) mTvDB[maker];            // ②
            foreach(TvInfo info in infoList){                                // ②
                Console.WriteLine(" +-{0}", info.getName());
                Console.WriteLine(" | +-{0}", info.getDesc());
            }
        }
    }
}

実行結果は省略します。