简体   繁体   中英

does php have built-in array functions that can sort/assign values for boolean result?

I have an array of orders, key represents order#. each element contains an array of employees that can fulfill those orders represented by employee number.

example

[0] =>         // <-- order#
    [0] => 0,
    [1] => 1,
    [2] => 2

[1] =>
    [0] => 0,
    [1] => 1,
    [2] => 2

[2] =>
    [0] => 3

[3] =>
    [0] => 3

so order 0 can be fulfilled by employee 0,1, or 2. order 1 as well. orders 2 and 3 can only be fulfilled by employee 3.

i need to return bool which is true if each order has one unique employee to fulfill it. so in this case return false because only one employee is available to fulfill orders 2 and 3 and cant be assigned to both.

i hope that makes sense. tapping this out on my phone agh

This is a php function I wrote quickly that does what you want, I have quickly tested it and it seems to work properly.

<?php

function compare($orders){

    if(sizeof($orders) == 0){
        return 1;
    }
    $quantity = array();
    foreach ($orders as $order) {
        if(sizeof($order) == 0){
            return 0;
        }
        foreach ($order as $employee) {
            if(array_key_exists($employee, $quantity)){
                $quantity[$employee]++;
            }
            else{
                $quantity[$employee] = 1;
            }
        }
    }
    $chosenEmployees = array_keys($quantity, min($quantity));
    $chosenEmployee = $chosenEmployees[0];

    $length = array();
    foreach ($orders as $key => $order) {
        $length[$key] = sizeof($order);
    }
    for ($i=0; $i < sizeof($orders); $i++) {
        $chosenOrders = array_keys($length, min($length));
        foreach ($chosenOrders as $orderKey) {
            if(in_array($chosenEmployee, $orders[$orderKey])){
                unset($orders[$orderKey]);
                foreach ($orders as $key1 => $order) {
                    foreach ($order as $key2 => $employee) {
                        if($employee == $chosenEmployee){
                            unset($orders[$key1][$key2]);
                        }           

                    }
                }
                return compare($orders);
            }
            else{
                unset($length[$orderKey]);
            }
        }
    }

}
$out = compare($orders);
?>

To use it, type compare($your_array_name), and the function will return 0 (false) or 1 (true).
Hope it helps.

Edit:
I doubt you'll find this code anywhere else because I wrote it for this question.
I have been working on proof and can prove that when the function returns true, it is true.

The function returned true.
=> The orders array is empty.
=> Before that, the orders array contained one order that can be done by at least employee one.
=> Before that, the orders array contained two orders that can be done respectively by at least employee one and employee two.
Via recurrence, we can deduce that n steps before the end, there were n orders, all doeable by at least one unique employee.
If n = size of initial array, we can conclude that if the function returns true, it is correct.

If this proof is not correct, please let me know. I will edit my post again if I find proof for the second half.

One solution is to use array_reduce and array_diff to speculatively assign employees to orders.

//Each step tries to add another employee id to the $carry array
function calc($carry, $item) 
{
    if (!$carry) {
        $carry = array();
    }
    $diff = array_diff($item, $carry);
    if (count($diff) > 0) {
        array_push($carry, reset($diff));
    }
    return $carry;
}

function cmp($a, $b)
{
    return count($a) - count($b);
}

function checkCondition($arrayToCheck) 
{
    uasort($arrayToCheck, 'cmp');
    $reduced = array_reduce($arrayToCheck, "calc");

    //If we have a shorter array than the number of orders, we have a problem
    return count($reduced) == count($arrayToCheck);
}

$o = array(
    array(0,1,2),
    array(0,1,2),
    array(3),
    array(3),
);

$result = checkCondition($o);

echo $result + " " + $result ? "Good" : "Bad";  //Should be Bad

$o = array(
    array(0,1,2),
    array(0,1,2),
    array(1),
    array(3),
);

$result = checkCondition($o);

echo $result + " " + $result ? "Good" : "Bad";  //Should be Good

Sorting the array first avoids the problem of "assigning" a worker to an order who MUST be assigned to a later order.

Algorithm

Your main issue is about the algorithm. As discussed, the language is of secondary importance.

What you want is to

check if each order can be assigned one employee and each employee assigned one or no order

You can draw it like a graph:

  • nodes are orders and employees
  • links are possible assignments

So you want to remove all necessary links so that

  • each employee node has one or no link left, and
  • each order node still has one (or more) link left

If you succeed, you have a solution and want the algorithm to return true . Otherwise, it should return false .


EDIT: J. Quick found an incredibly simple implementation that works. I would love to get a proof of it or a reference of where he found it though.

So I explain it here:

  1. If orders is empty, return true
  2. If any order of orders has no employee, return false
  3. Get employee e with least number of orders
  4. Get e 's order o with least number of employees
  5. Remove e and o from orders
  6. Repeat from 1.

JavaScript implementation

function areAllOrdersFulfilled(orders) {
    while (orders.length !== 0) {
        if (undefined !== orders.find( o => o.length === 0))
            // An order has no employee to fulfill it
            return false;
        // Get employee and order to remove
        let employees = [];
        orders.forEach( order => {
            order.forEach( employee => {
                employees[employee] = (employees[employee] || 0) + 1;
            });
        });
        let employeeToRemove = employees.indexOf(employees.slice().sort()[0]), // Employee with less orders
            orderToRemove = orders
                .sort( (o1, o2) => o1.length - o2.length ) // In-place sort orders
                .findIndex( o => o.includes(employeeToRemove)); // Order with less employees
        // Remove
        orders.splice(orderToRemove,1);
        orders = orders.map( o => o.filter(e => e !== employeeToRemove) )
    }
    return true;
}

You can run it like that:

const orders = [
    [0,1,2],
    [0,1,2],
    [3],
    [3]
];

console.log(areAllOrdersFulfilled(orders));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM