我遇到过几次,从来没有找到最好的方法来解决它。用一个具体的例子来说明是最容易的。样本数据:
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
的数据是不可变的,键总是数组索引。增强这一点的一些明显方法是:
- 使
$indexdefs
接受一个可调用对象,它返回一个项目的键,而不仅仅是一个字符串/int命名数组键。这允许您在任何形状的数据上创建索引,甚至创建与数据不直接对应的索引。(例如,按显示名称中的字符数索引,或按日期和时间分开的数组的日期+时间索引,等等) - 删除索引键只有一个值的要求。(现在你做的所有索引都被认为是唯一的。)
- 允许您为索引声明
SORT_*
数据类型,并将其自动包含在MultiIndex::sortedBy()
的array_multisort
参数中。 - 包括稀疏索引:如果你有非常常见或非常罕见的值,或者只是有一个非常大的数据集,你想节省内存,你可以这样做,所以只有某些值被索引。如果在索引中没有找到项,则退回到对数据中未索引项的完整扫描。
- 添加合理接口实现。
- 允许
MultiIndex
有多个后端,这样你就可以在任何类似数组的结构上有多个索引(例如dbm键值存储,DynamoDB云存储,memcached等),并使用相同的对象接口操作它们。 - 使
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