7

我希望能够对多个列上的关联数组进行排序。更复杂的是,我希望能够为每个键/列设置特定的排序选项。我有一组类似于 db 查询结果集的数据,但它实际上并不是来自一个,所以我需要在 PHP 而不是 SQL 中对其进行排序。

[
    ['first_name' => 'Homer', 'last_name' => 'Simpson', 'city' => 'Springfield', 'state' => 'Unknown', 'zip' => '66735'],
    ['first_name' => 'Patty', 'last_name' => 'Bouvier', 'city' => 'Scottsdale', 'state' => 'Arizona', 'zip' => '85250'],
    ['first_name' => 'Moe', 'last_name' => 'Szyslak', 'city' => 'Scottsdale', 'state' => 'Arizona', 'zip' => '85255'],
    ['first_name' => 'Nick', 'last_name' => 'Riviera', 'city' => 'Scottsdale', 'state' => 'Arizona', 'zip' => '85255'],
];

我希望能够对其进行排序,类似于使用数据库查询可以完成的操作。哦,有时需要按数字指定列/键。

我想到的是与此类似的东西:

$sortOptions = array(
    array( 'city', SORT_ASC, SORT_STRING),
    array( 'zip', SORT_DESC, SORT_NUMERIC),
    array( 2, SORT_ASC, SORT_STRING) // 2='last_name'
);
$sorter = new MultiSort($data, $sortOptions);
$sortedData = $sorter->getSortedArray();
print_r($jmsSorted);

我想结束的是:

Array
(
    [0] => Array
        (
            [first_name] => Nick
            [last_name] => Riviera
            [city] => Scottsdale
            [state] => Arizona
            [zip] => 85255
        )

    [1] => Array
        (
            [first_name] => Moe
            [last_name] => Szyslak
            [city] => Scottsdale
            [state] => Arizona
            [zip] => 85255
        )

    [2] => Array
        (
            [first_name] => Patty
            [last_name] => Bouvier
            [city] => Scottsdale
            [state] => Arizona
            [zip] => 85250
        )

    [3] => Array
        (
            [first_name] => Homer
            [last_name] => Simpson
            [city] => Springfield
            [state] => Unknown
            [zip] => 66735
        )

)

更新:我认为理想情况下,解决方案将导致动态创建

array_multisort( $city, SORT_ASC, SORT_STRING, $zip, SORT_DESC, SORT_NUMERIC, $last_name, SORT_ASC, SORT_STRING, $inputArray);

问题是我不想在那里“硬编码”那些键名。我尝试根据示例 #3从最终使用的文档中对数据库结果进行排序创建一个解决方案,但我似乎无法找到一种方法来使用我的动态构建的参数列表。array_multisort()array_multisort()array_multisort()

我的尝试是将这些参数“链接”到一个数组中,然后

call_user_func_array( 'array_multisort', $functionArgs);

这导致

警告:array_multisort() 的参数 2 应为参考,值在...

4

5 回答 5

4

这应该适用于您描述的情况。

usort($arrayToSort, "sortCustom");

function sortCustom($a, $b)
{
    $cityComp = strcmp($a['city'],$b['city']);
    if($cityComp == 0)
    {
        //Cities are equal.  Compare zips.
        $zipComp = strcmp($a['zip'],$b['zip']);
        if($zipComp == 0)
        {
            //Zips are equal.  Compare last names.
            return strcmp($a['last_name'],$b['last_name']);
        }
        else
        {
            //Zips are not equal.  Return the difference.
            return $zipComp;
        }
    }
    else
    {
        //Cities are not equal.  Return the difference.
        return $cityComp;
    }
}

您可以将其压缩为一行,如下所示:

function sortCustom($a, $b)
{
    return ($cityComp = strcmp($a['city'],$b['city']) ? $cityComp : ($zipComp = strcmp($a['zip'],$b['zip']) ? $zipComp : strcmp($a['last_name'],$b['last_name'])));
}

就具有可定制的排序功能而言,您正在重新发明轮子。看一下array_multisort()功能。

于 2009-05-01T02:45:34.677 回答
4

在 PHP 5.3 中,数组中的每个参数在调用时都必须是一个array_multisort()引用call_user_func_array()

此函数对多维数组进行排序,并展示了一种构建正确工作的引用参数数组的方法。

function msort()
{
  $params = func_get_args();
  $array = array_pop($params);

  if (!is_array($array))
    return false;

  $multisort_params = array();
  foreach ($params as $i => $param) 
  {
    if (is_string($param)) 
    {
      ${"param_$i"} = array();
      foreach ($array as $index => $row) 
      {
        ${"param_$i"}[$index] = $row[$param];
      }
    }
    else 
      ${"param_$i"} = $params[$i];

    $multisort_params[] = &${"param_$i"};
  }
  $multisort_params[] = &$array; 

  call_user_func_array("array_multisort", $multisort_params);

  return $array;
}

例子:

$data 是问题中的给定数组

$sorted_data = msort('city', SORT_ASC, SORT_STRING, 'zip', SORT_DESC, SORT_NUMERIC, $data)
于 2010-05-11T13:26:30.700 回答
1

您可能想尝试使用usort。您所要做的就是创建一个函数来告诉分拣机如何对其进行排序。文档有更多关于如何做到这一点的信息。

于 2009-05-01T01:39:55.723 回答
1

这是我最终决定对多维数组进行排序的原因。上面的两个答案都很好,但我也在寻找灵活的东西。

我绝对不认为有任何一个“正确”的答案,但这符合我的需求并且很灵活。

正如您在我@link的评论中看到的那样,_usortByMultipleKeys()它改编自当前似乎不存在的 PHP 手册中的评论,但我相信http://www.php.net/manual/en/function.usort。 php#104398是原始评论的新版本。我还没有探索过使用这个新建议。

/**
 * Sort the resultSet.
 *
 * Usage: $sortOptions = array(
 *          'section', // Defaults to SORT_ASC
 *          'row' => SORT_DESC,
 *          'retail_price' => SORT_ASC);
 *        $results->sortResults($sortOptions);
 *
 * @param array $sortOptions    An array of sorting instructions
 */
public function sortResults(array $sortOptions)
{
    usort($this->_results, $this->_usortByMultipleKeys($sortOptions));
}


/**
 * Used by sortResults()
 *
 * @link http://www.php.net/manual/en/function.usort.php#103722
 */
protected function _usortByMultipleKeys($key, $direction=SORT_ASC)
{
    $sortFlags = array(SORT_ASC, SORT_DESC);
    if (!in_array($direction, $sortFlags)) {
        throw new InvalidArgumentException('Sort flag only accepts SORT_ASC or SORT_DESC');
    }
    return function($a, $b) use ($key, $direction, $sortFlags) {
        if (!is_array($key)) { //just one key and sort direction
            if (!isset($a->$key) || !isset($b->$key)) {
                throw new Exception('Attempting to sort on non-existent keys');
            }
            if ($a->$key == $b->$key) {
                return 0;
            }
            return ($direction==SORT_ASC xor $a->$key < $b->$key) ? 1 : -1;
        } else { //using multiple keys for sort and sub-sort
            foreach ($key as $subKey => $subAsc) {
                //array can come as 'sort_key'=>SORT_ASC|SORT_DESC or just 'sort_key', so need to detect which
                if (!in_array($subAsc, $sortFlags)) {
                    $subKey = $subAsc;
                    $subAsc = $direction;
                }
                //just like above, except 'continue' in place of return 0
                if (!isset($a->$subKey) || !isset($b->$subKey)) {
                    throw new Exception('Attempting to sort on non-existent keys');
                }
                if ($a->$subKey == $b->$subKey) {
                    continue;
                }
                return ($subAsc==SORT_ASC xor $a->$subKey < $b->$subKey) ? 1 : -1;
            }
            return 0;
        }
    };
}
于 2011-08-13T00:23:28.260 回答
0

我已经看到了许多使用array_multisort()inside of的 Stack Overflow 解决方案call_user_function_array(),但是从 PHP5.6 开始,splat 运算符可以取消嵌套排序函数并允许它被独占调用。

假设您的数据声明为$array

$array = [
    ['first_name' => 'Homer', 'last_name' => 'Simpson', 'city' => 'Springfield', 'state' => 'Unknown', 'zip' => '66735'],
    ['first_name' => 'Patty', 'last_name' => 'Bouvier', 'city' => 'Scottsdale', 'state' => 'Arizona', 'zip' => '85250'],
    ['first_name' => 'Moe', 'last_name' => 'Szyslak', 'city' => 'Scottsdale', 'state' => 'Arizona', 'zip' => '85255'],
    ['first_name' => 'Nick', 'last_name' => 'Riviera', 'city' => 'Scottsdale', 'state' => 'Arizona', 'zip' => '85255'],
];

我的代码片段将使用一组不确定的规则对您的数据进行正确排序。它允许使用一个单独的字符串来说明应该对哪一列进行 ASC 排序,或者您可以明确说明排序标志。它还被构建为允许列的数字引用,以防您事先不知道确切的列名。

$sortingRules = [
    'city',                       // sort by city column ASC (could have also been: ['city'])
    ['zip', 'desc', 'numeric'],   // then sort by zip column DESC treating as numbers
    [1, 'desc'],                  // then sort by last_name column DESC
];

我已经创建了两个函数声明,但是如果您的项目从不希望处理传入的丢失/无效列键,那么您可以消除该sanitizeColumnReference()函数并只使用$column = array_column($input, array_shift($rule));. 事实上,如果您删除所有异常抛出逻辑,该代码段的大小可能大约是其一半。

TLDR;这种技术的美妙之处在于,您需要做的就是在array_multisort()“splat 运算符”(....

function sanitizeColumnReference($row, $columnReference) {
    if (!isset($row[$columnReference]) && is_int($columnReference)) {
        $columnReference = array_keys($row)[$columnReference] ?? null;  // attempt to derive column name by position
        if ($columnReference === null) {
            throw new Exception('Failed to locate column by position using column reference: ' . $columnReference);
        }
    }
    return $columnReference;
}

function dynamicSort(&$input, $sortingRules) {
    if (!$input || !$sortingRules || !is_array($input) || !is_array($sortingRules)) {
        return;  // return silently
    }
    $firstRow = current($input);
    $sortingParams = [];
    foreach ($sortingRules as $rule) {
        $rule = (array)$rule; // permit the passing of a solitary string as a sorting rule
        $columnReference = sanitizeColumnReference($firstRow, array_shift($rule)); 
        $column = array_column($input, $columnReference);
        if (!$column) {
            throw new Exception('Failed to source sortable data from column reference: ' . $columnReference);
        }
        $sortingParams[] = $column;
        foreach ($rule as $flag) {
            $sortingParams[] = constant('SORT_' . strtoupper($flag));  // convert strings to usable CONSTANTs
        }
    }
    $sortingParams[] = &$input;
    // var_export($sortingParams);
    array_multisort(...$sortingParams); // unpack into native sorting function
}

以下是调用自定义/动态排序函数的方法:

dynamicSort($array, $sortingRules);  // this modifies by reference like native sorting functions

输出:(演示

array (
  0 => 
  array (
    'first_name' => 'Moe',
    'last_name' => 'Szyslak',
    'city' => 'Scottsdale',
    'state' => 'Arizona',
    'zip' => '85255',
  ),
  1 => 
  array (
    'first_name' => 'Nick',
    'last_name' => 'Riviera',
    'city' => 'Scottsdale',
    'state' => 'Arizona',
    'zip' => '85255',
  ),
  2 => 
  array (
    'first_name' => 'Patty',
    'last_name' => 'Bouvier',
    'city' => 'Scottsdale',
    'state' => 'Arizona',
    'zip' => '85250',
  ),
  3 => 
  array (
    'first_name' => 'Homer',
    'last_name' => 'Simpson',
    'city' => 'Springfield',
    'state' => 'Unknown',
    'zip' => '66735',
  ),
)
于 2020-06-17T00:03:48.943 回答