質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

Q&A

解決済

4回答

2008閲覧

妥当な連想配列の置き方

hMatoba

総合スコア15

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

0グッド

4クリップ

投稿2017/06/16 08:57

編集2017/06/16 09:00

ここではPHP7を例にとって書いていきますが、RubyやPythonなどの言語をやっている方でもどう考えるかお聞きしたいです。
数値を任意の情報に変換するための連想配列があるとします。

php

1$kinds = [ 2 1 => "man", 3 2 => "dog", 4 3 => "cat", 5];

下記のように使うことを考えています。

php

1$kind = $kinds[$num];

この連想配列を任意のスクリプトファイルに入れて呼び出せるようにしておきたい場合、どのように置いておくでしょうか。

身近なところで関数に入れているプロジェクトがありました。

php

1function getKinds() { 2 $kinds = [ 3 1 => "man", 4 2 => "dog", 5 3 => "cat", 6 ]; 7 return $kinds; 8} 9// 使用例 10$kinds = getKinds(); 11$kind = $kinds[$num];

使うたびに関数呼び出しをするのが合理的ではないと思います。

下記のように、名前のバッティングを起こさないように、任意のクラスのメンバにするのが妥当だと考えています。

php

1class Util { 2 public $kinds = [ 3 1 => "man", 4 2 => "dog", 5 3 => "cat", 6 ]; 7}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

###まずはRubyで考える

始めにRubyで考えます。

PHPのarrayとは違いRubyでは配列(Array)と連想配列(Hash)は別の物です。そのため、まずはどちらが良いかを考える必要があります。

単純な数値をインデックスとする場合、Arrayは高速であり妥当な選択です。しかし、負の値には対応していませんし、最大値に応じたメモリ容量が必要になってきます。対して、Hashは任意の数値をkeyにでき、必要な分しかメモリは消費されません。しかし、インデックスアクセスという側面ではArrayに劣ります(計算量は同じO(1)ですが)。今回の例では1~3しかないためArrayとした方が良いと考えられますが、負の値や最大値が非常に大きくなる歯抜けのkeyであればHashの方が良いかもしれません。

次に、大事なのは書き換えを不可能にすると言うことです。immutableなデータは予期せぬバグを防止することができます。

以上を踏まえて、考察します。

####定数でArray

Ruby

1# frozen_string_literal: true 2 3module Util 4 KINDS = [ 5 nil, 6 'man', 7 'dog', 8 'cat', 9 ].freeze 10end 11 12num = 1 13kind = Util::KINDS[num] 14p kind

moudleは名前空間を作成しているだけです。定数にしてかつfreezeにして配列が変更されないようにします(文字列は最初のマジックコメントでfrozenされています)。配列の0番目は何もないためnilを入れています。Rubyはインデックスを超えている場合はnilが返すため、存在確認はnilで行うべきです。

これは1,2,3と連続した数値であったから問題なかった書き方です。これがもし、1,2,100となっていれば、配列のリテラルがnilで埋まることになり、綺麗なコードとは言えません。

順序の固定された文字列の配列と考えるのであれば、本来インデックスは0から始めるべきでしょう。そうであれば、もっとうまく書くことができます。

Ruby

1# frozen_string_literal: true 2 3module Util 4 KINDS = %w[man dog cat].freeze 5end 6 7num = 0 8kind = Util::KINDS[num] 9p kind

####定数でHash

Ruby

1# frozen_string_literal: true 2 3module Util 4 KINDS = { 5 1 => 'man', 6 2 => 'dog', 7 3 => 'cat', 8 }.freeze 9end 10 11num = 1 12kind = Util::KINDS[num] 13p kind

Arrayとそれほど違いはありません。こちらの利点は、何と言っても数値が負の値でも可能であり、また、0から始まる順番にならんでなくても、一つ一つリテラルで書けるということです。

Ruby

1# frozen_string_literal: true 2 3module Util 4 KINDS = { 5 -1 => 'man', 6 2 => 'dog', 7 10 => 'cat', 8 }.freeze 9end 10 11num = -1 12kind = Util::KINDS[num] 13p kind

####モジュールメソッド

Ruby

1# frozen_string_literal: true 2 3module Util 4 module_function 5 6 def kind_name_by_id(n) 7 case n 8 when 1 9 'man' 10 when 2 11 'dog' 12 when 3 13 'cat' 14 else 15 raise ArgumentError, 'Not found kind' 16 end 17 end 18end 19 20num = 1 21kind = Util.kind_name_by_id(num) 22p kind

完全なメソッドにしてしまいます。この場合の利点は、存在しない場合は例外を投げるなどの動作が可能になると言うことです。

上のコードはどの名前なのかを探すためにcase式を使用しています。if式でも同様のことができるでしょう。柔軟性は高いですが、見た目も良いわけではなく、また、順番に比較するため計算量もO(n)です。3個程度であれば問題ありませんが、数が多い場合は妥当な実装とは言えません。

そこで先ほどのArrayまたはHashと組み合わせます。今回はHashと組み合わせてみます。

Ruby

1# frozen_string_literal: true 2 3module Util 4 KINDS = { 5 1 => 'man', 6 2 => 'dog', 7 3 => 'cat', 8 }.freeze 9 10 module_function 11 12 def kind_name_by_id(n) 13 KINDS.fetch(n) 14 rescue KeyError 15 raise ArgumentError, 'Not found kind' 16 end 17end 18 19num = 1 20kind = Util.kind_name_by_id(num) 21p kind

fetchは存在しないキーに対してはKeyError例外を発生させます。同じエラーになるようにrescueで救出した後に再度例外を発生させています。

このようにすれば、計算量もO(1)のままですし、対応も別途書いてあるためすっきりした物になります。数が多い場合はこのようにした方が良いでしょう。

KINDSが定数として定義されており、他からも直接見えてしまうのはよくないと考えるかも知れません。では、次のようにした方が良いのでしょうか?

Ruby

1# frozen_string_literal: true 2 3module Util 4 module_function 5 6 def kind_name_by_id(n) 7 kinds = { 8 1 => 'man', 9 2 => 'dog', 10 3 => 'cat', 11 } 12 kinds.fetch(n) 13 rescue KeyError 14 raise ArgumentError, 'Not found kind' 15 end 16end 17 18num = 1 19kind = Util.kind_name_by_id(num) 20p kind

ローカル変数へアクセスする方法がないため、freezeの必要はありません。これなら、先ほどよりも良いのでは無いかと思うかも知れません。

しかし、これはあまりよくありません。なぜなら、メソッドを呼ぶ出す度にHashが毎回生成されるからです。Rubyのリテラルは定数ではありません。オブジェクトの生成式です。生成コストが小さい数値や再利用されるシンボルやfrozen化された文字列であれば問題ありませんが、[]{}で配列や連想配列を生成することは、Array.new()Hash.new()を呼び出しているのと同じです。書き換わることがにimmutableのオブジェクトであれば、公開されても問題ありませんので、定数に入れてしまって何度も利用した方が良いでしょう。

###PHPで考える。

Rubyでの考察が終わりましたので、PHPに戻ります。PHPではどのようにしたら良いのでしょうか?名前空間はmoduleではなくnamespaceを使います。ArrayとHashの区別がないため、arrayを使えば問題なさそうです。では、早速、ベストだと思われる方法を書いてみましょう。

PHP

1<?php 2namespace Util { 3 function kind_name_by_id($n) { 4 $kinds = [ 5 1 => 'man', 6 2 => 'dog', 7 3 => 'cat', 8 ]; 9 return $kinds[$n]; 10 } 11} 12 13namespace { 14 $num = 1; 15 $kind = Util\kind_name_by_id($num); 16 echo $kind; 17}

あれ、先ほどRubyではローカルで連想配列を作ることは駄目出しされたのでは?いいえ、PHPはこれでいいんです

PHP7.0からは全てがリテラルなarray(immutable array)についてキャッシュを持つようになりました。arrayは毎回作成されず、再利用されます。なので、Rubyのような問題が起こることはありません。

RubyにはないPHPの優れた部分を垣間見えたと思います。PythonとかRubyとか、そんな別言語の事情は参考にせず、PHP特有のPHPにしかない独自の事だけを考えた方が良いかと思います。

投稿2017/06/17 03:26

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hMatoba

2017/06/17 14:55

PHPに加えてRubyでのケースではということまで 説明していただきありがとうございます。 また書き方による振る舞いの違いもすごく参考になりました。
guest

0

それをどれくらいの範囲で参照するか次第かなあ。
このへんはプロジェクトによっても決まりがあるかもですし、
場合によっては(提示された)関数の方がいい場合も?

クラス内でしか参照しないなら、その中で定義してもいいし
全体で参照するなら config 的なものもありでしょう。
さらに、複数プロジェクト間で使うならデータベースもあり。

ちなみに、関数にするなら全体を返すんじゃなくて
id を指定して text を返すのが好みかなあ。
エラー処理も含めてね。

投稿2017/06/16 09:44

takasima20

総合スコア7458

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

具体案でなく、検討項目についてだけ述べます。

以下の様な事を考える必要があります。

  1. 連想配列の内容をどこで管理するか?

例:

  • プログラムコード中に定数宣言する
  • 外部ファイル (ini ファイル、csv/json/yaml ファイル、 Database のテーブル...,)
  • 環境変数で与える
  1. 変換を利用する側のコードが、連想配列そのものを利用するようにするのか、

(int -> 値) のメソッドとして利用するようにするのか?

連想配列のサイズが膨大でメモリー使用量が問題になりそうなら、Database の利用を検討するとか、
その(int -> 値) の変換機能そのものを別サーバーでサービスするとかを検討することが必要になります。
例: 郵便番号 -> 住所 の変換

連想配列の値の保守のことも重要です。
変更される可能性があるなら、書き換えの管理、書き換え後のテストがし易いようしておくことが必要です。
実行環境毎に変化するようなら、コードに固定値を記載することはできません。
外部ファイルにするか、環境変数で管理ようなことが必要になります。

連想配列そのものを利用するようにした場合は、その配列内容を外部側から書き換えできないようにガードすることが必要です。

(int -> 値) のメソッドを用意するようにした場合は、その呼出コストがを検討する必要があります。
キャッシュ化するなどの工夫が必要なる場合もあるかもしれません。

ともかく、データの特性、利用する側の特性 を分析し、メモリー量、計算量、処理時間といったことのトレードオフを踏まえることが必要です。
さらに、利用するプログラム言語環境内での実装方法も加味して決定していくことになるとおもいます。

投稿2017/06/18 10:25

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

普通にこうすればよいのでは?
ちょっと雑なので必要な仕様は追加して下さい

PHP

1class Util 2{ 3 public $kinds = []; 4 function __construct() 5 { 6 $this->initKinds(); 7 } 8 function initKinds() 9 { 10 $this->kinds=[ 11 1 => "man", 12 2 => "dog", 13 3 => "cat", 14 ]; 15 } 16 function setKind($a,$b) 17 { 18 $this->kinds[$a]=$b; 19 } 20 function getKind($a) 21 { 22 return $this->kinds[$a]; 23 } 24 25} 26$u=new Util; 27print $u->getKind(1)."<br>"; 28$u->setKind(1,'xxx'); 29print $u->getKind(1)."<br>"; 30print $u->getKind(2)."<br>"; 31

投稿2017/06/16 13:27

yambejp

総合スコア114784

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問