PHPの配列はマップ

マップの生成

PHPの配列は、始めから キー => 値 の組として存在しますので、マップです。

keyは整数または文字列。valueは任意の型のデータを入れることができます。

array()で生成しますが、PHP 5.4 以降[]を使えるようにもなりました。

$fruits = array("apple"=>"りんご", "orange"=>"みかん");
$fruits = ["apple"=>"りんご", "orange"=>"みかん"];

特筆すべきは、最後のコンマが無視されることです。次のプログラム例で示します。これで key=>value, を行ごとに書く時に、要素の追加が楽になります。

配列の内容の確認には、print_r(), var_dump(), var_export()が使えます。

<?php
$fruits = [
    "apple"  => "りんご",
    "orange" => "みかん",
    "peach"  => "もも",
];
echo "<p>";
print_r($fruits);
echo "</p>";
echo "<p>";
var_dump($fruits);
echo "</p>";
echo "<p>";
var_export($fruits);
echo "</p>";
?>

実行結果です。dumpのStringの後の数字はvalueの扱いがUTF-8だと推測できます。

Array ( [apple] => りんご [orange] => みかん [peach] => もも )

array(3) { ["apple"]=> string(9) "りんご" ["orange"]=> string(9) "みかん" ["peach"]=> string(6) "もも" }

array ( 'apple' => 'りんご', 'orange' => 'みかん', 'peach' => 'もも', )

空のマップをつくる

配列と同じものですからやり方も同じです。生成の時に要素が空であれば、要素数0のマップができます。

$phpmap = array();
$phpmap = [];

参照・変更・項目の追加

上で定義した$fruitsはページ内で値が保持されるので、ここで再度定義するのは無駄ですが、わかりやすさのために再度書きました。

$fruits = [
  'apple' => 'りんご',
  'orange' => 'みかん',
  'peach' => 'もも',
];
echo $fruits["apple"],"\n";
echo $fruits["orange"],"\n";
//echo $fruits["lemmon"],"\n"; //Notice:  Undefined index: lemmon
$fruits["orange"] = "オレンジ";
echo $fruits["orange"],"\n";
$fruits["pineapple"] = "パイナップル";
echo $fruits["pineapple"],"\n";;
var_export($fruits);

実行結果です。

りんご
みかん
オレンジ
パイナップル
array (
  'apple' => 'りんご',
  'orange' => 'オレンジ',
  'peach' => 'もも',
  'pineapple' => 'パイナップル',
)

出力時の""中のマップのkeyの扱い

配列:つまり整数をkeyとするマップでは、[ ]中に整数をリテラルとして書けば受け付けられました。echoなどの""の中でも同様です。

echo $names[1];  //OK
echo "<td>$names[1]</td>";  //OK

keyが文字列の場合は注意が必要です。

echo $fruits['apple'];  //OK
echo "<td>$fruits['apple']</td>";  //エラーになる

要するに""の中なのでもともと文字列という扱いだということでしょう。ちょっと混乱します。

echoの引数実行結果OK/NG説明
"<td>",$fruits['apple'],"</td>"りんごOKコンマで区切ると独立に処理され、通常通り
"<td>",$fruits[apple],"</td>"Notice: Use of undefined constant apple
- assumed 'apple'
''がないappleは未定義の定数とみなされた
これをしないように注意するのは当たり前のこと。
"<td>$fruits[apple]</td>"りんごOK""内は元々文字列なので'apple'と解釈される。ちょっと意外
"<td>$fruits['apple']</td>"syntax error, unexpected ''逆に''があるとエラーになる。注意
"<td>{$fruits['apple']}</td>"りんごOK{ }内は通常通り解釈される。お勧め
"<td>$fruits[$name]</td>"りんごOK$name='apple'となっていれば問題ない。

とすると、整数の echo "$names[1]"; においても、1は文字列として処理されている可能性があります。

データの型

最初に述べたとおり、keyは整数または文字列。valueは任意の型のデータを入れることができます。valueの型の混在が可能なことはPHPの配列とリストでやってみていますので、ここでは省略します。

keyは整数または文字列

空のマップを作るのも、配列と同じものですからやり方も同じです。生成の時に要素が空であれば、要素数0のマップができます。

$phpmap = array();
$phpmap = [];

keyを整数にしたものと、数字の文字列で区別が無いことを確認します。

つまり、$phpmap[22]と$phpmap['22']が同じということです。

$phpmap[22] = "number";
echo $phpmap[22],"<br>";      #number
echo $phpmap["22"],"<br>";    #number
echo "<p>";
var_export($phpmap);
echo "</p>";
$phpmap['22'] = "string";
echo $phpmap[22],"<br>";      #string
echo $phpmap["22"],"<br>";    #string
echo "<p>";
var_export($phpmap);
echo "</p>";
$phpmap[10+12] = "10+12";
echo $phpmap[22],"<br>";      #10+12
echo $phpmap["22"],"<br>";    #10+12
echo "<p>";
var_export($phpmap);
echo "</p>";

$phpmap[22]も$phpmap['22']も$phpmap[10+12]も同じkeyに対応付けられていて、同じ要素となり、$phpmapの要素は一つだけです。

結果です。

number
number

array ( 22 => 'number', )

string
string

array ( 22 => 'string', )

10+12
10+12

array ( 22 => '10+12', )

ここまでわかればいいのですが、もう一ついたずら。

$phpmap2 = [];
$phpmap2['022'] = '文字列で"022"';
$phpmap2[022] = "数値で022";
$phpmap2[026] = "数値で026";
echo "<p>";
var_export($phpmap2);
echo "</p>";

array ( '022' => '文字列で"022"', 18 => '数値で022', 22 => '数値で026', )

数値のリテラルで0から始まる場合は8進法になります。022は18、026は22です。

繰り返し機構

valueのみのものはPHPの配列とリストですでに扱いました。

foreach (array_expression as $value)

keyが不要なら、これもそのまま使えますが、必要な場合は次のようにします。

foreach (array_expression as $key => $value)

$key => $value の基本的な使い方は、

$fruits = [
  'apple' => 'りんご',
  'orange' => 'みかん',
  'peach' => 'もも',
];
foreach ($fruits as $en => $ja){
    echo $en," ",$ja,"/ ";
}

実行結果は、

apple りんご/ orange みかん/ peach もも/ 

連想配列に感動した昔

javascript版,Java版,Python版と同じ企画です。

連想配列に出会ったのは1993年のことでした。UNIX上のawkでした。

次のような名前と数値が組になったデータから、名前ごとの合計を出すというものです。

onion 50
carrot 226
carrot 315
eggplant 30
radish 75
eggplant 30
onion 120
carrot 417
eggplant 23
onion 80
radish 89
carrot 715
onion 95
radish 135
radish 98

こんな感じでできてしまいます。

$ awk '{sum[$1]+=$2} END {for(name in sum)print name,sum[name]}' onion.txt 
radish 397
eggplant 83
carrot 1673
onion 345

END以下は結果の表示のためのforで、計算の本体は連想配列 sum[$1] += $2 です。 $1がkey、$2が値でkeyが同じ場所に値を加算するものです。

PHPの場合

データをファイルから読ために file() 関数を使います。ファイル全体を読んで配列にします。 FILE_IGNORE_NEW_LINES(配列の各要素の最後に改行文字を追加しない)と FILE_SKIP_EMPTY_LINES(空行を読み飛ばす)の両方のオプションをつけています。

無い値を参照すると Notice: Undefined index が発生するので、array_key_exists()で存在を確認しています(強行しても計算はうまく行きます)。

$onions = file('onion.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$vegmap=[];
foreach ($onions as $line){
    $vegnum = explode(" ", $line);
    if (array_key_exists($vegnum[0], $vegmap)) {
    	    $vegmap[$vegnum[0]] += $vegnum[1]; 
    }else{
    	    $vegmap[$vegnum[0]] = $vegnum[1]; 
     }
    if ($vegnum[0]=="onion") echo $line,':',gettype($vegmap['onion']),"\n";
}
var_export($vegmap);

exploadされた2番目の項目は数字で構成され、文字列です。$vegmapのvalueとして最初に格納されたときも文字列ですが、+= で計算される時に数値として換算され、その後格納されるとvalueは数値(ここではinteger)になります。

onion 50:string
onion 120:integer
onion 80:integer
onion 95:integer
array (
  'onion' => 345,
  'carrot' => 1673,
  'eggplant' => 83,
  'radish' => 397,
)

存在確認・削除などの操作の一覧

数値のkeyで使用する主な関数と操作については、PHPの配列とリストで紹介をしました。key=>valueについては章を変えて書くつもりですと書いたのですが、加えるものが、array_key_existsだけなので、こんなことになりました。

また、sortについてはさらにあとで扱うつもりなので割愛します。

戻値関数説明
key array_search(x, array [,true]) x と等しい値を持つ最初のvalueに対応するkeyを返す。なければfalseを返す。
厳密な比較が必要なら,trueを加える。再掲載
bool in_array(x, array [,true]) x と等しい値を持つvalueがある時にtrueを返す。なければfalseを返す。
厳密な比較が必要なら第三引数として,trueを加える。再掲載
unset(var [,var...])
unset(array[key])
変数操作関数。変数を破棄するものだが、配列の要素に使える。
keyを指定して配列の要素を破棄するにはこれしかない。戻値はない。再掲載
bool array_key_exists(key, array)
key_exists(key, array)
array内に指定したkeyあるかどうかを調べる。
key_existsと書いても良い。

valueを指定して削除

整数keyの配列ではarray_search(value,array)を使ってkeyを求め、array_splice(array,開始位置,削除数) の開始位置にkeyを使えましたが、文字列のkeyではWarningが出ます。数字で始まらない文字列は0となるので、エラーでなく警告となるのでしょう。とにかくvalueの項目は削除できません。

unset(array[key])を使って削除します。

$fruits = [
  'apple' => 'りんご',
  'orange' => 'みかん',
  'peach' => 'もも',
  'mandarin' => 'みかん',
];
echo "start |";
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
$todel = "みかん";
$k = array_search($todel,$fruits);
echo "\nfound $todel in $k";
//array_splice($fruits,$i,1); //Warning:  array_splice() expects parameter 2 to be integer
unset($fruits[$k]);
echo "\nunset |";
foreach ($fruits as $key=>$val) echo $key,":",$val," ";

実行結果です。array_search()が最初の一致を見つけるので、ひとつだけ削除されます。

start |apple:りんご orange:みかん peach:もも mandarin:みかん 
found みかん in orange
unset |apple:りんご peach:もも mandarin:みかん 

keyを指定して削除

始めからkeyがわかっていれば、すぐに削除できます。

$fruits = [
  'apple' => 'りんご',
  'orange' => 'みかん',
  'peach' => 'もも',
  'mandarin' => 'みかん',
];
echo "start |";
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
unset($fruits['peach']);
echo "\nunset |";
foreach ($fruits as $key=>$val) echo $key,":",$val," ";

実行結果です

start |apple:りんご orange:みかん peach:もも mandarin:みかん 
unset |apple:りんご orange:みかん mandarin:みかん 

array_splice()は整数keyを書き換えるので注意

array_spliceはkeyの値でなく、開始位置と削除数で削除することを、確認します。

上記の警告は開始位置が'orenge'であったことから来るもので、整数で指定するのは問題ありません。

注意点を示すために'もも'以降のkeyを整数に変えました。

$fruits = [
  'apple' => 'りんご',
  'orange' => 'みかん',
  '1' => 'もも',
  '3' => 'みかん',
  '5' => 'レモン',
  '7' => 'メロン',
];
echo "start |";
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
array_splice($fruits,1,3);
echo "\nsplice |";
foreach ($fruits as $key=>$name) echo $key,":",$name," ";

実行結果は

start |apple:りんご orange:みかん 1:もも 3:みかん 5:レモン 7:メロン 
splice |apple:りんご 0:レモン 1:メロン 

確かに、1番目から3つ削除されましたが、整数のkeyは変更されてしまいます。

文字列キーの配列でもarray_splice()は使えますが、キーが数値になる場合(クォートしても)は危険をはらみます。

array_search()で見つからない場合falseが返るが

array_search()で見つからない場合はfalseが返ります。

falseは添字にすると0なので、確認せずにarray_splice()に使うと、先頭の要素から削除されてしまいます。

次はその確認。

$fruits = [
  'apple' => 'りんご',
  'orange' => 'みかん',
  'peach' => 'もも',
  'mandarin' => 'みかん',
];
echo "start  |";
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
$k = array_search('トマト',$fruits);
echo "\n戻値は'$k'で、そのtypeは'",gettype($k),"'である。(表示されないがfalse)\n";
var_dump($k);
echo "splice |";
array_splice($fruits,$k,1);
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
echo "\n\$kを 0, false, '' と比較 |\n";
echo ($k==0     ? '$k==0'    :'$k!=0'    ),"\n";
echo ($k==false ? '$k==false':'$k!=false'),"\n";
echo ($k==''    ? '$k==\'\'' :'$k!=\'\'' ),"\n";
echo ($k===0     ? '$k===0'    :'$k!==0'    ),"\n";
echo ($k===false ? '$k===false':'$k!==false'),"\n";
echo ($k===''    ? '$k===\'\'' :'$k!==\'\'' ),"\n";

実行結果です

start  |apple:りんご orange:みかん peach:もも mandarin:みかん 
戻値は''で、そのtypeは'boolean'である。(表示されないがfalse)
bool(false)
splice |orange:みかん peach:もも mandarin:みかん 
$kを 0, false, '' と比較 |
$k==0
$k==false
$k==''
$k!==0
$k===false
$k!==''

falseは==での比較では、0であり、''でもあります。正しく評価されません。

===で比較すると、0ではなく、''でもありません。見つからなかった時と添字0に見つかった時の区別が付きます。

つまり、以下のようにする必要があるということです。

if ($k!==false) array_splice($fruits,$k,1);

array_search()のtrueが必要な場合

phpの値の比較の決まりでは、数値と文字列の比較や数値にできる文字列は数値に変換して比較します。比較時の型変換を抑止したい時は === や !== を == や != の代わりに使います。

array_search()の3つめの引数にtrueを入れると、型変換を抑止し厳密な比較をします。

trueが必要な例を作ってみます。

$arr = [
    'string'  => 'some',
    'zero'    => 0,
    'boolean' => true,
    'findme'  => 'kore',
    'num_one' => 1,
    'str_one' => '1',
];
echo "find 'kore'\n";
var_dump( array_search( 'kore', $arr ) ); //zero
var_dump( array_search( 'kore', $arr, true ) ); //findme
echo "find '1'\n";
var_dump( array_search( '1', $arr ) ); //boolean
var_dump( array_search( '1', $arr, true ) ); //str_one
echo "find '33'\n";
var_dump( array_search( '33', $arr ) ); //boolean
var_dump( array_search( '33', $arr, true ) ); //str_one

実行結果です。

find 'kore'
string(4) "zero"
string(6) "findme"
find '1'
string(7) "boolean"
string(7) "str_one"
find '33'
string(7) "boolean"
bool(false)

'kore'では0との比較で'kore'を数値に変換して0とするので、'zero'が返ります。

'1'ではtrueとの比較で、'1'がbooleanに変換されてtrueになり'boolean'が返ります。

'33'もtrueになるので、0以外はtrueのようです。'boolean'が返ります。bool(false)は見つからなかったということです。

valueにtrueやfalseがあると厳密でない比較はおかしなことになります。

存在しないkeyの要素をunsetしても問題ない

存在しないkeyでunsetしても問題はありません。key=>valueのマップを使う時は特に問題なしです。keyの比較は常に文字列なので、意外なことは少ないです。

echo "start  |";
$fruits = [
  'apple' => 'りんご',
  'orange' => 'みかん',
  'peach' => 'もも',
  'mandarin' => 'みかん',
];
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
echo "\ntomato |";
unset($fruits['tomato']);
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
echo "\nmand.. |";
unset($fruits['mandarin']);
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
echo "\n''     |";
unset($fruits['']);
foreach ($fruits as $key=>$val) echo $key,":",$val," ";
echo "\n0      |";
unset($fruits[0]);
foreach ($fruits as $key=>$val) echo $key,":",$val," ";

実行結果です

start  |apple:りんご orange:みかん peach:もも mandarin:みかん 
tomato |apple:りんご orange:みかん peach:もも mandarin:みかん 
mand.. |apple:りんご orange:みかん peach:もも 
''     |apple:りんご orange:みかん peach:もも 
0      |apple:りんご orange:みかん peach:もも 

数値keyの配列はすこしトリッキーな所があります。

数値のkeyは整数化してから文字列にしてkeyとして使います。

echo "start |";
$names = array("mike","tama","kuro","tora","mimi",);
foreach ($names as $key=>$val) echo $key,":",$val," ";
echo "\npiyo  |";
unset($names['piyo']);
foreach ($names as $key=>$val) echo $key,":",$val," ";
echo "\n3.6   |";
unset($names[3.6]);
foreach ($names as $key=>$val) echo $key,":",$val," ";
echo "\n'2.4' |";
unset($names['2.4']);
foreach ($names as $key=>$val) echo $key,":",$val," ";
echo "\n'2'   |";
unset($names['2']);
foreach ($names as $key=>$val) echo $key,":",$val," ";
echo "\n04    |";
unset($names[04]);
foreach ($names as $key=>$val) echo $key,":",$val," ";
echo "\n''    |";
unset($names['']);
foreach ($names as $key=>$val) echo $key,":",$val," ";
echo "\n0==''?|";
var_dump(0=='');        //true
var_dump($names[0]);    //mike
var_dump($names[false]);//mike
//var_dump($names[""]); //Notice:  Undefined index: 
echo "false |";
unset($names[false]);
foreach ($names as $key=>$val) echo $key,":",$val," ";

実行結果です

start |0:mike 1:tama 2:kuro 3:tora 4:mimi 
piyo  |0:mike 1:tama 2:kuro 3:tora 4:mimi 
3.6   |0:mike 1:tama 2:kuro 4:mimi 
'2.4' |0:mike 1:tama 2:kuro 4:mimi 
'2'   |0:mike 1:tama 4:mimi 
04    |0:mike 1:tama 
''    |0:mike 1:tama 
0==''?|bool(true)
string(4) "mike"
string(4) "mike"
false |1:tama 

小数点以下は切り捨てられ、0で始まるものは8進数として解釈されてから文字列になり、比較されます。

piyoはないので削除されません。

3.6は数値なので小数点以下を切り捨てて'3'として3:toraが削除されます。

'2.4'は文字列なのでそのまま比較され、該当がないので削除されません。

'2'は文字列なので'2'として2:kuroが削除されます。

04は数値で0から始まるので8進数として解釈されて4。文字列にして'4'なので4:mimiが削除されます。

''は文字列なのでそのまま''として使われ、'0'ではありませんので何も削除されません。

''と0を厳密でなく比較すると、数値に合わせて変換されるので、''は0と変換されてtrueになります。つまり等しいとされます。

添字にする時はfalseも0となりますから、$names[0]も$names[false]もmikeです。

array_search()で、valueとして存在しない値を探した時falseが返ります。そのkeyでunsetすると、0:mikeが削除されてしまうことになります。