PHP пользовательская сортировка: ручная сортировка массива по указанным ключам - PullRequest
2 голосов
/ 30 мая 2020

У меня есть массив, который выглядит как

$array = [
    //...
    'name' => ['value' => 'Raj KB'],
    'street' => ['value' => 'Street ABC'],
    'city' => ['value' => 'Dubai'],
    'country_id' => ['value' => 'UAE'],
    'region' => ['value' => 'DXB'],
    'region_id' => ['value' => 11],
    'zip_code' => ['value' => 12345],
    'city_id' => ['value' => 22],
    //...
];

Я хотел бы отсортировать массив так, чтобы возникали ключи country_id, region, region_id, city, city_id последовательно, сохраняя позицию других.

Ожидаемый результат

 $array = [
    //...
    'name' => ['value' => 'Raj KB'],
    'street' => ['value' => 'Street ABC'],
    'country_id' => ['value' => 'UAE'],
    'region' => ['value' => 'DXB'],
    'region_id' => ['value' => 11],
    'city' => ['value' => 'Dubai'],
    'city_id' => ['value' => 22],
    'zip_code' => ['value' => 12345],
    //...
];

Я пробовал как:

Проба № 1

uksort($array, function ($a, $b) {

  $order = ['country_id' => 0, 'region' => 1, 'region_id' => 2, 'city' => 3, 'city_id' => 4];
  if (isset($order[$a]) && isset($order[$b])) {
    return $order[$a] - $order[$b];
  } else {
    return 0;
  }
});

var_dump($array);

Проба # 2

uksort($array, function ($a, $b) {

  $order = ['country_id' => 0, 'region' => 1, 'region_id' => 2, 'city' => 3, 'city_id' => 4];
  if (!isset($order[$a]) && !isset($order[$b])) {
    return 0;
  } elseif (!isset($order[$a])) {
    return 1;
  } elseif (!isset($order[$b])) {
    return -1;
  } else {
    return $order[$a] - $order[$b];

  }
});

var_dump($array);

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

Ответы [ 4 ]

4 голосов
/ 01 июня 2020

Похоже, что то, что вы хотите, трудно достичь с помощью одного из методов сортировки PHP. Более того, поскольку относительный порядок несовпадающих ключей не должен меняться, мы можем стремиться к лучшей временной сложности, чем с помощью метода сортировки O (nlogn) .

Итак, я предложил бы написать функцию, которая выполняет несколько итераций по обоим массивам ($array, $order), чтобы она собирала пары ключ / значение в ожидаемом порядке. Это составляет O (n + m) временную сложность, где n и m - два размера двух массивов.

Здесь - это функция:

function sortadjacent($array, $order) {
    $insertAt = 0;
    foreach($array as $key => $_) {
        if (isset($order[$key])) break;
        $insertAt++;
    }

    $special = [];
    foreach($order as $key => $_) {
        if (isset($array[$key])) $special[$key] = $array[$key];
    }

    $result = [];
    foreach($array as $key => $value) {
        if (!isset($order[$key])) $result[$key] = $value;
        else if (count($result) == $insertAt) $result = array_merge($result, $special);
    }

    return $result;
}

Вы бы назвали ее следующим образом:

$result = sortadjacent($array, $order);

Обратите внимание, что эта функция не вносит изменений в $array, а вместо этого возвращает ожидаемый результат в виде новый массив.

3 голосов
/ 01 июня 2020

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

Поскольку вам также нужна последовательность существующих ключей для сохранения, а другие «извлеченные» ключи должны быть вставлены после country_id, вы можете сохранить ссылку на исходный порядок сортировки и использовать ее для определения порядка сортировки по отношению к country_id для других полей (и между другими поля для сохранения текущего порядка сортировки)

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

$order = ['country_id' => 1, 'region' => 2, 'region_id' => 3, 'city' => 4, 'city_id' => 5];
$preset_order = array_flip(array_keys($array));

uksort($array, function ($a, $b) use ($order, $preset_order) {
  if (isset($order[$a]) && isset($order[$b])) {
    return $order[$a] - $order[$b];
  } else if (isset($order[$a])) {
    return $preset_order['country_id'] - $preset_order[$b];
  } else if (isset($order[$b])) {
    return $preset_order[$a] - $preset_order['country_id'];
  } else {
    return $preset_order[$a] - $preset_order[$b];
  }
});

Выходы:

array(8) {
  'name' =>
  array(1) {
    'value' =>
    string(6) "Raj KB"
  }
  'street' =>
  array(1) {
    'value' =>
    string(10) "Street ABC"
  }
  'country_id' =>
  array(1) {
    'value' =>
    string(3) "UAE"
  }
  'region' =>
  array(1) {
    'value' =>
    string(3) "DXB"
  }
  'region_id' =>
  array(1) {
    'value' =>
    int(11)
  }
  'city' =>
  array(1) {
    'value' =>
    string(5) "Dubai"
  }
  'city_id' =>
  array(1) {
    'value' =>
    int(22)
  }
  'zip_code' =>
  array(1) {
    'value' =>
    int(12345)
  }
}
2 голосов
/ 02 июня 2020

PHP использует Quicksort , поэтому вы не можете просто вернуть возвращаемые значимые значения для элементов, которые хотите отсортировать. На мой взгляд, использовать здесь uksort - плохая идея, потому что вам придется использовать текущие индексы вашего массива в качестве значений, но это невозможно, потому что вы не можете получить доступ к копии старого массива изнутри вашей функции сравнения. Также вам нужно знать, по какому индексу находится первое из ваших специальных значений.

Итак, я бы посоветовал сделать что-то подобное, потому что я думаю, что невозможно делать то, что вы хотите, с помощью uksort:

function customSort($array)
{
    $order = ['country_id' => 0, 'region' => 1, 'region_id' => 2, 'city' => 3, 'city_id' => 4];
    $keyArray = array();
    $sortedArray = array();
    foreach ($array as $i => $value) {
        $keyArray[] = $i;
    }
    $counter = 0;
    $hasStarted = false;
    $insertLater = array();
    for ($i = 0; $i < count($keyArray); $i++) {
        if ($hasStarted) {
            if ($counter < count($order)) {
                $sortedArray[array_search($counter, $order)] = $array[array_search($counter, $order)];

                $counter++;
                if (!isset($order[$keyArray[$i]])) {
                    array_push($insertLater, ["key" => $keyArray[$i], "value" => $array[$keyArray[$i]]]);
                }
                continue;
            }
        }

        if (count($insertLater) > 0) {
            $itemToInsert = array_shift($insertLater);
            $sortedArray[$itemToInsert["key"]] = $itemToInsert["value"];

            if (!isset($order[$keyArray[$i]])) {
                array_push($insertLater, ["key" => $keyArray[$i], "value" => $array[$keyArray[$i]]]);
            }
            continue;
        }
        if (isset($order[$keyArray[$i]]) && !$hasStarted) {
            $sortedArray[array_search($counter, $order)] = $array[array_search($counter, $order)];
            $hasStarted = true;
            $counter++;
            continue;
        }
        $sortedArray[$keyArray[$i]] = $array[$keyArray[$i]];
    }
    return $sortedArray;
}
It's may

не лучшее решение, но работает O (n).

1 голос
/ 07 июня 2020

Идея создания генератора, который выдает ключи в том порядке, в котором вы хотите (из моего понимания вопроса, группа упорядоченных ключей начинается, когда появляется первый упорядоченный ключ):

$ordered_keys = ['country_id', 'region', 'region_id', 'city', 'city_id'];

$keys = (function ($array, $ordered_keys) {
    $flag = true;
    foreach ($array as $key => $v) {
        if ( $flag && in_array($key, $ordered_keys) ) {
            yield from $ordered_keys;
            $flag = false;
        }
        yield $key;
    }
})($array, $ordered_keys);

$result = [];

foreach($keys as $key) {
    $result[$key] = $array[$key];
}

print_r($result);

demo

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


Вы не t даже нужен генератор, вы можете сделать это прямо в одном l oop:

$ordered_keys = ['country_id', 'region', 'region_id', 'city', 'city_id'];

$flag = true;
$result = [];

foreach ($array as $k=>$v) {
    if ( $flag && in_array($k, $ordered_keys) ) {
        $flag = false;
        foreach ($ordered_keys as $key) {
            $result[$key] = $array[$key];
        }
    } else {
        $result[$k] = $array[$k];
    }
}

print_r($result);

demo


Другая идея: создание массива с те же ключи, но с целочисленными значениями.

Перед первым «упорядоченным ключом» значения находятся в диапазоне от 0 до 255, после значений в диапазоне от 512 до бесконечности. Значения для заказанных ключей уже установлены в диапазоне 256-511. (Эти диапазоны абсолютно произвольны, вы можете выбрать те, которые хотите.)

$ordered_keys = ['country_id' => 256, 'region' => 257, 'region_id' => 258, 'city' => 259, 'city_id' => 260];

$index = 0;

$result = [];

foreach ($array as $k => $v) {
    if ( isset($ordered_keys[$k]) ) {
        $result[$k] = $ordered_keys[$k];
        if ( $index < 255 ) $index = 512;
    } else {
        $result[$k] = $index++;
    }
}

asort($result);

foreach($result as $k => $v) {
    $result[$k] = $array[$k];
}

print_r($result);

демонстрация

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...