1

I'm not sure how the best way to explain what I want to do. But I'm gonna give it a try! I've have an array like this:

$array = array(
    "a" => array(
         "key1" => 3,
         "key2" => 4,
         "key3" => 1
    ),
    "b" => array(
         "key1" => 4,
         "key2" => 5,
         "key3" => 2
    ),
    "c" => array(
         "key1" => 2,
         "key2" => 3,
         "key3" => 3
    ),
    "d" => array(
         "key1" => 1,
         "key2" => 2,
         "key3" => 2
    )
);

Note: the array will be much larger and the values are not going to be only those (but the are always going to be an integer).

How can I split this array in half having at each part a similar average value from each different key (a similar average value for key1, similar average value for key2 and similar average value for key3)?

In the above example, the result should be:

$array[0] = array(
    "a" => array(
         "key1" => 3,
         "key2" => 4,
         "key3" => 1
    ),
    "c" => array(
         "key1" => 2,
         "key2" => 3,
         "key3" => 3
    )
);

$array[1] = array(
    "b" => array(
         "key1" => 4,
         "key2" => 5,
         "key3" => 2
    ),
    "d" => array(
         "key1" => 1,
         "key2" => 2,
         "key3" => 2
    )
);

(Both array have an average of 2.5 value for key1, 3.5 for key2 and of 2 for key3)

7
  • 1
    What you have tried? Commented Jul 25, 2013 at 19:01
  • Almost nothing :( I'm not a coder and the logic behind this kind of operation is completely out of my reach. I don't know where to even start... Commented Jul 25, 2013 at 20:01
  • You said there could be more values. Does that mean those with "key1", etc., or those with letters as keys? In other words, will there always be just four letters: a, b, c and d? Commented Jul 25, 2013 at 20:12
  • It could be more of both: more "keyN" and more or those with letters. But it is specially important that the number of those with letter could be dynamic (is it not big problem if I have to set the amount of "keyN" to a fixed 3) Commented Jul 25, 2013 at 20:44
  • Will the number of letters be even? Otherwise, how can you divide them in half? Commented Jul 25, 2013 at 20:51

1 Answer 1

0

The following function was adapted from the Hacker's Delight site:

function combos($bits, $max=32){
    $out = array();
    if ($bits < 1 || $bits > $max || $max > 32) return $out;
    $x = pow(2, $bits) - 1;
    $last = $x * pow(2, ($max - $bits));
    while($x <= $last){
        $out[] = decbin($x);
        $smallest = ($x & -$x);
        $ripple = $smallest + $x;
        $new_smallest = ($ripple & -$ripple);
        $ones = (($new_smallest/$smallest) >> 1) - 1;
        $x = $ripple | $ones;
    }
    return $out;
}

The following function is optional. It preps arrays in the case there are an odd number of subarrays, or the items in the subarrays are unequal. If the items are unequal, it pads the subarray with 0s. If the number of subarrays is odd, it adds one with zero-padded items:

function array_prep($arr){
    $cnt = 0;
    foreach ($arr as $key => $value){
        $cnt = (count($value) > $cnt) ? count($value) : $cnt;
    }
    if (count($arr)%2 == 1){
        $arr[] = array_fill(0, $cnt, 0);
    }
    foreach ($arr as $key => $value){
        $arr[$key] = array_pad($value, $cnt, 0);
    }
    return $arr;
}

Here's the main code for balancing the subarrays:

$array = array_prep($array);//remove if prepping is unnecessary
$n = count($array);
$cmb = combos(floor($n/2), $n-1);
array_unshift($array, null);
$rows = call_user_func_array('array_map', $array);
array_shift($array);
$score = array_combine($cmb, array_fill(0, count($cmb), 0));
foreach ($rows as $key => $row){
    foreach ($score as $combo => $val){
        $c = str_split(str_pad($combo, $n, '0', STR_PAD_LEFT));
        $sc = 0;
        foreach ($row as $k => $i){
            $sc += $i*(1 - 2*$c[$k]);
        }
        $score[$combo] += abs($sc);
    }
}
$min = array_keys($score, min($score));
$winner = str_split(str_pad($min[0], $n, '0', STR_PAD_LEFT));
//hack to keep original keys associated with values
foreach ($winner as $key => $value){
    $winner[$key] = $n*$value + $key; 
}
$winner_copy = $winner;
$keys = array_keys($array);
array_multisort($winner, $array);
array_multisort($winner_copy, $keys);
$array = array_combine($keys, $array);
$arr1 = array_slice($array, 0, floor($n/2));
$arr2 = array_slice($array, floor($n/2));
Sign up to request clarification or add additional context in comments.

1 Comment

Spectacular Pé de Leão! It does exactly what I was looking for! Thank you so much! (I'll test more in deep, but the few tests I've done give the right answer, If I see something weird I'll let you know).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.