ラベル 配列 の投稿を表示しています。 すべての投稿を表示
ラベル 配列 の投稿を表示しています。 すべての投稿を表示

論理型の値を保持する配列の先頭要素を取得する際の注意点

過去の記事reset() を使って配列の先頭要素を取得する方法について書いたが、論理型の値を保持する配列に対しては注意が必要である。

論理型の配列データの先頭要素が false の場合、reset()false を返すが、reset() は空の配列に対しても false を返すため、どちらも同じ false も得てしまう。


function head($array)
{
  return reset($array); // 配列の先頭要素を取得
}

$array1 = [false, true, false];
$array2 = [];

$v1 = head($array1);
var_dump($v1);
/* var_dump 出力
bool(false)
*/
$v2 = head($array2);
var_dump($v2);
/*
bool(false)
*/
var_dump($v1 === $v2);
/*
bool(true)
*/

どちらも false を得てしまう問題を回避するには、reset() を呼ぶ際に count() で配列の要素数を確認するか、key() の返り値が null でないことを確認する必要がある。


function head($array)
{
  return reset($array); // 配列の先頭要素を取得
}

$array1 = [false, true, false];
$array2 = [];

$v1 = count($array1) ? head($array1) : null;
$v2 = count($array2) ? head($array2) : null;
var_dump($v1 === $v2);
/*
bool(false)
*/

$v1 = !is_null(key($array1)) ? head($array1) : null;
$v2 = !is_null(key($array2)) ? head($array2) : null;
var_dump($v1 === $v2);
/*
bool(false)
*/

配列から空の要素を削除する

まず、変数が空であるかどうかのチェックを empty() に委ねる場合を考える。empty() では次のような値は空とみなされる。

  • "" (空文字列)
  • 0 (整数 の 0)
  • 0.0 (浮動小数点数の 0)
  • "0" (文字列 の 0)
  • NULL
  • FALSE
  • array() (空の配列)

これらは boolean に変換すると全て false とみなされる値となる。

また、array_filter() は、コールバック関数が与えられなかった場合、配列要素の中で boolean に変換して false に等しいものを削除してくれる。

よって、empty() で空とみなされる値を削除したい場合は、array_filter() を使って配列から空の要素を削除することができる。


// booleanに変換すると全てfalseとみなされる値
$array = ["", 0, 0.0, "0", NULL, false, []];

foreach ($array as $v)
{
  var_dump((bool)$v);
/*
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
*/
}

// 配列要素の中でbooleanに変換してfalseに等しいものを削除
$array = array_filter($array);
var_dump($array);
/*
array(0) {
}
*/

次に、変数が空であるかどうかを empty() だけでチェックできない場合を考える。例えば、数値や論理値、文字列の "0" を空とみなさない場合である。

独自に変数の値が空かどうかを判定する blank() を定義する。この関数の定義は、Laravelのヘルパー関数 blank() を参考にした。
framework/helpers.php at master · laravel/framework · GitHub

そして、blank() の返り値の逆を返す not_blank() を定義し、array_filter() のコールバック関数に指定すると、blank() で空とみなされる要素が削除された配列を得ることができる。


function blank($value)
{
  if (is_null($value)) {
    return true;
  }

  if (is_string($value)) {
    return trim($value) === '';
  }

  // 数値や論理値は空とみなさない
  if (is_numeric($value) || is_bool($value)) {
    return false;
  }

  return empty($value);
}

function not_blank($value)
{
  return !blank($value);
}

$array = ["", 0, 0.0, "0", NULL, false, []];

// blank()で空とみなす要素を削除
$array = array_filter($array, "not_blank");
var_dump($array);
/*
blank()では数値や論理値、文字列の"0"を空とみなさないので、削除されない
array(4) {
  [1]=>
  int(0)
  [2]=>
  float(0)
  [3]=>
  string(1) "0"
  [5]=>
  bool(false)
}
*/

配列から特定の値を持つ要素を検索して削除する

配列から特定の値を持つ要素を検索して削除する場合、複数の方法が考えられる。下記は、foreach を使って配列の値を確認していく一番シンプルな方法の例。


$array = [
  ['id' => 1000, 'name' => "aaa"],
  ['id' => 1001, 'name' => "bbb"],
  ['id' => 1002, 'name' => "ccc"],
];

$del_id = 1001;

foreach ($array as $i => $v)
{
  if ($array[$i]["id"] == $del_id)
    unset($array[$i]);
}

var_dump($array);
/* var_dump 出力
array(2) {
  [0]=>
  array(2) {
    ["id"]=>
    int(1000)
    ["name"]=>
    string(3) "aaa"
  }
  [2]=>
  array(2) {
    ["id"]=>
    int(1002)
    ["name"]=>
    string(3) "ccc"
  }
}
*/

また、別の方法として array_search() を使う方法がある。array_search() は、指定した値を配列内から探し、最初に見つかった要素のキーを返す関数である。

ただし、サンプルコードのような多次元配列に対して array_search() を使う場合、1つ工夫が必要で array_column() を使って検索対象にしたいデータを抜き出す必要がある。


// array_search()の例

$array = [
  ['id' => 1000, 'name' => "aaa"],
  ['id' => 1001, 'name' => "bbb"],
  ['id' => 1002, 'name' => "ccc"],
];

$del_id = 1001;

var_dump(array_column($array, "id"));
/*
array_columnを使うと、多次元配列から"id"キーに対応するデータを抜き出すことができる
array(3) {
  [0]=>
  int(1000)
  [1]=>
  int(1001)
  [2]=>
  int(1002)
}
*/

// "id"キーに対応する要素の値が$del_idを持つものを検索
$i = array_search($del_id, array_column($array, "id"));

if ($i)
  unset($array[$i]);

var_dump($array);
/*
array(2) {
  [0]=>
  array(2) {
    ["id"]=>
    int(1000)
    ["name"]=>
    string(3) "aaa"
  }
  [2]=>
  array(2) {
    ["id"]=>
    int(1002)
    ["name"]=>
    string(3) "ccc"
  }
}
*/

さらに別の方法として、array_keys() を使う方法がある。array_keys() は、2番目の引数を指定するとその指定した値を持つ要素のキーのみを返してくれる。array_keys() の返り値は、配列であることに注意。

また、array_search() のときと同様に、多次元配列に対しては、array_column() を使って検索対象にしたいデータを抜き出す必要がある。


// array_keys()の例

$array = [
  ['id' => 1000, 'name' => "aaa"],
  ['id' => 1001, 'name' => "bbb"],
  ['id' => 1002, 'name' => "ccc"],
];

$del_id = 1001;

$keys = array_keys(array_column($array, "id"), $del_id);
foreach ($keys as $i)
{
  unset($array[$i]);
}

var_dump($array);
/*
array(2) {
  [0]=>
  array(2) {
    ["id"]=>
    int(1000)
    ["name"]=>
    string(3) "aaa"
  }
  [2]=>
  array(2) {
    ["id"]=>
    int(1002)
    ["name"]=>
    string(3) "ccc"
  }
}
*/

元の配列データを変更せずに先頭の要素を取得する

配列の先頭の要素を取得する関数として array_shift() がある。

ただし、array_shift() は先頭の要素を配列から取り除いてしまうので、「元の配列データを変更せずに」という制約がある場合、array_shift() は使えない。

このような場合、reset() が使える。関数名からは想像が付かないが、reset() は、配列の内部ポインタを先頭の要素に戻し、そして、「配列の最初の要素の値」を返してくれる。

reset() を使えば、元の配列データを変更せず、また、先頭のキーが不明な場合でも、先頭の要素を取得できる。


$array = [
  'status_130134' => ['text' => "hi",  'date' => "20200101"]
];

$v = reset($array); // 配列の先頭の要素を取得

var_dump($v);
/* var_dump 出力
array(2) {
  ["text"]=>
  string(2) "hi"
  ["date"]=>
  string(8) "20200101"
}
*/
var_dump($array);
/* var_dump 出力
元の配列データが変更されていないことを確認
array(1) {
  ["status_130134"]=>
  array(2) {
    ["text"]=>
    string(2) "hi"
    ["date"]=>
    string(8) "20200101"
  }
}
*/

reset() という関数名と実際の処理の意図が合っていないことで、コードの可読性を懸念する場合、reset() を適当な名前を付けた関数でラップしてもよいだろう。


// reset()をheadという名前を付けた関数でラップ
function head($array)
{
  return reset($array);
}

$array = [
  'status_130134' => ['text' => "hi",  'date' => "20200101"]
];

$v = head($array); // 配列の先頭の要素を取得
var_dump($v);
/*
array(2) {
  ["text"]=>
  string(2) "hi"
  ["date"]=>
  string(8) "20200101"
}
*/

実際、Laravelのヘルパー関数 head()(配列の先頭の要素を返す関数)は、reset() の返り値を返す関数として実装されている。Laravelのヘルパー関数のソースコードを確認したい場合は、下記のリンクからソースコードを読むことができる。
framework/helpers.php at master · laravel/framework · GitHub