web-dev-qa-db-ja.com

カスタム配列による配列のPHP配列のソート

私は配列の配列を持っています:

Array ( 
    [0] => Array (
        [id] = 7867867,
        [title] = 'Some Title'),
    [1] => Array (
        [id] = 3452342,
        [title] = 'Some Title'),
    [2] => Array (
        [id] = 1231233,
        [title] = 'Some Title'),
    [3] => Array (
        [id] = 5867867,
        [title] = 'Some Title')
)

特定の順序で進む必要性:

  1. 3452342
  2. 5867867
  3. 7867867
  4. 1231233

それをどうやってやるの?私は以前に配列をソートし、それに関する他の記事をたくさん読んでいますが、それらは常に比較ベースです(つまり、valueA <valueB)。

ヘルプは大歓迎です。

52
Honus Wagner

usort()を使用して、配列のソート方法を正確に指定できます。この場合、$order配列を比較関数内で使用できます。

以下の例では、生活を楽にするために closure を使用しています。

$order = array(3452342, 5867867, 7867867, 1231233);
$array = array(
    array('id' => 7867867, 'title' => 'Some Title'),
    array('id' => 3452342, 'title' => 'Some Title'),
    array('id' => 1231233, 'title' => 'Some Title'),
    array('id' => 5867867, 'title' => 'Some Title'),
);

usort($array, function ($a, $b) use ($order) {
    $pos_a = array_search($a['id'], $order);
    $pos_b = array_search($b['id'], $order);
    return $pos_a - $pos_b;
});

var_dump($array);

この動作の鍵は、比較される値を持っていることです。つまり、$order配列内のidsの位置です。

比較関数は、$order配列内で比較される2つのアイテムのIDの位置を見つけることによって機能します。 $a['id']$b['id']配列の$orderの前にある場合、関数の戻り値は負になります($aのほうが上に「浮いている」)。 $a['id']$b['id']の後に来る場合、関数は正の数を返します($aの方が大きいため、「沈み込みます」)。

最後に、クロージャーを使用する特別な理由はありません。これらの種類のスローアウェイ関数をすばやく書くための、まさに私の頼りになる方法です。同様に、通常の名前付き関数を使用できます。

107
salathe

この追加要件の salatheの答え の拡張:

ソートではなく配列にアイテムを追加するとどうなりますか?私が指定した順序の後に来る限り、それらの順序は気にしません。

ソート機能に2つの追加条件を追加する必要があります。

  1. 「気にしない」アイテムは、「気にかける」アイテムよりも大きいと見なす必要があります
  2. 2つの「ドントケア」項目は等しいと見なされる必要があります

したがって、修正されたコードは次のようになります。

$order = array(
    3452342,
    5867867,
    7867867,
    1231233
);
$array = array(
    array("id" => 7867867, "title" => "Must Be #3"),
    array("id" => 3452342, "title" => "Must Be #1"),
    array("id" => 1231233, "title" => "Must Be #4"),
    array("id" => 5867867, "title" => "Must Be #2"),
    array("id" => 1111111, "title" => "Dont Care #1"),
    array("id" => 2222222, "title" => "Dont Care #2"),
    array("id" => 3333333, "title" => "Dont Care #3"),
    array("id" => 4444444, "title" => "Dont Care #4")
);
function custom_compare($a, $b){
    global $order;
    $a = array_search($a["id"], $order);
    $b = array_search($b["id"], $order);
    if($a === false && $b === false) { // both items are dont cares
        return 0;                      // a == b
    }
    else if ($a === false) {           // $a is a dont care item
        return 1;                      // $a > $b
    }
    else if ($b === false) {           // $b is a dont care item
        return -1;                     // $a < $b
    }
    else {
        return $a - $b;
    }
}
shuffle($array);  // for testing
var_dump($array); // before
usort($array, "custom_compare");
var_dump($array); // after

出力:

Before                         |  After
-------------------------------+-------------------------------
array(8) {                     |  array(8) {
  [0]=>                        |    [0]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(4444444)               |      int(3452342)
    ["title"]=>                |      ["title"]=>
    string(12) "Dont Care #4"  |      string(10) "Must Be #1"
  }                            |    }
  [1]=>                        |    [1]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(3333333)               |      int(5867867)
    ["title"]=>                |      ["title"]=>
    string(12) "Dont Care #3"  |      string(10) "Must Be #2"
  }                            |    }
  [2]=>                        |    [2]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(1231233)               |      int(7867867)
    ["title"]=>                |      ["title"]=>
    string(10) "Must Be #4"    |      string(10) "Must Be #3"
  }                            |    }
  [3]=>                        |    [3]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(1111111)               |      int(1231233)
    ["title"]=>                |      ["title"]=>
    string(12) "Dont Care #1"  |      string(10) "Must Be #4"
  }                            |    }
  [4]=>                        |    [4]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(5867867)               |      int(2222222)
    ["title"]=>                |      ["title"]=>
    string(10) "Must Be #2"    |      string(12) "Dont Care #2"
  }                            |    }
  [5]=>                        |    [5]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(2222222)               |      int(1111111)
    ["title"]=>                |      ["title"]=>
    string(12) "Dont Care #2"  |      string(12) "Dont Care #1"
  }                            |    }
  [6]=>                        |    [6]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(3452342)               |      int(3333333)
    ["title"]=>                |      ["title"]=>
    string(10) "Must Be #1"    |      string(12) "Dont Care #3"
  }                            |    }
  [7]=>                        |    [7]=>
  array(2) {                   |    array(2) {
    ["id"]=>                   |      ["id"]=>
    int(7867867)               |      int(4444444)
    ["title"]=>                |      ["title"]=>
    string(10) "Must Be #3"    |      string(12) "Dont Care #4"
  }                            |    }
}                              |  }
16
Salman A

array_search()の反復呼び出しでメソッドを使用している他の答えは、可能な限り効率的ではありません。 「順序」ルックアップ配列を再構築/反転することにより、すべてのarray_search()呼び出しを完全に省略することができ、タスクがより効率的かつ簡潔になります。最も近代的な「宇宙船演算子」(_<=>_)を使用しますが、以前の手法は比較行でも同じように機能します。

方法#1-usortがすべてのid値が_$order_に存在する場合( デモ =)

_$order=array_flip([3452342,5867867,7867867,1231233]);  // restructure with values as keys, and keys as order (ASC)
// generating $order=[3452342=>0,5867867=>1,7867867=>2,1231233=>3];
$array=[
    ['id'=>7867867,'title'=>'Some Title'],
    ['id'=>3452342,'title'=>'Some Title'],
    ['id'=>1231233,'title'=>'Some Title'],
    ['id'=>5867867,'title'=>'Some Title']
];

usort($array,function($a,$b)use($order){
    return $order[$a['id']]<=>$order[$b['id']];
    // when comparing ids 3452342 & 1231233, the actual comparison is 0 vs 3
});
// uasort() if you want to preserve keys

var_export($array);
_

メソッド#2-usort値が_$order_に存在しない場合idデモ
*注、isset()array_search()よりも安価な呼び出しです

_$order=array_flip([3452342,5867867,7867867,1231233]);  // restructure with values as keys, and keys as order (ASC)
// generating $order=[3452342=>0,5867867=>1,7867867=>2,1231233=>3];
$outlier=1+max($order);
// generating $outlier=4
$array=[
    ['id'=>7867867,'title'=>'Some Title'],
    ['id'=>3452342,'title'=>'Some Title'],
    ['id'=>'foo','title'=>'Some Title'],
    ['id'=>1231233,'title'=>'Some Title'],
    ['id'=>'bar','title'=>'Some Title'],
    ['id'=>5867867,'title'=>'Some Title']
];

usort($array,function($a,$b)use(&$order,$outlier){  // make $order modifiable with &
    if(!isset($order[$a['id']])){$order[$a['id']]=$outlier;}  // update lookup array with [id]=>[outlier number]
    if(!isset($order[$b['id']])){$order[$b['id']]=$outlier;}  // and again
    return $order[$a['id']]<=>$order[$b['id']];
});

var_export($array);
_

代替方法#2-usortの一部のid値が_$order_に存在しない場合

...また、場合によっては、isset()の繰り返し二重呼び出しを避けることは、usort()を呼び出す前に_$order_配列を完全に準備するよりも魅力的ではない場合があることにも触れたいと思います。

このワンライナーにより、id値が欠落しないことが保証されるため、ソート関数内の比較行以外の必要性がなくなります。 ( フルスニペットデモ

_$order=array_replace(array_fill_keys(array_column($array,'id'),$outlier),$order);
_
4
mickmackusa

インデックスの関連付けを維持する場合は、独自の比較関数を定義し、 usort または uasort を使用する必要があります。

4
Nev Stokes

@salathe salatheのusortが何をしているのか理解するのに苦労しているあなたのために:

$ arrayの各アイテムは、トーナメントの「チャンピオン」であり、新しい配列の先頭にあります(番号1の代わりに番号0にすることを除く)。

$ aはホームチャンピオンで、$ bは試合の対戦相手チャンピオンです。

コールバックからの$ pos_aと$ pos_bは、チャンピオンaとbの戦いで使用される属性です。この場合、この属性は$ orderのチャンピオンIDのインデックスです。

それから帰りに戦いがあります。ここで、属性をより多くまたはより少なくする方が良いかどうかを確認します。国内の戦いでは、ホームチャンピオンは負の数を望んでいるので、彼はより早くアレイに参加できます。アウェイチャンピオンは正の数を望んでいます。そして、0があればそれは同点です。

したがって、アウェイチャンピオンの属性($ orderのインデックス)がホームチームの属性から減算されるというこのアナロジーに従って、アウェイチャンピオンの属性が大きいほど、正の数を獲得して勝つ可能性は低くなります。ただし、属性の使用方法を逆にすると、ホームチャンピオンの属性がアウェイチャンピオンの属性から差し引かれます。この場合、アウェイチャンピオンの数字が大きいほど、マッチエンドが正数になる可能性が高くなります。

コードは次のようになります。

注:コードは、実際のトーナメントが誰が最初に取得するかを決定するために多くのバトルを行うように何度も実行されます(つまり、0 /配列の開始)。

//tournament with goal to be first in array
    usort($champions, function ($home, $away) use ($order) {
        $home_attribute = array_search($a['id'], $order);
        $away_attribute = array_search($b['id'], $order);
        //fight with desired outcome for home being negative and away desiring positive
        return $home_attribute - $away_attribute;
    });
1
Jason Basanese

並べ替えなしでも取得できます。

重複したIDはありません。

<?php

    $order = array(3452342, 5867867, 7867867, 1231233);
    $array = array(
        array('id' => 7867867, 'title' => 'Some Title'),
        array('id' => 3452342, 'title' => 'Some Title'),
        array('id' => 1231233, 'title' => 'Some Title'),
        array('id' => 5867867, 'title' => 'Some Title'),
    );

    $order = array_flip($order);
    $array = array_column($array,null,"id");
    $result = array_replace($order,$array);
    var_dump(array_values($result));

IDが重複している場合、

<?php

    $order = array(3452342, 5867867, 7867867, 1231233);
    $array = array(
        array('id' => 7867867, 'title' => 'Some Title'),
        array('id' => 3452342, 'title' => 'Some Title'),
        array('id' => 1231233, 'title' => 'Some Title'),
        array('id' => 5867867, 'title' => 'Some Title'),
    );

    $order_dict = array_flip($order);
    $order_dict = array_combine($order,array_fill(0,count($order),[]));
    foreach($array as $item){
        $order_dict[$item["id"]][] = $item;
    }
    //$order_dict = array_filter($order_dict);  // if there is empty item on some id in $order array
    $result = [];
    foreach($order_dict as $items){
        foreach($items as $item){
            $result[] = $item;
        }
    }
    var_dump($result);
0
Kris Roofe