views:

103

answers:

7

Assume you have a set of items in an array.

A, B, C, D, E, F, G, H

Using PHP, how would you randomly pair the letters together without pairing them with a duplicate of themselves?

Such as this:

 A->pairedLetter = G
 B->pairedLetter = C
 C->pairedLetter = E
 D->pairedLetter = A
 E->pairedLetter = B
 F->pairedLetter = D
 G->pairedLetter = F

and so on...

EDIT: Oh, and also, If A is paired with F, F can NOT be paired with A. So there will have to be as many relationships as there are items.

A: 

I've tried using a copy of the original array and using array_rand() but if it finds the same item as I'm iterating over, I have to run array_rand() again, which can fall into a endless loop.

I'm stumped, there has to be an elegant way to do this.

brennanag
@brennanag, don't add answers to your own question unless you are _answering_ your own question (which is encouraged). _edit_ your question.
aaronasterling
This isn't an answer, it should be added as a comment to your question or your question should be edited with this new information
colithium
also, you are not going to go into an infinite loop that way unless you have a seriously broken random number generator. think about it a little harder. it would have to return _only_ the number that your trying to match against for this to occur.
aaronasterling
Believe me, I have thought about it plenty hard. Sometimes there are only two or three possible return values. I'll add comments instead, thanks for the pointer.
brennanag
@brennanag. as long as there is only one return value that is not the letter you are matching against, this will not be an infinite loop. I'm not saying that this is the best way to do it but it's a point worth making.
aaronasterling
+3  A: 

Duplicate the array. Call shuffle on one array. Then:

  1. Check that the first elements are different (if they are switch the first two elements of one array).
  2. array_pop off an item from each array to form a pair.
  3. If the items are the same, pop two off one array and pair up, then two off the other.

edit

With your extra condition, you'll need to check that every pair doesn't already exist. If it does (at the third stage) you can try taking different combinations (pop off first and third {pop off first, second, third and push second back on}) but you may still end up with an invalid pair at the end so would need to start again.

fredley
Thank you, I'll try it. Do I want to call array_rand(), or shuffle()?
brennanag
Shuffle, sorry. Answer amended.
fredley
I added some edits to the question, I don't think your solution will solve it with those edits.
brennanag
Does this work? What if everything works okay except that the last items in both arrays are the same?
Damien_The_Unbeliever
Thanks! I'll update my answer...
fredley
@Damien Hence the first bullet point: `array_pop` takes from the end.
fredley
A: 
$arrayValues = range('A','H');
shuffle($arrayValues);
while (count($arrayValues) > 0) {
   echo array_pop($arrayValues),' is matched with ',array_pop($arrayValues),'<br />';
}

EDIT

following changes to the question

$arrayValues = range('A','H');
$tmpArrayValues = $arrayValues;
$pairs = array();
shuffle($tmpArrayValues);
foreach($arrayValues as $arrayValue) {
    $tmpValue = array_pop($tmpArrayValues);
    while (($arrayValue == $tmpValue) || ((isset($pairs[$tmpValue])) && ($pairs[$tmpValue] == $arrayValue))) {
        array_unshift($tmpArrayValues,$tmpValue);
        $tmpValue = array_pop($tmpArrayValues);
    }
    $pairs[$arrayValue] = $tmpValue;
}

foreach($pairs as $key => $value) {
   echo $key,' is matched with ',$value,'<br />';
}
Mark Baker
This only works with even numbers, and it also fails the second stipulation I added after you posted (my bad). But I love its elegance.
brennanag
@brennanag - just spotted and fixed a silly logic error in my updated version
Mark Baker
way to over complicated, you might as well build a matrix
RobertPitt
+1  A: 
$array = array(
    'A','B','C',
    'D','E','F',
    'G','H','I','J'
);
$new = array();

if(count($array) % 2 != 0)
{
    array_pop($array); // Got to remove 1 element to make them even.
}

foreach($array as $item)
{
    $_t = array_pop($array);
    //Key
    if(!isset($new[$item]))
    {
        $new[$item] = $_t;
        $new[$_t] = $item;
    }
}


var_dump($new);

This would print:

array(10){
    ["A"]=> string(1) "J"
    ["J"]=> string(1) "A"
    ["B"]=> string(1) "I"
    ["I"]=> string(1) "B"
    ["C"]=> string(1) "H"
    ["H"]=> string(1) "C"
    ["D"]=> string(1) "G"
    ["G"]=> string(1) "D"
    ["E"]=> string(1) "F"
    ["F"]=> string(1) "E"
}

the way this works is it loops the values and deletes a value at the same time, so you always have 2 values at once but at the same time shrinking the array by 1.

then when we set the new keys were setting both of them to the new array, so the next time the loop iterates if the same value comes back around it just gets discarded :)

RobertPitt
I just edited my question to include the stipulation that Items cannot be paired with each other like A <=> B. B has to paired with another item. There will need to be as many relationships as there are items.
brennanag
Check my update !
RobertPitt
If A is paired with J, J can't be paired with A
brennanag
+2  A: 

What about this ?

// input array
$arr = array('A','B','C','D','E','F');
// result array
$res = array();
// get first element and save it
$first = $ele1 = array_shift($arr);
while(count($arr)) {
    // get random element
    $ele2 = array_rand($arr);
    // associate elements
    $res[$ele1] = $arr[$ele2];
    // random element becomes next element
    $ele1 = $arr[$ele2];
    // delete the random element
    array_splice($arr, $ele2, 1);
}
// associate last element woth the first one
$res[$ele1] = $first;

print_r($res);

Output:

Array
(
    [A] => B
    [B] => F
    [F] => E
    [E] => D
    [D] => C
    [C] => A
)

Works with even number of elements arrays as well as odd.

Update, using Chris' algorithm:

$arr = array('A','B','C','D','E','F');
shuffle($arr);
$res=array();
for($i=0;$i<count($arr);$i++) {
  $res[$arr[$i]] = $arr[$i+1];
}
$res[$arr[count($arr)-1]] = $arr[0];
M42
I'm trying it... looks promising.
brennanag
I've just seen this answer and its the same principle as I suggested in my answer. If there exists functions to shuffle the array (as other answers tell me there are) then you don't need all this code, you jsut shuffle the array and that has the same effect. There are implicit restrictions in this though that may make a difference to whether it is satisfactory or not. You can't for example generate a->b->c->a, d->e->f->d whcih satisfies the reuested conditions but will never be generated by this method.
Chris
That did it. Thank you so much.
brennanag
@Chris I actually like your solution better, but since his came first, and it works, and it has code for others to use, I chose it. But I would prefer to split it.
brennanag
@brennanag: I'm not much of a php programmer so if I tried for working code I'd probabyl fail. I just get attracted to "algorithm" tags. :) M42 is welcome to code up a version of my answer if (s)he wants to make this one more comprehensive.
Chris
+3  A: 

Does it have to be perfectly random given your constraints? If you were willing to add in another constraint then you could make the problem very simple. If you were willing to make it so that the mappings all formed a single loop then you could just shuffle your array and then make element one pont at two, two point at three and the last element points at the first. You will guarantee that no element can poitn at itself (unless the array only has one element), no element will point at the element pointing at it (unless the array only has two elements) and that no element will be pointed at by more than one item.

You are restricting your results set slightly so losing a certain amount of the random element but if you are happy with this its extremely simpel to do.

Chris
+1 for lateral thinking to simplify the problem
Mark Baker
A: 

Using the shuffle function on the array and checking that the mappings are valid (A doesn't map to A and that if A maps to B then B doesn't map to A). The following should do what you want:

$values = array('A','B','C','D','E','F','G');
$new_values = array('A','B','C','D','E','F','G');

$random = shuffle($new_values);
//randomly shuffle the order of the second array
$mappings = array();

$validMappings = false;
//continue to retry alternative mappings until a valid result is found
while(!$validMappings) {
   //set the mappings from $values to $new_values
   for($i = 0; $i < count($values); $i++) {
        $mappings[$values[$i]] = $new_values[$i];
    }

    $validMappings = true;
    //check validity of the current mapping
    foreach ($mappings as $key=>$value) {
        if($mappings[$key] == $mappings[$value]) {
            $validMappings = false;
            break;
        }
    }
    //if false shuffle the new values and test again
    if(!$validMappings) {
        $random = shuffle($new_values);
    }
}
print_r($mappings);
jkilbride