PHP:排序/搜索具有2个键的数据(或由子数组键组成的数组)


PHP: sorting/searching data that has 2 keys (or an array of arrays by a sub-array key)?

我遇到过几次,从来没有找到最好的方法来解决它。用一个具体的例子来说明是最容易的。样本数据:

product_id     display_name         display_order
----------     ------------         -------------
"samgal3"      "Samsung Galaxy 3"        0
"motorazrh"    "Motorola Razr HD"        1
"iphone5"      "Apple iphone 5"          2
etc

实际的数组通常很小(<20个元素),但并非总是如此,并且保证了唯一的键/值。每个项目都有一个唯一的排序键(用于html表/枚举)、一个唯一的内部键(用于项目查找)和一个人类可读的显示名称。

当在表单上使用选项列表时,我通常会遇到这个问题。相同的数据既用于填充表单上的下拉框,也用于验证提交的$GET/POST数据。在生成表单时,需要按"sort"顺序枚举/列出表单,以便按顺序创建SELECT框选项。当提交表单时,需要通过'product_id'进行搜索(以验证"…&action=view&product_id=elephant…"是列表中的产品)。

如果我使用'sort'=>数组(其他数据)作为键,那么通过'sort'显示很容易,但在$data[*]['product_id']中搜索很难*(即识别$ key,如果它存在,具有$data[$ key]['product_id'] == 'htcvox')*。如果我使用'product_id'=>数组(其他数据)作为键,然后搜索'samgal3'是否在数组中,并找到它的数据很容易,但没有简单的方法来通过'排序'来步行/枚举数组来创建表单。

我想我可以做一个自定义搜索/排序在$data中的任何成员$ I的搜索/排序键是$ I ['product_id']或$ I ['sort'],但它是笨拙的,我从来没有这样做过。因为代码是开源的,所以简单性很重要。

我希望将数据编码为数组的数组,像这样:

$data = array(
    0 => array('product_id'=>'samgal3',   'display_name' => 'Samsung Galaxy 3'),
    1 => array('product_id'=>'motorazrh', 'display_name' => 'Motorola Razr HD'),
    ...

$data = array(
    'samgal3'   => array('sort'=>0, 'display_name' => 'Samsung Galaxy 3'),
    'motorazrh' => array('sort'=>1, 'display_name' => 'Motorola Razr HD'),
    ...

把同样的问题换一种方式,给定一个二维数组:$data = array(array1, array2, array3, ....); where所有的array1, array2, array3,…包含一个键/字段与固定的名称,是否有一个简单的方法来搜索/排序嵌套数组$ARRAY[**]['named_field']?

实际上您需要的是在单个数组中添加多个索引,就像在关系数据库中的单个表中添加多个索引一样。(排序是一个独立但相关的问题。)

让我们从这个基本的数据结构开始,这是一组简单的数组,键没有特别重要:

$data = array(
    array('display_order'=> 0, 'product_id'=>'samgal3',   'display_name' => 'Samsung Galaxy 3'),
    array('display_order'=> 1, 'product_id'=>'motorazrh', 'display_name' => 'Motorola Razr HD'),
    array('display_order'=> 2, 'product_id'=>'a', 'display_name' => 'a'),
    array('display_order'=> 3, 'product_id'=>'c', 'display_name' => 'c'),
    array('display_order'=> 4, 'product_id'=>'d', 'display_name' => 'd'),
    array('display_order'=> 5, 'product_id'=>'b', 'display_name' => 'b'),
    array('display_order'=> 6, 'product_id'=>'q', 'display_name' => 'q'),
    array('display_order'=> 7, 'product_id'=>'f', 'display_name' => 'f'),
);

您可以轻松地创建显式索引,然后使用它来获取$data项:

$product_id_idx = array_flip(array_map(function($item){return $item['product_id'];}, $data));
$samgal3_array = $data[$product_id_idx['samgal3']]; // same as $data[0]
对于排序,您可以使用经常被遗忘的array_multisort。请查看文档中的示例3。诀窍是创建要排序的数组,并将完整的数据集作为最后一个参数。例如:
array_multisort(array_keys($product_id_idx), SORT_ASC, SORT_STRING, $data);

$data现在按产品键排序。然而,$data的原始数字数组键丢失了,这意味着我们的$product_id_idx不再可用了。因此,如果您想继续使用索引,最好对数据数组的副本进行排序。

我们可以将这两种方法合并到一个类中,以保持我们的理智:

class MultiIndex {
    protected $array;
    protected $indexes = array();
    protected $indexdefs = array();
    function __construct($array, $indexdefs)
    {
        $this->array = $array;
        $this->indexdefs = $indexdefs;
        foreach ($indexdefs as $name => $column) {
            $this->indexes[$name] = $this->makeIndex($column);
        }
    }
    function makeIndex($column)
    {
        $index = array();
        foreach ($this->array as $k => $v) {
            $index[$v[$column]] = $k;
        }
        return $index;
    }
    function get($key, $index=null)
    {
        $datapk = ($index===null) ? $key : $this->indexes[$index][$key];
        return $this->array[$datapk];
    }
    function getIndex($index)
    {
        return $this->indexes[$index];
    }
    function getData()
    {
        return $this->array;
    }
    function indexedBy($index)
    {
        $indexed = array();
        $indexedcolumn = $this->indexdef[$index];
        foreach ($this->indexes[$index] as $indexk => $arrayk) {
            $newarray = $this->array[$arrayk];
            unset($newarray[$indexedcolumn]);
            $indexed[$indexk] = $newarray;
        }
        return $indexed;
    }
    function sortedBy(/*multisort args*/)
    /* with strings converted to arrays corresponding to index of same name */
    {
        $args = func_get_args();
        foreach ($args as $n => $arg) {
            if (is_string($arg)) {
                $args[$n] = array_keys($this->indexes[$arg]);
            }
        }
        $sorted = $this->array;
        $args[] = $sorted;
        call_user_func_array('array_multisort', $args);
        return $sorted;
    }
}

使用示例:

$dataidx = new MultiIndex($data, array('id'=>'product_id', 'disp'=>'display_order'));
var_export($dataidx->sortedBy('disp', SORT_STRING, SORT_ASC));
var_export($dataidx->indexedBy('id'));
var_export($dataidx->get('samgal3', 'id'));

这应该是一个非常基本的基础,对于小数组来说应该很好。为了简单起见,MultiIndex的数据是不可变的,键总是数组索引。增强这一点的一些明显方法是:

  1. 使$indexdefs接受一个可调用对象,它返回一个项目的键,而不仅仅是一个字符串/int命名数组键。这允许您在任何形状的数据上创建索引,甚至创建与数据不直接对应的索引。(例如,按显示名称中的字符数索引,或按日期和时间分开的数组的日期+时间索引,等等)
  2. 删除索引键只有一个值的要求。(现在你做的所有索引都被认为是唯一的。)
  3. 允许您为索引声明SORT_*数据类型,并将其自动包含在MultiIndex::sortedBy()array_multisort参数中。
  4. 包括稀疏索引:如果你有非常常见或非常罕见的值,或者只是有一个非常大的数据集,你想节省内存,你可以这样做,所以只有某些值被索引。如果在索引中没有找到项,则退回到对数据中未索引项的完整扫描。
  5. 添加合理接口实现。
  6. 允许MultiIndex有多个后端,这样你就可以在任何类似数组的结构上有多个索引(例如dbm键值存储,DynamoDB云存储,memcached等),并使用相同的对象接口操作它们。
  7. 使MultiIndex保存可变数据,并在数据变化时自动增量更新索引。

我将把这段代码放在一个要点上,以便于分叉。

使用http://www.php.net/usort生成用户自定义的排序。

的例子:

<?php
//added a few more values
$data = array(
    0 => array('product_id'=>'samgal3',   'display_name' => 'Samsung Galaxy 3'),
    1 => array('product_id'=>'motorazrh', 'display_name' => 'Motorola Razr HD'),
    2 => array('product_id'=>'a', 'display_name' => 'a'),
    3 => array('product_id'=>'c', 'display_name' => 'c'),
    4 => array('product_id'=>'d', 'display_name' => 'd'),
    5 => array('product_id'=>'b', 'display_name' => 'b'),
    6 => array('product_id'=>'q', 'display_name' => 'q'),
    7 => array('product_id'=>'f', 'display_name' => 'f'),
);
function cmp($a,$b){
    return strcasecmp($a['display_name'],$b['display_name']);
}
usort($data,'cmp');
var_export($data);

http://codepad.viper - 7. - com/3my8nu