【问题标题】:Sorting array - child after its parent, but keep parents positions to each other排序数组 - 子在其父之后,但保持父位置彼此
【发布时间】:2013-11-21 01:06:29
【问题描述】:

我从数据库数组中得到这样的(用户选择按价格排序):

Array
(
    [0] => Array
        (
            [id] => 812
            [price] => 0
            [par_id] => 310
        )
    [1] => Array
        (
            [id] => 445
            [price] => 3400
            [par_id] => 0
        )
    [2] => Array
        (
            [id] => 1102
            [price] => 3500
            [par_id] => 0
        )
    [3] => Array
        (
            [id] => 310
            [price] => 3700
            [par_id] => 0
        )
    [4] => Array
        (
            [id] => 311
            [price] => 3700
            [par_id] => 310
        )
    [5] => Array
        (
            [id] => 800
            [price] => 3900
            [par_id] => 310
        )
)

我需要将此数组排序为具有 par_id == 0 的项目,并且在它们之下是其子项 child.par_id = parent.id 。要获得具有这些条件的数组,我使用函数 usort():

usort($array, "cmp");

function cmp($a, $b) {
  if ( $a['id'] == $b['id'] ) {
    return 0;

  } else if ( $a['par_id'] ) {
    if ( $a['par_id'] == $b['par_id'] ) {
       return ( $a['id'] < $b['id'] ? -1 : 1 );
    } else {
      return ( $a['par_id'] >= $b['id'] ? 1 : -1 );
    }
  } else if ( $b['par_id'] ) {
    return ( $b['par_id'] >= $a['id'] ? -1 : 1);
  } else {
    return ( $a['id'] < $b['id'] ? -1 : 1 );
  }
}

它可以工作,然后我得到这样的数组:

Array
(
    [0] => Array
        (
            [id] => 310
            [price] => 3700
            [par_id] => 0
        )
    [1] => Array
        (
            [id] => 311
            [price] => 3700
            [par_id] => 310
        )
    [2] => Array
        (
            [id] => 800
            [price] => 3900
            [par_id] => 310
        )
    [3] => Array
        (
            [id] => 812
            [price] => 0
            [par_id] => 310
        )
    [4] => Array
        (
            [id] => 445
            [price] => 3400
            [par_id] => 0
        )
    [5] => Array
        (
            [id] => 1102
            [price] => 3500
            [par_id] => 0
        )
)

但是,这个数组不是按价格排序的。我的问题是,是否可以在上述状态下对数组进行排序,但也可以继续按价格排序,至少在父级(在子级也是理想情况)。所以我想得到的结果数组是这样的:

Array
(
    [0] => Array
        (
            [id] => 445
            [price] => 3400
            [par_id] => 0
        )
    [1] => Array
        (
            [id] => 1102
            [price] => 3500
            [par_id] => 0
        )
    [2] => Array
        (
            [id] => 310
            [price] => 3700
            [par_id] => 0
        )
    [3] => Array
        (
            [id] => 812
            [price] => 0
            [par_id] => 310
        )
    [4] => Array
        (
            [id] => 311
            [price] => 3700
            [par_id] => 310
        )
    [5] => Array
        (
            [id] => 800
            [price] => 3900
            [par_id] => 310
        )
)

父级优先,紧随其后的是子级,并且仍然按用户选择的价格排序。

【问题讨论】:

  • 你最好使用多数组来做这个,这样会更清楚。
  • 你只获取一个父项的子项吗?
  • 不,可以有更多的父项和更多的子项,但这只是一层结构。孩子不能有另一个孩子,每个孩子只有一个父母。

标签: php arrays sorting


【解决方案1】:

好问题。您不比较 cmp 函数中的价格,因此结果列表不按价格排序是合乎逻辑的。看看下面的cmp函数:

function cmp($a, $b) {
    if ($a['id'] === $b['id']) {
        return 0;
    }

    // both are child or both are parents, so compare by price
    $comparePrices = ($a['par_id'] && $b['par_id'])
        || (!$a['par_id'] && !$b['par_id']);

    $priceResult = $a['price'] > $b['price'] ? 1 : -1;

    if ($comparePrices) {
        return $priceResult;
    } else if ($a['par_id']) {
        // a is a child, so check if b is the parent
        return $a['par_id'] === $b['id'] ? 1 : $priceResult;
    } else if ($b['par_id']) {
        // b is a child, so check if a is the parent
        return $b['par_id'] === $a['id'] ? -1 : $priceResult;
    }
}

这应该有效,但不能保证它适用于所有情况。

为了便于阅读,无论如何你都应该实现这个不同的方法。将您的查询分成两部分。第一个按价格获取所有父级和订单,第二个按父级 ID 然后按价格获取所有子级和订单。这些数组的合并操作是foreacharray_map 的简单组合。例如:

$childItemsByParentId = [];
foreach ($childItems as $childItem) {
    $childItemsByParentId[$childItem['par_id']][] = $childItem;
}

$parentItems = array_map(function ($parentItem) use ($childItemsByParentId) {
    $childs = isset($childItemsByParentId[$parentItem['id']])
        ? $childItemsByParentId[$parentItem['id']]
        : [];

    $parentItem['childs'] = $childs;
    return $parentItem;
}, $parentItems);

【讨论】:

  • 谢谢。我相信 cmp 功能现在应该没问题,但在我的情况下,用户可以选择不同的列进行排序。如何将第三个参数添加到 cmd 函数 - 要排序的列名。还有更多,第四个参数应该告诉,如果排序是 ASC 或 DESC。有可能吗?
  • 谷歌刚刚告诉我,可以在全局变量中添加另一个参数,对吧?所以我需要的最后一个是对 DESC 进行排序的机会。上面的函数总是按 ASC 排序。
  • 我用 array_map() 尝试了你的第二个建议,但这并不是我所需要的。不幸的是,系统需要单级数据,因此子级必须与其父级处于同一级。看起来您编写的排序 cmp 函数在大约一半的情况下有效。是因为不可能处理所有情况还是可以改进?
  • 如果你想将cmp函数与动态排序列/方向一起使用,你必须创建一个工厂,然后它将使用指定的排序列/方向算法创建闭包。但这真的会是一团糟。你应该坚持我的第二个建议。从这两个查询创建一个平面数组并不难。
  • 能否提供cmp函数产生错误结果的数据集?
【解决方案2】:

我最终发明了另一种方法。

它遍历整个数组并找出实际项目是父项还是子项。如果它是孩子,它会再次通过数组找到它的父母。如果找到父项,它将向前或向后移动子项和父项位置之间的所有项目(取决于子项的位置 - 如果在父项之前或之后)以在数组中腾出一个空间,子项将放置在哪里。

$last_id = 0;
for($i = 0; $i < count($data); $i++) {
      $item = $data[$i];

      if ($item['par_id'] == 0) {
        $last_id = $item['id'];
        continue;
      }
      if ($last_id == $item['par_id']) {
        continue;
      }

      foreach($data as $d => $dat) {
        if ($item['par_id'] == $dat['id']) {

          if ($i < $d) {
            for($j = $i; $j < $d; $j++) { $data[$j] = $data[$j + 1]; }
            $empty = $d;
            $i -= 1;
          } else {
            for($j = $i; $j > $d; $j--) { $data[$j] = $data[$j - 1]; }
            $empty = $d + 1;
          }

          $data = $item;
    } } }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多