Matrix Combination Logic - php

NOTE: **Please read all other related questions:**
Here is my first and second attempts at asking this question:
Efficient way to determine the outcome of test matrix
Would cartesian product be the best approach for this
Here is the problem:
I have several ( like 20 ) Boolean validations ( true / false )
All Boolean validations as a whole also have a validation Result
I'm trying to find the best solution to test all the validations and also the validation result. I was looking into a Matrix to hold all possible combinations but that might be an overkill.
Here is an example ( 1 - 20 ):
test_1 = Has 30 Kills
test_2 = Has Found Map 1
test_3 = Has Mastered Level 1
test_4 = Has Achieved Grunt Status
test_5 = Has Assault weapon
test_6 = Has Knife
test_7 = Has Grenade
test_x = Etc...
So when the Player has all these validations as TRUE I can then give a level result
if test_1, test_2, test_3 ( any combination of the three ): level = green
All combinations are ( 15 ):
test_1
test_2
test_3
test_1, test_2
test_1, test_3
test_2, test_1 ( duplicate can skip this )
test_2, test_3
test_3, test_1 ( duplicate can skip this )
test_3, test_2 ( duplicate can skip this )
test_1, test_2, test_3
test_1, test_3, test_2 ( duplicate can skip this )
test_2, test_1, test_3 ( duplicate can skip this )
test_2, test_3, test_1 ( duplicate can skip this )
test_3, test_1, test_2 ( duplicate can skip this )
test_3, test_2, test_1 ( duplicate can skip this )
So unique combinations are ( 7 instead of 15 ):
test_1
test_2
test_3
test_1, test_2
test_1, test_3
test_2, test_3
test_1, test_2, test_3
Now I'm trying to find the best possible solution to find unique combinations for all 20 validations and come up with a level validation from that matrix.
UPDATE:
Also I need to find only TRUE Combinations so you might read the Unique Combinations like this:
test_1
test_2
test_3
test_1, test_2
test_1, test_3
test_2, test_3
test_1, test_2, test_3
Boolean Value Results from Validation Tests
TRUE, FALSE, FALSE
FALSE, TRUE, FALSE
FALSE, FALSE, TRUE
TRUE, TRUE, FALSE
TRUE, FALSE, TRUE
FALSE, TRUE, TRUE
TRUE, TRUE, TRUE
So any of these combinations would be a GREEN level.
Also I need to know the order of the test validations as well as the matrix order to compare for level assignment. So for GREEN level I only need the validation result combination matrix for test 1, 2 and 3. So I could ignore tests 4 - 20
UPDATE #2:
I know this looks like a simple OR condition but I wanted to take out the combination logic to set the level into a matrix. I could use the matrix of combinations to determine the level logic without having to code additional or modify current logic in the code itself. I wanted to just compare the validations results for a given set of tests and assign a level to those results. Different permutations of the validation combinations would result in different level assignments.
I understand that I could add the combination logic in the code itself, but as this logic looks to be very volatile and thought this might offer a more flexible solution.
Suggestions?

(removed my two previous answers for clarity)
After your last edit, instead of answering directly, I would like first to be sure to 100% understand the "level detection algorithm" you want.
If I understand well, you would like to define/maintain a simple configuration structure telling which tests give which level.
e.g. with an associative array:
array(
'green' => array('test1', 'test2', 'test3'),
'orange' => array('test2', 'test3', 'test5')
...
);
With the meaning: if one or more of the tests in the list are satisfied, assign that level (array key) to the player. Such logic could easily cover quite a lot of combinations, and would avoid handling a huge matrix.
Maybe you want to extend the logic to tell, for example, that at least N tests among the test list are satisfied.
array(
'green' => array(
'tests' => array('test1', 'test2', 'test3'),
'nb_required' => 2
),
...
);
Is that what you want?
BTW, why don't you use a classic XP/level up system? :-p

Not quite to answer your question, but it seems you're missing something.
Lets say you have twenty tests, labeled 1 though n. Decide for test n whether you want to validate for it, or not. Either you include the test for validation, or you don't. That's two choices for every test. Proceed through (n-1) until you have no more tests. For n=20, that's 2^20 = 1048576 possible combinations of tests (including one combination where you don't select any tests), which means 1048576 results. Now I still don't understand what you mean by "level of validation", but I have to wonder why you'd need that many combinations of tests in the first place.
Edit: Well, a test matrix (so to speak) could work.... but you'd still have to generate the matrix. You're likely not going to hand code all 1048576 combinations. But supposing you've already created the mapping, you just have a zero indexed array as a look up table with 1048576 values that you desire and you're done! All you would need to map the test results to the matrix would be assigning each test a binary digit (test1 would be the one's place, test2 would be the 2's place, etc.)
What I suspect you really want is a quick way to generate the mapping given some broader rules you might encode in php... but here's funny part about that. If you had some code that generated the matrix for any and every set of tests, the matrix is then superfluous; Your code is essentially a compressed representation for the matrix. The only point in using one over the other is whether or not one would be faster than the other.
It also seems you really don't care about all 1048576 combinations. I suspect it might benefit you to partition your tests into their own set of tests (one for red, one for blue, etc). For instance, if you partition tests into groups of 5, and there is, oh, 3 different possibilities (instead of 16) for each group, you're only working with (3 different results per group)^(4 groups) = 81 unique results. Eighty one unique results is much more manageable than over a million.
It seems you may also need different partitions for different independent things. It also may not matter even which tests result in true as long as a certain number of them are true, that is "goal: 3 out of 5 blue objectives met" "goal: 7 out of 10 red and blue objectives met" or whatever. Those tests would have to be accessed independently and they wouldn't necessarily be multiplicative with the results of the other tests-- there will never be 7 out of 10 red and blue objectives met if none of your blue objectives are met (I'll admit, this is hard to explain further without an example).
So, really, there's no quick answer. Either you deal with all and every one of the 1048576 combinations individually, or you create and encode some sort of generic grouping scheme for your tests to drastically cut down on combinations. You can certainly create your full matrix scheme by said scheme, and maybe even dynamically generate your full 1048576 element matrix for a given set of combinations. But you cannot burn a whole candle by only burning half of it.
Edit II (and III):
I'm going to make one more guess. The levels you've proposed seem to be related to the number of objectives completed per each (and previous batch). So encode your results in to a string of ("T", and "F") and count the "F"s for each batch and determine from there. If the count of Fs is the number of characters in the string, no objective of the batch is completed, if the number of Fs is zero, all objectives of the batch are completed.
Lets say the batch after blue is purple. Could somebody achieve a goal in purple without completing all the green batch? If so, you have another color to assign for that possibility; if not, then, I would assume that these levels are ordered (orange between green and blue, maybe "brown" or something between blue and purple--you can tell I pulled this out of thin air-- and it should be fairly simple to cascade through these counts to determine the current "level".
If there is no ordering, then it's quite analogous to the situation I mentioned above: Each group has a result { "no objectives completed", "some objectives completed", "all objectives completed"}. There are x groups which means 3^x possible results. You should be basing your level matrix on these results, not the results of the tests previously mentioned.

Introduction
You can easy get combinations like this :
echo "<pre>";
$test = ["test_1","test_2","test_3"];
// Get Combination
$return = uniqueCombination($test);
//Sort
sort($return);
//Pretty Print
print_r(array_map(function($v){ return implode(",", $v); }, $return));
function uniqueCombination($in, $minLength = 1, $max = 10) {
$count = count($in);
$members = pow(2, $count);
$return = array();
for($i = 0; $i < $members; $i ++) {
$b = sprintf("%0" . $count . "b", $i);
$out = array();
for($j = 0; $j < $count; $j ++)
$b{$j} == '1' and $out[] = $in[$j];
count($out) >= $minLength && count($out) <= $max and $return[] = $out;
}
return $return;
}
Output
Array
(
[0] => test_1
[1] => test_2
[2] => test_3
[3] => test_1,test_2
[4] => test_1,test_3
[5] => test_2,test_3
[6] => test_1,test_2,test_3
)
The Problem
They are about 1,048,576 combination and i believe this is not the kind of array you want i would suggest a condition based combination rather than all possible combination
Example
// Game Conditions
$game = new Game();
$game->addCondition(new Condition(new Level(1), new Kill(30)));
$game->addCondition(new Condition(new Level(2), new Map(1), new Kill(10)));
$game->addCondition(new Condition(new Level(3), new Grunt(10)));
$game->addCondition(new Condition(new Level(4), new Knife(1), new Ak47(1)));
$game->addCondition(new Condition(new Level(5), new Grenade(1), new Combo(7)));
$game->addCondition(new Condition(new Level(6), new Kill(100), new Blow(10), new Stab(10)));
$game->addCondition(new Condition(new Level(7), new Herb(10), new Medipack(1), new Map(1), new Artwork(1)));
$game->addCondition(new Condition(new Level(8), new Grenade(20),new Artwork(5)));
// User Starts Game
$user = new User($game);
$user->task(new Map(1));
$user->task(new Herb(5));
$user->task(new Kill(10));
$user->task(new Kill(10));
$user->task(new Herb(10));
$user->task(new Kill(10));
$user->task(new Kill(10));
$user->task(new Ak47(1));
$user->task(new Knife(1));
$user->task(new Map(1));
$user->task(new Grunt(17));
$user->task(new Kill(60));
$user->task(new Combo(1));
$user->task(new Kill(40));
$user->task(new Medipack(1));
$user->task(new Artwork(1));
$user->task(new Grenade(1));
$user->task(new Combo(10));
$user->task(new Blow(10));
$user->task(new Stab(5));
$user->task(new Blow(10));
$user->task(new Stab(5));
$user->task(new Stab(5));
printf("\n<b>Total Point %s",number_format($user->getPoint(),0));
Output
+Task Map Added (1)
+Task Herb Added (5)
+Task Kill Added (10)
^Task Kill Updated (20)
^Task Herb Updated (15)
^Task Kill Updated (30)
*Level 1 Completed*
*Level 2 Completed*
^Task Kill Updated (40)
+Task Ak47 Added (1)
+Task Knife Added (1)
^Task Map Updated (2)
+Task Grunt Added (17)
*Level 3 Completed*
*Level 4 Completed*
^Task Kill Updated (100)
+Task Combo Added (1)
^Task Kill Updated (140)
+Task Medipack Added (1)
+Task Artwork Added (1)
+Task Grenade Added (1)
^Task Combo Updated (11)
*Level 5 Completed*
+Task Blow Added (10)
+Task Stab Added (5)
^Task Blow Updated (20)
^Task Stab Updated (10)
*Level 6 Completed*
*Level 7 Completed*
^Task Stab Updated (15)
<b>Total Point 1,280</b>
Classes Used
class Task {
private $no;
function __construct($no = 1) {
$this->no = $no;
}
function getNo() {
return $this->no;
}
function getName() {
return get_called_class();
}
function merge(Task $task) {
$this->no += $task->getNo();
return $this;
}
}
class User {
private $game;
private $point;
private $tasks = array();
function __construct(Game $game) {
$this->game = $game;
}
function getPoint() {
return $this->point;
}
function getTask() {
return $this->tasks;
}
function task(Task $task) {
if (isset($this->tasks[$task->getName()])) {
$this->tasks[$task->getName()]->merge($task);
printf("^Task %s \tUpdated (%s)\n", $this->tasks[$task->getName()]->getName(), $this->tasks[$task->getName()]->getNo());
} else {
printf("+Task %s \tAdded (%s)\n", $task->getName(), $task->getNo());
$this->tasks[$task->getName()] = $task;
}
$this->point += $task->getNo() * $task->d;
$this->game->notify($this);
}
}
class Condition {
private $task = array();
private $status = false;
function __construct(Level $level) {
$this->level = $level;
$tasks = func_get_args();
array_shift($tasks);
$this->task = new SplObjectStorage($tasks);
foreach ( $tasks as $task )
$this->task->attach($task);
}
function update(Game $game, User $user) {
if ($this->status)
return;
$n = 0;
foreach ( $this->task as $cTask ) {
foreach ( $user->getTask() as $task ) {
if ($cTask->getName() == $task->getName()) {
if ($task->getNo() >= $cTask->getNo())
$n ++;
}
}
}
if ($n === count($this->task) && ($game->getLevel()->getNo() + 1) == $this->level->getNo()) {
$this->status = true;
$game->setLevel($this->level);
printf("\n*Level %d Completed* \n\n", $this->level->getNo());
}
}
function getStatus() {
return $this->status;
}
}
class Game {
private $taskCondition;
private $level;
public function __construct() {
$this->taskCondition = new SplObjectStorage();
$this->level = new Level(0);
}
function setLevel(Level $level) {
$this->level = $level;
}
function getLevel() {
return $this->level;
}
function addCondition($condition) {
$this->taskCondition->attach($condition);
}
public function notify($user) {
foreach ( $this->taskCondition as $conditions ) {
if ($conditions->getStatus() === true) {
// detached completed condition
$this->taskCondition->detach($conditions);
continue;
}
$conditions->update($this, $user);
}
}
public function hasCondition() {
return count($this->taskCondition);
}
}
class Level extends Task{}
class Action extends Task{};
class Weporn extends Task{};
class Skill extends Task{};
class Tresure extends Task{};
class Medicine extends Task{};
class Kill extends Action{public $d = 5 ;};
class Blow extends Action{public $d = 7 ;};
class Stab extends Action{public $d = 10 ;};
class Map extends Tresure{public $d = 10 ;};
class Artwork extends Tresure{public $d = 20 ;};
class Knife extends Weporn{public $d = 5 ;};
class Grenade extends Weporn{public $d = 10 ;};
class Ak47 extends Weporn{public $d = 10 ;};
class Jump extends Skill{public $d = 2 ;};
class Grunt extends Skill{public $d = 4 ;};
class Combo extends Skill{public $d = 7 ;};
class Medipack extends Medicine{public $d = 5 ;};
class Herb extends Medicine{public $d = 5 ;};
Simple Online Demo

I have found the graphic display of your question as you mentioned.
|0|1|2|3|4|5|6|7|8|9|
|1|T|T|T|T|T|T|T|T|T|
|2|F|T|T|T|T|T|T|T|T|
|3|F|F|T|T|T|T|T|T|T|
|4|F|F|F|T|T|T|T|T|T|
|5|F|F|F|F|T|T|T|T|T|
|6|F|F|F|F|F|T|T|T|T|
|7|F|F|F|F|F|F|T|T|T|=>[7,9] if this is the answer
|8|F|F|F|F|F|F|F|T|T|
|9|F|F|F|F|F|F|F|F|T|
In my opinion you should check the conditions in reverse diagonal order, then left to right.
That means
first check [9,9]
if it fails check [8,8]
if this fails check [7,7]
if it gives true check [7,8]
if this gives false check [7,9] this must be the answer and the simple shortcut,
This method will reduce over all process time.

Related

Best practice to map points with level

I have a "points" field in the users table which increments points for the user after certain actions. I want to associate this with a "level" field (e.g. Level 1 , 2 ,3 ... etc).
I thought of creating a new table with numbers from 1 to 5000 for example, and map each range of numbers to their corresponding level. But I might need more points in the furure (maybe 100,000 points) which will make the table big. I feel there is a better solution.
I'd go with a simple little helper function/method:
<?php
function getLevel($numberOfPoints) {
if ( $numberOfPoints >= 10001 ) {
return 'Level 3';
} elseif ( $numberOfPoints >= 5001 ) {
return 'Level 2';
} elseif ( $numberOfPoints >= 1 ) {
return 'Level 1';
}
}
echo getLevel(52); // Level 1
echo getLevel(5000); // Level 1
echo getLevel(1233010); // Level 3
Extend the function with new levels as required

Generate all possible combinations

I have an app where users can customize the product they are going to purchase by choosing options from a menu. This menu has many sections, and each section may have a list of checkboxes for multichoice, or radiobuttons when only one option can be selected. The user must select at least one option in each section. The menu structure is something like this:
$sections = array();
$sections[1] = array(
'multichoice' => true,
'options' => array('A','B','C')
);
$sections[2] = array(
'multichoice' => false,
'options' => array('A','B','C','D')
);
$sections[3] = array(
'multichoice' => false,
'options' => array('A','B')
);
$sections[4] = array(
'multichoice' => true,
'options' => array('A','B','C','D','E')
);
Example: Sandwich is the product. Type of bread is one "section" of choice. You may want light bread, dark bread, milk bread or vegan bread. Only one option can be chosen under this section. Now in the "salad" section, you may choose more than one type of salad to add to the bread.
Now, my boss asked for me to create a page listing all possible combinations in case the user is too lazy to build the product himself. So I must be able to generate a structure like this:
$combinations = array(
array(
1 => array('A','B'),
2 => 'A',
3 => 'A',
4 => array('B','D','E')
),
array(
1 => array('A'),
2 => 'B',
3 => 'A',
4 => array('A','B')
)
// etc...
);
I have managed to find all possible combinations using a random approach, generating hashes for comparision with what has already been generated. This actually works, but runs very slowly (this is brute-force basically):
...
function generate(){
$result = array();
$ids = array();
foreach($this->getSections() as $sect){
$items = $this->getSectionOptions($sect['id']);
if($sect['multi']=='N'){
$item = $items[rand(0, count($items)-1)];
$result[$sect['id']] = $item['id'];
$ids[] = $item['id'];
} else {
$how_many = rand(1,count($items));
shuffle($items);
for($i=1;$i<=$how_many;$i++){
$item = array_shift($items);
$result[$sect['id']][] = $item['id'];
$ids[] = $item['id'];
}
}
}
sort($ids);
return array(
'hash' => implode(',',$ids),
'items' => $result
);
}
function generateMany($attempts=1000){
$result = array();
$hashes = array();
for($i=1;$i<=$attempts;$i++){
$combine = $this->generate();
if(!in_array($combine['hash'],$hashes)){
$result[] = $combine['items'];
$hashes[] = $combine['hash'];
}
}
return $result;
}
...
I want your help to create something more precise and faster. Remember that each combination must have at least one option of each section. And also keep in mind that the order of options in multichoice sections is irrelevant, (i.e. E,B,A is the same as B,E,A)
Thanks
Thanks this was a puzzle really fun to do!
So how did I solve, recursion recursion recursion :D
I've started with the multi choice since it is the hardest one! (and actually it will solve also the mixing)
Explanation
To explain how I got it to work, lets take an example with choices A, B, C. We would have then the following combinations:
A B
A B C
A C
B
B C
C
If we take a look closer, we can see some how a pattern. Lets take the result list the first element (A)
B
B C
C
---
B
B C
C
Hmm, interesting... Now lets take again the first element (B)
C
---
C
It's a simple case but this will happen in any size case.
So I've made the recursion script to get to the end, then adding backwards, the iteration combination and duplicating it with the previous value.
And voila! this is it!
For the final mix where all the elements are demanded, I've made a very similar method, but it must contain 1 element of each
And it's quite fast!
Total time for 100000 iterations with 126 combinations: 14.410287857056 seconds
if you find any mistake ping me :D
Code
https://gist.github.com/MLoureiro/a0ecd1ef477e08b6b83a

(PHP) Trouble iterating through objects

Background: I am pulling in XML objects from a public IMDB-for-TV API. My goal is to pull certain values out of those objects -- like, say, a list of every episode title, put into an array so that I can do what I want with it.
Problem: Although I can write code that does execute exactly the way I want it, it spits out errors as it does so, so I know something's wrong with the way I iterate over my objects... but I can't figure out a better way. I'd really love some advice.
So first off, here's a look at the object I'm dealing with.
SimpleXMLElement Object
(
[Episodelist] => SimpleXMLElement Object
(
[Season] => Array
(
[0] => SimpleXMLElement Object
(
[episode] => Array
(
[0] => SimpleXMLElement Object
(
[epnum] => 1
[seasonnum] => 01
[prodnum] => 101
[airdate] => 1989-07-05
[link] => http://www.tvrage.com/Seinfeld/episodes/305788
[title] => Good News, Bad News
)
[1] => SimpleXMLElement Object
(
[epnum] => 2
[seasonnum] => 02
[prodnum] => 103
[airdate] => 1990-05-31
[link] => http://www.tvrage.com/Seinfeld/episodes/150618
[title] => The Stakeout
)
The show is an object containing an object "episode list", which contains object "season x", which contains object "episode y", which contains the value I'm after -- "title". So for each episode, I want to grab $this->list->Season[X]->episode[Y]->title.
Here is the function I wrote to do this. It takes two arguments: the season number, and the episode number.
public function showEpisode($s,$e) {
$s = $s - 1; // To compensate for zero-indexing.
$e = $e - 1;
if (!empty($this->list->Season[$s]) && !empty($this->list->Season[$s]->episode[$e])) {
return $this->list->Season[$s]->episode[$e]->title;
} else {
return 0;
}
}
I know there's something wrong with how it's written.
Anyway, here's my code for actually working with it.
$season = 1;
$episode = 1;
$errors = 0;
while ($errors < 2) {
if ($xfiles->showEpisode($season,$episode)!= 0) {
echo $xfiles->showEpisode($season,$episode) . "<br />";
$episode++;
$errors = 0;
} else {
$errors++;
$season++;
$episode = 1;
}
}
My logic is:
Start at Season 1, Episode 1.
Echo the title of the current episode.
If that fails, increment the error-counter by one, and go up a season by incrementing $season++ and putting the $episode counter back at 1.
If you get two errors in a row, it means going up a season failed, because we've hit the end of the show, so the loop ends.
Desired result: A neat, simple list of every episode title.
Actual result: A blank page, using this code; nothing is ever returned. When I used my last version of the function, which I have very stupidly deleted and cannot seem to recreate, I did echo a full set of episodes exactly as I wanted -- but after each season, and three times at the end of the file, I got "Trying to get property of non-object" errors, because of the calls to non-existent episodes.
Question: What is the correct, practical way to loop through a large object like this? What conditions should I use in my showEpisode function to check if the result will exist or not?
Thanks a ton to anyone who can help, I've done my best and Googled a lot but I'm just baffled.
This looks like a job for a foreach-loop.
foreach ($xfiles->list->Season as $season) {
foreach ($season->episode as $episode) {
echo $episode->title . "<br />";
}
}
Alternatively (or should I say ideally), put this inside a method of the list object and replace $xfiles with $this.

PHP-How To Pair Up items in Array based on condition

How can I pair up items in an array?
Let's say I have an array of Fighters. And I want to pair them up based on their Weights. Fighters with closest weights should be paired as the Best Match. But if they are in the same team they shouldn't be paired.
**---Team 1--**
Fighter A Weight is 60
Fighter B Weight is 65
**--Team 2--**
Fighter C Weight is 62
Fighter D Weight is 60
**--Team 3--**
Fighter E Weight is 64
Fighter F Weight is 66
Output:
Fighter A VS Fighter D
Fighter B VS Fighter F
Fighter C VS Fighter E
I've been researching for this topic and found something similar but not quite:
Random But Unique Pairings, with Conditions
Would really appreciate some help. Thanks in advance!
I liked your question a lot, so I made a full robust version of it.
<?php
header("Content-type: text/plain");
error_reporting(E_ALL);
/**
* #class Fighter
* #property $name string
* #property $weight int
* #property $team string
* #property $paired Fighter Will hold the pointer to the matched Fighter
*/
class Fighter {
public $name;
public $weight;
public $team;
public $paired = null;
public function __construct($name, $weight, $team) {
$this->name = $name;
$this->weight = $weight;
$this->team = $team;
}
}
/**
* #function sortFighters()
*
* #param $a Fighter
* #param $b Fighter
*
* #return int
*/
function sortFighters(Fighter $a, Fighter $b) {
return $a->weight - $b->weight;
}
$fighterList = array(
new Fighter("A", 60, "A"),
new Fighter("B", 65, "A"),
new Fighter("C", 62, "B"),
new Fighter("D", 60, "B"),
new Fighter("E", 64, "C"),
new Fighter("F", 66, "C")
);
usort($fighterList, "sortFighters");
foreach ($fighterList as $fighterOne) {
if ($fighterOne->paired != null) {
continue;
}
echo "Fighter $fighterOne->name vs ";
foreach ($fighterList as $fighterTwo) {
if ($fighterOne->team != $fighterTwo->team && $fighterTwo->paired == null) {
echo $fighterTwo->name . PHP_EOL;
$fighterOne->paired = $fighterTwo;
$fighterTwo->paired = $fighterOne;
break;
}
}
}
First, the fighters are kept in classes, which makes it easier to assign properties to them (if you haven't done so yourself, I urge you to do!)
Make an array of fighters, and assign them names, weights and teams.
sort the array by weight (using usort() and a sorting function sortFighters() to sort by the weight property of each element.
Iterate through the array and match based on:
Fighter one is not already matched
Fighter two is not on the same team as Fighter one
Fighter two is not already matched
When a match is found, store the object pointer of each matching fighters to each other (So it's not null anymore, plus you can access each of fighters' pairs by going to $fighterVariable->paired)
Finally, print the result.
This is just extension of Truth's answer based on comments:
The first thing I'd do differently is basic keeping trace players.
$unassignedPlayers = $fighterList;
Than the algorithm would work in the way: prepare list of teams (if you're using database, use SELECT DISTINCT or GROUP BY teams.id):
$teams = array();
foreach( $fighterList as $fighter){
$teams[] = $figter->team;
}
$teams = array_unique( $teams);
Next we'll need method that will split array of fighters (let's say, we have teams {A,A,B,B,C,C} we want to split that into {A,A}, {B,B,C,C}):
// Don't use string type declaration, it's just ilustrating
function splitFighters( array $input, string $team){
$inteam = array();
$outteam = array();
foreach( $input as $fighter){
if( $figter->team == $team){
$inteam[] = $fighter;
} else {
$outteam[] = $fighter;
}
}
return array( $inteam, $outteam);
}
Now that we do have that, we may create function that will sort team members:
function assignFighters( array &$input, array $teams, array &$output){
// Nothing to work with?
if( !count( $input)){
return true;
}
// No team left and still unassigned players, that fatal error
if( !cont( $teams)){
throw new Exception( 'Unassigned players occurred!');
}
// Shift team
$team = array_shift( $teams);
// Split into in and out team
list( $inteam, $outteam) = splitFighters( $input, $team);
// Inteam is already empty (let's say all players were assigned before)
// Just go deeper (where's DiCaprio?)
if( !count( $inteam) && count( $teams)) {
return assignFighters( $input, $teams, $output)
}
// There're unassigned and nonassignable players in this team
// This is error and we'll have to deal with it later
if( !count($outteam)){
$input = $inteam; // Propagate unassigned players to main
return false;
}
// Sort both teams by fighters weight
// Uses Truth's comparison function
usort($inteam, "sortFighters");
usort($outteam, "sortFighters");
// Fig = Fighter
while( $fig1 = array_shift( $inteam)){
// Are there any players to work with
if( !count( $outteam)){
array_unshift( $inteam, $fig1);
$input = $inteam; // Propagate unassigned players to main
return false;
}
// Assign players to each other
$fig2 = array_shift( $outteam);
$fig1->paired = $fig2;
$fig2->paired = $fig1;
// This will propagate players to main nicely
$output[] = $fig1;
$output[] = $fig2;
}
// Still here? Great! $inteam is empty now
// $outteam contains all remaining players
$input = $outteam;
return assignFighters( $input, $teams,$output);
}
Until this point you could use Truth's algorithm, but this should have better weight matching and represents what you intend more clearly but anyway now comes $unassignedPlayers into work:
$assignedPlayers = array();
$state = assignFighters( $unassignedPlayers, $teams, $assignedPlayers);
// Note:
$state === !(bool)count($unassignedPlayers)
// should evaluate as true, otherwise I'm having an error in algorithm
So what now... If you have $state === false resp. count( $unassignedPlayers) > 0 something went wrong and we need to apply some magic. How will that magic work:
// Keep the trace of swapped players so we don't end up in endless loop
$swappedPlayers = array();
// Browse all unassigned players
while( $fig1 = array_shift( $unassignedPlayers)){
// Store as swapped
$swappedPlayers[] = $fig1;
// At first check whether there's not unassigned player in the different team
// this shouldn't occur in first iteration (all fighters should be from one team
// in the beginning) but this is most effective part of this method
foreach( $unassignedPlayers as $key => $fig2){
if( $fig2->team != $fig1->team){
$fig1->pair = $fig2;
$fig2->pair = $fig1;
continue;
}
}
// No luck, normal magic required
list( $inteam, $outteam) = splitFighters( $assignedPlayers, $fig1->team);
$fig2 = null; // I like my variables initialized, this actually quite important
// Now select someone from $outteam you will want to swap fights with.
// You may either iterate trough all players until you find best weight
// match or select it random, or whatever, I'll go with random,
// it's your job to implement better selection
$i = 1000; // Limit iterations
while(($i--) > 1){
$key1 = array_rand( $outteam, 1);
if( $outteam[$key]->team == $fig1->team){
continue; // No point in swapping fit team member
}
// No recursive swaps
if( in_array( $outteam[$key], $swappedPlayers)){
continue;
}
// This may speed things really up:
// That means we'll get rid of 2 players due to foreach loop at the beggining
// However I'm not sure how this condition will really work
if( $outteam[$key]->pair->team == $fig1->team){
continue;
}
// Store matched fighter
$fig2 = $outteam[$key];
// Unset pair from another fighter
$fig2->pair->pair = null;
// Find the pair in $assignedPlayers and move it to $unassignedPlayers
$key = array_search( $fig2->pair, $assignedPlayers);
if( $key === false){
throw new Exception( 'Cannot find pair player');
}
unset( $assignedPlayers[$key]);
$unassignedPlayers[] = $fig2->pair;
$swappedPlayers[] = $fig2->pair;
// Remove pair from self
$fig2->pair = null;
$swappedPlayers[] = $fig2;
break; // hh, try forgetting this one :)
}
// This shouldn't be happening
if( $fig2 === null){
throw new Exception( 'Didn\'t find good match in 1000 iterations.');
}
// Ok now just make matches as go to the next iteration
$fig1->pair = $fig2;
$fig2->pair = $fig1;
// And store those
$assignedPlayers[] = $fig1;
$assignedPlayers[] = $fig2;
}
I've written all this out of my head (it was challenge), test it and leave notes in comments please :)
Sort the array by weight. You will then have pairs of weights that are close to each other.

Count elements in adjacent array

I have an array that has a hierarchic structure. In this example there are two elements in the first level, 83 and 82. 82 has children 84 and 87 . 87 is a child of 84.
Array
(
[_children] => Array
(
[0] => Array
(
[id] => 11
[uid] => 24
[rid] => 83
[date] => 2011-06-18 15:08:10
)
[1] => Array
(
[id] => 10
[uid] => 24
[rid] => 82
[date] => 2011-06-18 15:08:07
[_children] => Array
(
[0] => Array
(
[id] => 12
[uid] => 82
[rid] => 84
[date] => 2011-06-18 15:30:34
[_children] => Array
(
[0] => Array
(
[id] => 13
[uid] => 84
[rid] => 87
[date] => 2011-06-18 16:11:50
)
)
)
)
)
)
)
I want to loop through that array and store some information for each level in another array. This is how my array should look like at the end.
Array
(
[0] => Array
(
[elements] => 2
)
[1] => Array
(
[elements] => 1
)
)
E.g. this shows that at level 0 are 3 nodes and at level 1 there are 5 nodes. How can I do that? Any ideas?
I would just count the numbers.
This means each item of the base key plus the same for it's children.
In opposite to recursion, I decided to use a stack for this.
To have a value per level, the counter needs a level context as well, which is added to the stack next to the node:
$count = array();
$stack[] = array(-1, $arr);
while($item = array_shift($stack)) {
list($level, $node) = $item;
$count[$level] = isset($count[$level]) ? $count[$level]+1 : 1;
if (!isset($node['_children']))
continue;
foreach($node['_children'] as $item)
$stack[] = array($level+1, $item);
}
var_dump($count);
This does output:
array(4) {
[-1]=> int(1)
[0] => int(2)
[1] => int(1)
[2] => int(1)
}
where -1 is the root node only containing children. So you might want to remove it after processing:
array_shift($count);
Edit: Storing the nodes
The following code adds a collector of nodes per level as well, called $nodes. It's a bit redundant as per count($nodes[$level]) is $count[$level], but it's just to show how to collect nodes as well:
$count = array();
$nodes = array(); // nodes per level go here.
$stack[] = array(-1, $arr);
while($item = array_shift($stack)) {
list($level, $node) = $item;
$count[$level] = isset($count[$level]) ? $count[$level]+1 : 1;
$nodes[$level][] = $node; // set here!
if (!isset($node['_children']))
continue;
foreach($node['_children'] as $item)
$stack[] = array($level+1, $item);
}
var_dump($count, $nodes);
Discussion
Using a stack prevents to make recursion function calls and reduces complexity. The idea behind it is fairly simple:
The stack is initialized with the first node (which only contains children) - the tree's root node.
An entry on the stack contains the node to be counted and it's level
Each element on the stack is treated the same:
An item gets removed from the beginning (or end) of the stack.
The counter for it's level is increased by one.
If it has children, each child-node is added to the stack with it's level.
The stack will be processed until all elements are consumed.
This ensures that every node will be counted with a minimal overhead. The strategy of consuming the stack can be fully controlled. Either FIFO (First In, First Out), LIFO (Last In, First Out) - which is easy to implement - or even a weighted strategy (first process child-nodes that have no/the most children to consume them fast) or even random stack consumption to distribute the constraints the data-structure implies accros the whole stack.
Comparing Stack to Recursion Function Stack
Comparing using an own stack to the PHP's function stack first of all shows that both ways have things in common. Both make use of a stack.
But the main difference is, that an own stack prevents using the function calls. Calling a function, especially recursively has multiple implications that can be avoided.
Another key difference is, that an own stack always allows to interrogate with it while there is no control over the PHP language's own stack.
Next to that, an own stack can be processed sequentially, while the function stack is a recursive construct that maps the processing of the data 1:1 into the program flow.
While recursion will hide away the recursive nature of the data (tree) for the code within the function (which can simplify processing), there is the need to offer the state that gets hidden away by function calls to be introduced as function parameters.
Managing the data to solve the problem therefore is not less but equal or more complex with recursion.
So next to the overhead of the function calls, one can already smell an overhead for data.
It's worth to see this in the light of the PHP language itself.
To make recursion work, PHP needs to add additional variable tables onto the function stack. These tables need to be managed before and after each function call.
The recursion also needs you to pass variables by references. This is an additional overhead, as those values need to be set/processed before and after the function ends by PHP.
Such a reference is needed in the recursion to share the data structure for the counter. This overhead in a recursive implementation could be reduced by implementing the recursion into a class of it's own in PHP, which has not been suggested so far and is probably worth to consider.
Like the counter array in a user stack implementation, such a class could share the counter between all function calls, providing a hash accessible to the class's member function(s) w/o making use of variable references. Further on, complexity in solving the problem can be distributed over different functions with in the class or even injected as a dependency. PHP offers a default recursive iterator interface already.
Another place to reduce one could make use of is the global variable table ($GLOBALS) but with the downside of making the code less re-useable which signals a design flaw. Even if design is not an issue, that so called superglobals array needs to be managed between function calls as as well. That results in a quite-like behavior to passing by reference for which it was considered to prevent it. Therefore it is not a preferable solution.
Recursive Implementation by Class
This is a recursive implementation based on the idea raised in the discussion above to get more grip on it:
/**
* class implementation of a recursive child counter
*
* Usage:
*
* Object use:
*
* $counter = new LevelChildCounter($arr, '_children');
* $count = $counter->countPerLevel();
*
* Functional use:
*
* $count = LevelChildCounter::count($arr, '_children');
*/
class LevelChildCounter {
private $tree;
private $childKey;
private $counter;
public function __construct(array $tree, $childKey) {
$this->tree = $tree;
$this->childKey = $childKey;
}
/**
* functional interface of countPerLevel
*/
public static function count(array $tree, $childKey) {
$counter = new self($tree, $childKey);
return $counter->countPerLevel();
}
private function countUp($level) {
isset($this->counter[$level])
? $this->counter[$level]++
: $this->counter[$level] = 1;
}
private function countNode($node, $level) {
// count node
$this->countUp($level);
// children to handle?
if (!isset($node[$this->childKey]))
return;
// recursively count child nodes
foreach($node[$this->childKey] as $childNode)
$this->countNode($childNode, $level+1)
;
}
/**
* Count all nodes per level
*
* #return array
*/
public function countPerLevel() {
$this->counter = array();
$this->countNode($this->tree, -1);
return $this->counter;
}
}
$count = LevelChildCounter::count($arr, '_children');
array_shift($count);
var_dump($count);
And it's output:
array(3) {
[0]=> int(2)
[1]=> int(1)
[2]=> int(1)
}
It seems that a recursive function is the easiest way to implement this.
The function should have as an argument the _children array and should keep the results, a "results" array and level counter.
The function should be called like getStructure($myarray['_children']), which calls the base function, _getStructure($array, &$results, $depth): $results is an empty array at first and $depth set to zero.
The function _getStructure then loops over the array (foreach) and increases the $results[$depth]++. If a child contains the _children field, this function calls _getStructure again, with an increased $detph++.
After _getStructure returns, the $results array is filled with your given structure. This is due to the Pass by Reference, which will pass the $results array to functions without copying it: all changes to it are seen by the scope which called it. See PHP docs.
I'm assuming you have a fixed depth, it would be quite simple to adapt it for unlimited depth:
<?php
function count_child( $node, &$results, $depth, $curr_depth = 0){
if( $curr_depth >= $depth)
return;
if( is_set( $results[$curr_depth] ))
$results[$curr_depth] += count( $node['_children'] );
else
$results[$curr_depth] = 1;
if( is_array( $node ) && is_set( $node['_children']) )
foreach( $node['_children'] as $next_node){
count_child( $next_node, $result , $depth , $curr_depth + 1);
}
}
?>

Categories