Сортировать многомерный массив по нескольким полям - PullRequest
2 голосов
/ 31 октября 2019

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

Мне нужно сначала отсортировать массив пользователей по их (массиву)названий, а затем по фамилии, рассмотрите следующий код:

<?php
$users = [
    [
        'lastName' => 'Clarks',
        'titles' => ['Manager', 'Supervisor']
    ],
    [
        'lastName' => 'Clarkson',
        'titles' => ['Sales']
    ],
    [
        'lastName' => 'Adams',
        'titles' => ['Supervisor']
    ],
    [
        'lastName' => 'Adams',
        'titles' => ['Manager', 'Senior Manager']
    ],
    [
        'lastName' => 'Clarkson',
        'titles' => ['Manager']
    ],
    [
        'lastName' => 'Davids',
        'titles' => ['Senior Manager']
    ]
];

И порядок, который я хочу:

<?php
$order = [
    'Senior Manager',
    'Manager',
    'Supervisor'
];

Если есть несколько менеджеров, они должны быть отсортированы по lastName,поэтому вывод в этом случае будет:

<?php
$sorted = [
    [
        'lastName' => 'Adams',
        'titles' => ['Manager', 'Senior Manager']
    ],
    [
        'lastName' => 'Davids',
        'titles' => ['Senior Manager']
    ],
    [
        'lastName' => 'Clarks',
        'titles' => ['Manager', 'Supervisor']
    ],
    [
        'lastName' => 'Clarkson',
        'titles' => ['Manager']
    ],
    [
        'lastName' => 'Adams',
        'titles' => ['Supervisor']
    ],
    [
        'lastName' => 'Clarkson',
        'titles' => ['Sales']
    ]
];

Я пробовал что-то в этом духе, но не могу заставить его работать, и мне сложно его отладить usort:

<?php
foreach ($order as $title) {
    usort($users, function ($a, $b) use ($title) {
        # Both have the title
        if (in_array($title, $a['titles']) and in_array($title, $b['titles']) ) {
            # Sort by name
            return strcmp($a['lastName'], $b['lastName']);
        }
        # A has the title
        elseif (in_array($title, $a['titles'])) {
            return 1;
        }
        # B has the title
        elseif (in_array($title, $b['titles'])) {
            return -1;
        }

        # No-one has the title
        return strcmp($a['lastName'], $b['lastName']);
    });
}

Ответы [ 2 ]

2 голосов
/ 31 октября 2019

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

Итак, прежде всего, вы должны изменить порядок, который вы хотите:

$order = array_reverse([
    'Senior Manager',
    'Manager',
    'Supervisor'
]);

Тогда вы не хотите перемещать элементы, если ни один из них не имеет заголовка. Поскольку в других ваших итерациях ваш старший менеджер может не иметь звания супервизора, а другой пользователь также может не иметь звания, поэтому оба они не должны менять позиции. Таким образом, вы можете изменить последний возврат на return 0;

И, наконец, вы отменили возврат на 1 и -1, поэтому вам просто нужно поменять их.

Наконецконечный код будет выглядеть так:

$users = [
    [
        'lastName' => 'Clarks',
        'titles' => ['Manager', 'Supervisor']
    ],
    [
        'lastName' => 'Clarkson',
        'titles' => ['Sales']
    ],
    [
        'lastName' => 'Adams',
        'titles' => ['Supervisor']
    ],
    [
        'lastName' => 'Adams',
        'titles' => ['Manager', 'Senior Manager']
    ],
    [
        'lastName' => 'Clarkson',
        'titles' => ['Manager']
    ],
    [
        'lastName' => 'Davids',
        'titles' => ['Senior Manager']
    ]
];

$order = array_reverse([
    'Senior Manager',
    'Manager',
    'Supervisor'
]);

foreach ($order as $title) {
    usort($users, function ($a, $b) use ($title) {
        if (in_array($title, $a['titles']) && in_array($title, $b['titles'])) {
            return strcmp($a['lastName'], $b['lastName']);   
        }
        # A has the title
        elseif (in_array($title, $a['titles'])) {
            return -1;
        }
        # B has the title
        elseif (in_array($title, $b['titles'])) {
            return 1;
        }

        return 0;
    });   
}

И вывод будет:

array(6) {
  [0]=>
  array(2) {
    ["lastName"]=>
    string(5) "Adams"
    ["titles"]=>
    array(2) {
      [0]=>
      string(7) "Manager"
      [1]=>
      string(14) "Senior Manager"
    }
  }
  [1]=>
  array(2) {
    ["lastName"]=>
    string(6) "Davids"
    ["titles"]=>
    array(1) {
      [0]=>
      string(14) "Senior Manager"
    }
  }
  [2]=>
  array(2) {
    ["lastName"]=>
    string(6) "Clarks"
    ["titles"]=>
    array(2) {
      [0]=>
      string(7) "Manager"
      [1]=>
      string(10) "Supervisor"
    }
  }
  [3]=>
  array(2) {
    ["lastName"]=>
    string(8) "Clarkson"
    ["titles"]=>
    array(1) {
      [0]=>
      string(7) "Manager"
    }
  }
  [4]=>
  array(2) {
    ["lastName"]=>
    string(5) "Adams"
    ["titles"]=>
    array(1) {
      [0]=>
      string(10) "Supervisor"
    }
  }
  [5]=>
  array(2) {
    ["lastName"]=>
    string(8) "Clarkson"
    ["titles"]=>
    array(1) {
      [0]=>
      string(5) "Sales"
    }
  }
}
1 голос
/ 01 ноября 2019

То, что вы хотите, - это отсортировать пользователей на основе самого низкого индекса их заголовков в $order. Вы можете использовать array_search, чтобы найти индекс каждого из названий в $order и найти наименьшее число, используя min. Если они одинаковы, вернитесь к strcmp.

usort($users, function($a, $b) use ($order) {
    $minAPos = min(array_map(function($title) use ($order) {
        $pos = array_search($title, $order);
        return $pos === false? sizeof($order) : $pos;
    }, $a['titles']));
    $minBPos = min(array_map(function($title) use ($order) {
        $pos = array_search($title, $order);
        return $pos === false? sizeof($order) : $pos;
    }, $b['titles']));

    if($minAPos === $minBPos) {
        return strcmp($a['lastName'], $b['lastName']);
    } else {
        return $minAPos <=> $minBPos;
    }
});
...