72

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

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)。

助けていただければ幸いです。

4

7 に答える 7

144

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

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

$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);

この作業の鍵は、比較される値を配列id内の s の位置にすることです。$order

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

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

于 2012-06-21T19:41:01.313 に答える
24

この追加要件に対するサラテの答えを拡張します。

並べ替えではなく配列に項目を追加するとどうなりますか? 私が指定したものの後に来る限り、それらがどのような順序で表示されるかは気にしません。

並べ替え関数に 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")
);

shuffle($array);  // for testing
var_dump($array); // before

usort($array, function ($a, $b) use ($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
        return 1;                       // $a > $b
    } else if ($b === false) {          // $b is a dont care
        return -1;                      // $a < $b
    } else {
        return $a - $b;                 // sort $a and $b ascending
    }
});
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"
  }                            |    }
}                              |  }
于 2012-06-22T14:53:38.720 に答える
2

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

于 2012-06-21T19:33:19.177 に答える
2

より効率的なソリューション

$dict = array_flip($order);
$positions = array_map(function ($elem) use ($dict) { return $dict[$elem['id']] ?? INF; }, $array);
array_multisort($positions, $array);

すべての比較でポジションを再計算しない

配列が大きい場合、または ID の取得にコストがかかる場合、usort()比較ごとに ID を再計算するため、使用がうまくいかないことがあります。array_multisort()事前に計算された位置 (以下の例のmediumsortまたはを参照) を試してみてくださいfastsort。これはそれほど複雑ではありません。

また、各比較で順序配列の id を検索しても (受け入れられた回答のように)、比較ごとに反復処理されるため、パフォーマンスは向上しません。一度計算してみてください。

以下のコード スニペットでは、主な 3 つの並べ替え関数を確認できます。

  • slowsort
    受け入れられた答え。比較ごとに位置を検索します。
  • mediumsort
    slowsort事前に位置を計算することで 改善
  • fastsort
    mediumsortまとめて検索することを回避することで 改善されました。

これらは、フォールバック値 を提供することにより、順序で指定されていない ID を持つ要素を処理することに注意してくださいINF。注文配列が元の配列の ID と 1 対 1 で一致する場合は、まとめて並べ替えることを避け、要素を正しい位置に挿入するだけです。cheatsortまさにそれを行う機能を追加しました。

より一般的には、配列を重みで並べ替えることができます (weightedsort例を参照)。優れたパフォーマンスを実現するために、重みの計算は必ず 1 回だけにしてください。

パフォーマンス (長さ 1000 の配列の場合)

fastsort     about  1 ms
mediumsort   about  3 ms
slowsort     about 60 ms

ヒント: 配列が大きくなると、違いはさらに悪化します。

ソート機能比較

<?php

/**
 * accepted answer
 *
 * re-evaluate position in order on each comparison
 */
function slowsort(&$array, $order, $key = 'id')
{
  usort($array, function ($a, $b) use ($order, $key) {
    $pos_a = array_search($a[$key], $order);
    $pos_b = array_search($b[$key], $order);
    return $pos_a - $pos_b;
  });
}

/**
 * calculate element positions once
 */
function mediumsort(&$array, $order, $key = 'id')
{
  $positions = array_map(function ($elem) use ($order, $key) {
    return array_search($elem[$key], $order);
  }, $array);
  array_multisort($positions, $array);
}

/**
 * calculate positions without searching
 */
function fastsort(&$array, $order, $key = 'id')
{
  $dict = array_flip($order);
  $positions = array_map(function ($elem) use ($dict, $key) {
    return $dict[$elem[$key]] ?? INF;
  }, $array);
  array_multisort($positions, $array);
}

/**
 * when each order element gets used exactly once, insert elements directly
 */
function cheatsort(&$array, $order, $key = 'id')
{
  $dict = array_flip($order);
  $copy = $array;
  foreach ($copy as $elem) {
    $pos = $dict[$elem[$key]];
    $array[$pos] = $elem;
  }
}

/**
 * Sort elements in $array by their weight given by $weight_func
 * 
 * You could rewrite fastsort and mediumsort by replacing $position by a weight function
 */
function weightedsort(&$array, $weight_func)
{
  $weights = array_map($weight_func, $array);
  array_multisort($weights, $array);
}



/**
 * MEASUREMENTS
 */

/**
 * Generate the sorting problem
 */
function generate($size = 1000)
{
  $order = array();
  $array = array();

  for ($i = 0; $i < $size; $i++) {
    $id = random_int(0, PHP_INT_MAX);
    $order[] = $id;
    $array[] = array('id' => $id);
  }
  shuffle($order);
  return [$array, $order];
}

/**
 * Time $callable in ms
 */
function time_it($callable)
{
  $then = microtime(true);
  $callable();
  $now = microtime(true);
  return 1000 * ($now - $then);
}

/**
 * Time a sort function with name $sort_func
 */
function time_sort($sort_func) 
{
  echo "Timing $sort_func", PHP_EOL;
  [$array, $order] = generate();
  echo time_it(function () use ($sort_func, &$array, $order) {
    $sort_func($array, $order);
  }) . ' ms' . PHP_EOL;
}

time_sort('cheatsort');
time_sort('fastsort');
time_sort('mediumsort');
time_sort('slowsort');
于 2020-04-17T08:34:38.200 に答える