Как отсортировать связанный список в PHP? - PullRequest
1 голос
/ 07 февраля 2010
$arr[] = array(...,'id'=1,'prev'=>2,'next'=>null);
$arr[] = array(...,'id'=2,'prev'=>3..,'next'=>1);
$arr[] = array(...,'id'=3,'prev'=>4,'next'=>2);
..

Порядок каждой записи может быть произвольным.

Как отсортировать массив такого типа, чтобы запись со значением prev null была первой, а запись со null next - последней?

Ответы [ 5 ]

7 голосов
/ 07 февраля 2010

Массив не контейнер для связанного списка. Связанный список - это список с связанными объектами , а не список с объектами, имеющими отношения. По сути, у вас есть худший из двух контейнеров. Я бы попытался преобразовать эту структуру в какой-то другой контейнер данных; реальный связанный список никогда не нужно сортировать так, как вам нужно сортировать данные.

Хороший способ - это что-то подобное. Я оставлю вам способ вставлять объекты в середину списка, это не так сложно.

<?php
class LinkedObject
{
    var $value;
    var $prev;
    var $next;

    public function __construct($value, $prev = null, $next = null)
    {
        $this->value = $value;
        $this->prev = $prev;
        $this->next = $next;
    }

    public function append(LinkedObject $insertee)
    {
        $link = $this;
        while($link->next != null)
            $link = $link->next;

        $link->next = $insertee;
        $insertee->prev = $link;
    }

    public function __toString()
    {
        $str = $this->value;
        if($this->next != null)
        {
            $str .= " » ";
            $str .= $this->next;
        }
        return $str;
    }
}

$head = new LinkedObject("foo");
$head->append(new LinkedObject("bar"));
$head->append(new LinkedObject("baz"));
echo $head . "\n"; // gives "foo » bar » baz"
?>

Но, если по какой-то оккультной причине вы действительно нуждаетесь в них в массиве, вот что вам нужно:

<?php
function find_row($array, $id)
{
    foreach($array as $current_row)
    {
        if($current_row['id'] === $id)
            return $current_row;
    }
    return null;
}

function what_the_heck_sort($array)
{
    $start_record = $array[0];
    $working_record = $array[0];
    $result = array($working_record);
    while($working_record['prev'] !== null)
    {
        $working_record = find_row($array, $working_record['prev']);
        array_unshift($result, $working_record);
    }

    $working_record = $start_record;
    while($working_record['next'] !== null)
    {
        $working_record = find_row($array, $working_record['next']);
        array_push($result, $working_record);
    }
    return $result;
}

// the test code
$test = array(
    array("foo 01", 'id' => 0, 'prev' => null, 'next' => 1),
    array("foo 02", 'id' => 1, 'prev' => 0, 'next' => 2),
    array("foo 03", 'id' => 2, 'prev' => 1, 'next' => 3),
    array("foo 04", 'id' => 3, 'prev' => 2, 'next' => 4),
    array("foo 05", 'id' => 4, 'prev' => 3, 'next' => 5),
    array("foo 06", 'id' => 5, 'prev' => 4, 'next' => 6),
    array("foo 07", 'id' => 6, 'prev' => 5, 'next' => 7),
    array("foo 08", 'id' => 7, 'prev' => 6, 'next' => 8),
    array("foo 09", 'id' => 8, 'prev' => 7, 'next' => 9),
    array("foo 10", 'id' => 9, 'prev' => 8, 'next' => null));

shuffle($test);
print_r(what_the_heck_sort($test));
?>

Но на самом деле, сделайте себе одолжение и составьте реальный связанный список, используя объекты, а не массивы. Приведенный выше метод сортировки, на мой взгляд, вполне приличный, зная ограничения, но он смехотворно медленный , потому что ему нужно искать массив для каждого идентификатора.

1 голос
/ 07 февраля 2010

Я бы сделал первый раунд с указанием идентификаторов в качестве ключей массива. В этот же момент записывается первый элемент.

$newArray = array():
$firstElement = null;
foreach( $array as $row ) {
    $newArray[$row['id']] = $row;
    if( $row['prev'] == null ) $firstElement = $row['id'];
}

После этого вы можете перебирать список следующим образом:

$curId = $firstElement;
while($curId != null) {
    do_something($newArray[ $curId ])
    $curId = $newArray[ $curId ]['next'];
}

Для эффективности вы могли бы рассмотреть возможность просмотра вашего гидратора данных (функция, которая получает данные из базы данных в массиве), или он может добавить идентификатор в качестве ключа массива сразу. Также вы можете с помощью порядка сортировки запросов убедиться, что первый элемент всегда является первым элементом в исходном массиве, что позволяет легко найти этот идентификатор.

Кстати, не называйте это реализацией связанного списка, поскольку связанный список характеризуется ссылкой на объект на следующий элемент (не на индекс).

Редактировать: Одна вещь, которую я еще не упомянул. Если вы хотите иметь отсортированный список в массиве, то замените do_something ($ newArray [$ curId]); с $ array [] = $ newArray [$ curId];.

Я полагаю, что это решение намного прозрачнее / быстрее, чем большинство других решений, так как оно стоит два раунда по всему массиву или, если вы можете интегрировать первую часть в свой метод гидратации без затрат, всего одна итерация по массиву.

1 голос
/ 07 февраля 2010

Хм, так что вы хотите получить ваш массив во что-то вроде:

$array[] = array('id'=>1324, 'prev'=>null, 'next'=>15834);
$array[] = array('id'=>15834, 'prev'=>1324, 'next'=>1023);
$array[] = array('id'=>1023, 'prev'=>15834, 'next'=>12482);
$array[] = array('id'=>12482, 'prev'=>1023, 'next'=>null);

независимо от того, в каком порядке они начали? Ну, это не будет базовый шаблон сортировки, поэтому я бы выбрал что-то вроде:

// Find the first entry
foreach($arr as $index => $row) {
  if ($row['prev'] == null) {
    // This is the first row
    $cur_row = $row;
    break; // Jump out of the foreach loop
  }
}
$sorted = array();
$sorted[] = $cur_row;
while ($cur_row['next'] != null) {
  // Find the next row
  foreach($arr as $index => $row) {
    if ($row['id'] = $cur_row['next']) {
      // This is the next row
      $sorted[] = $row;
      $cur_row = $row;
      break; // Jump out of the foreach loop
    }
  }
}
print_r($sorted); // $sorted now has your sorted array
0 голосов
/ 06 апреля 2012

Этот код будет работать

$a[0] = array('0', '00', '000');
$a[1] = array('1', '11', '111');
$a[2] = array('2', '22', '222');
$a[3] = array('3', '33', '333');
$a[4] = array('4', '44', '444');
$result = count($a);
echo $result; // print count


list ($result1, $result2, $result3) = $a[4]; // array to list
echo $result3; // print data in list
0 голосов
/ 07 февраля 2010

Я считаю, что встроенный usort будет хорошо работать для описанной вами структуры данных.

редактировать: это не работает правильно. Пожалуйста, не принимайте, чтобы я мог удалить его.

<?php
//$arr = your array as described

usort($arr, 'subkey_compare_next');

function subkey_compare_next($a, $b) {
    $a = $a['next'];
    $b = $b['next'];
    if($a === null) {
        return -1;
    }
    if ($a == $b) {
        return 0;
    }
    return ($a < $b) ? -1 : 1;

}
?>
...