I need to return family data (parents, siblings and partners) for 'x' number of generations (passed as $generations parameter) starting from a single person (passed as $id parameter). I can't assume two parents, this particular genealogy model has to allow for a dynamic number of parents (to allow for biological and adoptive relationships). I think my recursion is backwards, but I can't figure out how.
The code below is triggering my base clause 5 times, once for each generation, because $generation is being reduced by 1 not for every SET of parents but for every parent. What I want is for the base clause ($generations == 0) to only be triggered once, when 'x' number of generations for all parents of the initial person are fetched.
public function fetchRelationships($id = 1, $generations = 5, $relationships = array())
{
$perId = $id;
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
$relationships[$perId]['partners'] = $this->fetchPartners($perId);
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$parent = $parentRel->getPer2();
$pid = $parent->getId();
$relationships[$perId]['siblings'][$pid] = $this->fetchSiblings($perId, $pid);
$perId = $pid;
$relationships[$perId] = $this->fetchRelationships($perId, $generations, $relationships);
}
}
return $relationships;
}
}
The methods fetchPartners, fetchParents and fetchSiblings just fetch the matching entities. So I am not pasting them here. Assuming that there are 2 parents, 5 generations and each generation has 2 parents then the return array should contain 62 elements, and should only trigger the base clause once those 62 elements are filled.
Thanks, in advance, for any help.
-----------Edit--------
Have rewritten with fetchSiblings and fetchPartners code removed to make it easier to read:
public function fetchRelationships($id = 1, $generations = 5, $relationships = array())
{
$perId = $id;
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$perId = $parentRel->getPer2()->getId();
$relationships[$perId] = $this->fetchRelationships($perId, $generations, $relationships);
}
}
return $relationships;
}
}
Garr Godfrey got it right. $generations will equal zero when it reaches the end of each branch. So you'll hit the "base clause" as many times as there are branches. in the foreach ($parents as $parentRel) loop, you call fetchRelationships for each parent. That's two branches, so you'll have two calls to the "base clause". Then for each of their parents, you'll have another two calls to the "base clause", and so on...
Also, you're passing back and forth the relationships, making elements of it refer back to itself. I realize you're just trying to retain information as you go, but you're actually creating lots of needless self-references.
Try this
public function fetchRelationships($id = 1, $generations = 5)
{
$perId = $id;
$relationships = array();
if ($generations == 0) {
return $relationships;
} else {
$parents = $this->fetchParents($perId);
$relationships[$perId]['parents'] = $parents;
if (!empty($parents)) {
--$generations;
foreach ($parents as $parentRel) {
$perId = $parentRel->getPer2()->getId();
$relationships[$perId] = $this->fetchRelationships($perId, $generations);
}
}
return $relationships;
}
}
you'll still hit the base clause multiple times, but that shouldn't matter.
you might be thinking "but then i will lose some of the data in $relationships", but you won't. It's all there from the recursive returns.
If you're pulling this out of a database, have you considered having the query do all of the leg work for you?
Not sure how you need the data stacked or excluded, but here's one way to do it:
<?php
class TreeMember {
public $id;
// All three should return something like:
// array( $id1 => $obj1, $id2 => $obj2 )
// and would be based on $this->$id
public function fetchParents(){ return array(); }
public function fetchPartners(){ return array(); };
public function fetchSiblings(){ return array(); };
public function fetchRelationships($generations = 5)
{
// If no more to go
if ($generations == 0) { return; }
$branch = array();
$branch['parents'] = $this->fetchParents();
$branch['partners'] = $this->fetchPartners();
$branch['partners'] = $this->fetchSiblings();
// Logic
$generations--;
foreach($branch as $tmType, $tmArr)
{
foreach($tmArr as $tmId => $tmObj)
{
$branch[$tmType][$tmId] =
$mObj->fetchRelationships
(
$generations
)
);
});
return array($this->id => $branch);
}
}
Related
I have the following method
public function getNextAvailableHousesToAttack(\DeadStreet\ValueObject\House\Collection $collection, $hordeSize)
{
$houses = $collection->getHouses();
$housesThatCanBeAttacked = array_filter($houses, function($house) use (&$hordeSize) {
if(!isset($house)) {
return false;
}
$house = $this->houseModel->applyMaxAttackCapacity($house, $hordeSize);
if($this->houseModel->isAttackable($house)) {
return $house;
}
return false;
});
return $housesThatCanBeAttacked;
However, this array can be huge.
I want to limit $housesThatCanBeAttacked to whatever the size of $hordeSize is set to, as I only need as many houses as there are zombies in the horde to attack this round.
However, this array $housesThatCanBeAttacked could end up containing 1 million houses, where there are only 100 in the zombie horde.
Is there a way to limit the size of this array built from the callback?
You could simply use a loop, and stop processing the array when you have enough houses.
$houses = $collection->getHouses();
housesThatCanBeAttacked[];
$i = 0;
foreach ($houses as $house) {
$house = $this->houseModel->applyMaxAttackCapacity($house, $hordeSize);
if ($this->houseModel->isAttackable($house)) {
housesThatCanBeAttacked[] = $house;
if (++$i == $hordeSize) {
break;
}
}
}
I would add a counter of houses outside of callback and use it inside callback to skip all excessive houses. Here is how a solution can look like:
public function getNextAvailableHousesToAttack(\DeadStreet\ValueObject\House\Collection $collection, $hordeSize)
{
$houses = $collection->getHouses();
$counter = $hordeSize;
$housesThatCanBeAttacked = array_filter($houses, function($house) use (&$hordeSize, &$counter) {
if($counter == 0 && !isset($house)) {
return false;
}
$house = $this->houseModel->applyMaxAttackCapacity($house, $hordeSize);
if($this->houseModel->isAttackable($house)) {
$counter--;
return $house;
}
return false;
});
return $housesThatCanBeAttacked;
This way you array_filter won't return more then $counter values.
I've a university project in which I've to print the relations between students in different classes level by level. The idea is if we have John and Kris studying in the same class they are friends of first level, if Kris studies with Math in same class then John and Math are friends of second level. I researched the problem and I found algorithms like this, but my main problem is that I use objects as input data :
<?php
class Student {
private $id = null;
private $classes = [];
public function __construct($id) {
$this->id = $id;
}
public function getId() {
return $this->id;
}
public function getClasses() {
return $this->classes;
}
public function addClass(UClass $class) {
array_push($this->classes, $class);
}
}
class UClass {
private $id = null;
private $students= [];
public function __construct($id) {
$this->id = $id;
}
public function getId() {
return $this->id;
}
public function getStudents() {
return $this->students;
}
public function addStudent(Student $student) {
array_push($this->students, $student);
$student->addClass($this);
}
}
function getRelations(Student $start_student, &$tree = array(), $level = 2, &$visited) {
foreach ($start_student>Classes() as $class) {
foreach ($class->Students() as $student) {
if($start_student->getId() != $student->getId() && !is_int(array_search($student->getId(), $visited))) {
$tree[$level][] = $student->getId();
array_push($visited, $student->getId());
getRelations($student, $tree, $level+1, $visited);
}
}
}
}
$class = new UClass(1);
$class2 = new UClass(2);
$class3 = new UClass(3);
$student = new Student(1);
$student2 = new Student(2);
$student3 = new Student(3);
$student4 = new Student(4);
$student5 = new Student(5);
$student6 = new Student(6);
$class->addStudent($student);
$class->addStudent($student2);
$class->addStudent($student4);
$class2->addStudentr($student2);
$class2->addStudent($student4);
$class2->addStudent($student5);
$class3->addStudent($student4);
$class3->addStudent($student5);
$class3->addStudent($student6);
$tree[1][] = $student->getId();
$visited = array($student->getId());
getRelations($student, $tree, 2, $visited);
print_r($tree);
I'm stuck at writing getRelations() function that should create an array that is something like
Array ( [1] => Array ( [0] => 1 ) [2] => Array ( [0] => 2 [1] => 4 ) [3] => Array ( [0] => 5 [1] => 6 ) )
but I can't get the recursion right(or probably the whole algorithm). Any help will be greatly appreciated.
The logic in your recursive procedure is not correct. Example:
Say you enter the procedure for some level A and there are actually 2 students to be found for a connection at that level.
You handle the first, assign the correct level A, mark him as "visited".
Then, before getting to the second, you process level A+1 for the first student. Somewhere in his "chain" you may also find the second student that was waiting to get handled at level A. However, he now gets assigned some higher level A+n, and is then marked as visited.
Next, when the recursion for student1 is finished, you continue with the second. However, he has already been "visited"...
(By the way, I do not quite understand (but my php is weak...) why your first invocation of GetRelations specifies level=2.)
Anyway, to get your logic right there's no need for recursion.
Add a property "level" to each student. Put all students also in an overall collection "population".
Then, for a chosen "startStudent", give himself level=0, all other students level=-1.
Iterate levels and try to fill in friendship levels until there's nothing left to do. My php is virtually non-existent, so I try some pseudo-code.
for(int level=0; ; level++) // no terminating condition here
{
int countHandled = 0;
for each (student in population.students)
{
if (student.level==level)
{
for each (class in student.classes)
{
for each (student in class.students)
{
if(student.level==-1)
{
student.level = level+1;
countHandled++;
}
}
}
}
}
if(countHandled==0)
break;
}
Hope this helps you out. Of course, you still have to fill in the tree/print stuff; my contribution only addresses the logic of assigning levels correctly.
I come up with that function(not sure if it's the best solution, but it works with the class objects)
function print_students(Student $start_student, &$tree = array(), $lvl = 1) {
if (!$start_student) {
return;
}
$tree[$lvl][] = $start_student->getId();
$q = array();
array_push($q, $start_student);
$visited = array($start_student->getId());
while (count($q)) {
$lvl++;
$lvl_students = array();
foreach ($q as $current_student) {
foreach ($current_student->getClasses() as $class) {
foreach ($class->getStudents() as $student) {
if (!is_int(array_search($student->getId(), $visited))) {
array_push($lvl_students, $student);
array_push($visited, $student->getId());
$tree[$lvl][] = $student->getId();
}
}
}
}
$q = $lvl_students;
}
}
I have a question about a recursive PHP function.
I have an array of ID’s and a function, returning an array of „child id’s“ for the given id.
public function getChildId($id) {
…
//do some stuff in db
…
return childids;
}
One childid can have childids, too!
Now, I want to have an recursive function, collecting all the childids.
I have an array with ids like this:
$myIds = array("1111“,"2222“,"3333“,“4444“,…);
and a funktion:
function getAll($myIds) {
}
What I want: I want an array, containing all the id’s (including an unknown level of childids) on the same level of my array. As long as the getChildId($id)-function is returning ID’s…
I started with my function like this:
function getAll($myIds) {
$allIds = $myIds;
foreach($myIds as $mId) {
$childids = getChildId($mId);
foreach($childids as $sId) {
array_push($allIds, $sId);
//here is my problem.
//what do I have to do, to make this function rekursive to
//search for all the childids?
}
}
return $allIds;
}
I tried a lot of things, but nothing worked. Can you help me?
Assuming a flat array as in your example, you simply need to call a function that checks each array element to determine if its an array. If it is, the function calls it itself, if not the array element is appended to a result array. Here's an example:
$foo = array(1,2,3,
array(4,5,
array(6,7,
array(8,9,10)
)
),
11,12
);
$bar = array();
recurse($foo,$bar);
function recurse($a,&$bar){
foreach($a as $e){
if(is_array($e)){
recurse($e,$bar);
}else{
$bar[] = $e;
}
}
}
var_dump($bar);
DEMO
I think this code should do the trick
function getAll($myIds) {
$allIds = Array();
foreach($myIds as $mId) {
array_push($allIds, $mId);
$subids = getSubId($mId);
foreach($subids as $sId) {
$nestedIds = getAll($sId);
$allIds = array_merge($allIds, $nestedIds);
}
}
return $allIds;
}
I'm finding it difficult to find a card ranking tutorial, or even some source code to read off to point me in the right direction.
I'm basically going in the direction of creating multiple functions with multiple in_array And writing them from scratch, as doing this will make it easy for three of a kind. Example
function is_trip($card1, $card2, $card3)
{
if (in_array($card1, $aces) && in_array($card2, $aces) && in_array($card3, $aces))
{
$score = 9500;
$rank = 'Three Aces';
}
if (in_array($card1, $kings) && in_array($card2, $kings) && in_array($card3, $kings))
{
$score = 9000;
$rank = 'Three Kings';
}
} And so on ...
So that would most likely work with trips, but then for a straight flush I would use a method of the way the cards are organized by number, as they're in the array in suit order.
So a straight flush would be hopefully as simple as $highcard + $lowcard / 2 == $midcard if that is true then you have a straight flush.
As for a straight, I'm stuck and would most likely have to use an array with my current mind set but writing that would seem like a lot of code when it is most likely simpler..
And for flushes it wouldn't be difficult to use the in_array as I'd only need to range 1-13 14-26 27-39 40-52 in an in_array to determine a flush, but then I'd need $highcard value $midcard value to also play a role to determine a flush against others.
You may of got to this point and thought, What's his question??
Well, my question is.. Am I going the right way about ranking the cards, should I use a bucket counting method to put the ranks into a bit-code and use a lookup table? Or have you any advice on where I should be heading if my methods of doing it are completely stupid..
Thanks in advance for any help.
It's very rough, and untested, but what about something like: -
<?php
$hand = new Hand;
$hand->addCard(new Card(RankInterface::ACE, SuitInterface::SPADE));
$hand->addCard(new Card(RankInterface::QUEEN, SuitInterface::HEART));
$hand->addCard(new Card(RankInterface::KING, SuitInterface::CLUB));
$isFlush = isFlush($hand);
Using something like: -
<?php
namespace Card;
interface SuitInterface {
const
SPADE = 'spade',
HEART = 'heart',
DIAMOND = 'diamond',
CLUB = 'club';
}
interface RankInterface {
const
JOKER = 0,
ACE = 1,
TWO = 2,
THREE = 3,
FOUR = 4,
FIVE = 5,
SIX = 6,
SEVEN = 7,
EIGHT = 8,
NINE = 9,
TEN = 10,
JACK = 11,
QUEEN = 12,
KING = 13;
}
class Card {
protected
$rank,
$suit;
public function __construct($rank, $suit) {
$this->rank = $rank;
$this->suit = $suit;
}
public function getRank() {
return $this->rank;
}
public function getSuit() {
return $this->suit;
}
public function isSameRank(Card $card) {
return $this->getRank() === $card->getRank();
}
public function isSameSuit(Card $card) {
return $this->getSuit() === $card->getSuit();
}
}
class Hand
{
protected
$storage = array();
public function addCard(Card $card) {
$this->storage[] = $card;
return $this;
}
public function asArray() {
return $this->storage;
}
}
function isFlush(Hand $hand) {
$cards = $hand->asArray();
$first = array_shift($cards);
foreach($cards as $card) {
if( ! $first->isSameSuit($card)) {
return false;
}
}
return true;
}
You would then just need to add some individual pieces of logic for the various valid hands/combos.
Recently, I was given a small task of using graph data structure as core to make a web application. I started out with an idea of simple path optimization problem, which can be completed in few days. The problem is that I am not able decide the correct framework for this task. Using just PHP was the only thing i could think of given the time constraint.
So, how can I represent a graph data structure using PHP's custom data structure( array).
Furthermore,can you suggest some other frameworks on which i can work for this task.
You can use the array to keep an adjacency list.
PHP's array has two uses: it can be a list of objects, or an associative array, which associates one object with another. You can also use an associative array as a poor man's set, by keeping the using the data from the set as keys in the associative array. Since it is usual to associate data to vertices, and edges, we can actually make use of this in a natural way. Following is an example of an undirected graph class.
<?php
/**
* Undirected graph implementation.
*/
class Graph
{
/**
* Adds an undirected edge between $u and $v in the graph.
*
* $u,$v can be anything.
*
* Edge (u,v) and (v,u) are the same.
*
* $data is the data to be associated with this edge.
* If the edge (u,v) already exists, nothing will happen (the
* new data will not be assigned).
*/
public function add_edge($u,$v,$data=null)
{
assert($this->sanity_check());
assert($u != $v);
if ($this->has_edge($u,$v))
return;
//If u or v don't exist, create them.
if (!$this->has_vertex($u))
$this->add_vertex($u);
if (!$this->has_vertex($v))
$this->add_vertex($v);
//Some sanity.
assert(array_key_exists($u,$this->adjacency_list));
assert(array_key_exists($v,$this->adjacency_list));
//Associate (u,v) with data.
$this->adjacency_list[$u][$v] = $data;
//Associate (v,u) with data.
$this->adjacency_list[$v][$u] = $data;
//We just added two edges
$this->edge_count += 2;
assert($this->has_edge($u,$v));
assert($this->sanity_check());
}
public function has_edge($u,$v)
{
assert($this->sanity_check());
//If u or v do not exist, they surely do not make up an edge.
if (!$this->has_vertex($u))
return false;
if (!$this->has_vertex($v))
return false;
//some extra sanity.
assert(array_key_exists($u,$this->adjacency_list));
assert(array_key_exists($v,$this->adjacency_list));
//This is the return value; if v is a neighbor of u, then its true.
$result = array_key_exists($v,$this->adjacency_list[$u]);
//Make sure that iff v is a neighbor of u, then u is a neighbor of v
assert($result == array_key_exists($u,$this->adjacency_list[$v]));
return $result;
}
/**
* Remove (u,v) and return data.
*/
public function remove_edge($u,$v)
{
assert($this->sanity_check());
if (!$this->has_edge($u,$v))
return null;
assert(array_key_exists($u,$this->adjacency_list));
assert(array_key_exists($v,$this->adjacency_list));
assert(array_key_exists($v,$this->adjacency_list[$u]));
assert(array_key_exists($u,$this->adjacency_list[$v]));
//remember data.
$data = $this->adjacency_list[$u][$v];
unset($this->adjacency_list[$u][$v]);
unset($this->adjacency_list[$v][$u]);
//We just removed two edges.
$this->edge_count -= 2;
assert($this->sanity_check());
return $data;
}
//Return data associated with (u,v)
public function get_edge_data($u,$v)
{
assert($this->sanity_check());
//If no such edge, no data.
if (!$this->has_edge($u,$v))
return null;
//some sanity.
assert(array_key_exists($u,$this->adjacency_list));
assert(array_key_exists($v,$this->adjacency_list[$u]));
return $this->adjacency_list[$u][$v];
}
/**
* Add a vertex. Vertex must not exist, assertion failure otherwise.
*/
public function add_vertex($u,$data=null)
{
assert(!$this->has_vertex($u));
//Associate data.
$this->vertex_data[$u] = $data;
//Create empty neighbor array.
$this->adjacency_list[$u] = array();
assert($this->has_vertex($u));
assert($this->sanity_check());
}
public function has_vertex($u)
{
assert($this->sanity_check());
assert(array_key_exists($u,$this->vertex_data) == array_key_exists($u,$this->adjacency_list));
return array_key_exists($u,$this->vertex_data);
}
//Returns data associated with vertex, null if vertex does not exist.
public function get_vertex_data($u)
{
assert($this->sanity_check());
if (!array_key_exists($u,$this->vertex_data))
return null;
return $this->vertex_data[$u];
}
//Count the neighbors of a vertex.
public function count_vertex_edges($u)
{
assert($this->sanity_check());
if (!$this->has_vertex($u))
return 0;
//some sanity.
assert (array_key_exists($u,$this->adjacency_list));
return count($this->adjacency_list[$u]);
}
/**
* Return an array of neighbor vertices of u.
* If $with_data == true, then it will return an associative array, like so:
* {neighbor => data}.
*/
public function get_edge_vertices($u,$with_data=false)
{
assert($this->sanity_check());
if (!array_key_exists($u,$this->adjacency_list))
return array();
$result = array();
if ($with_data) {
foreach( $this->adjacency_list[$u] as $v=>$data)
{
$result[$v] = $data;
}
} else {
foreach( $this->adjacency_list[$u] as $v=>$data)
{
array_push($result, $v);
}
}
return $result;
}
//Removes a vertex if it exists, and returns its data, null otherwise.
public function remove_vertex($u)
{
assert($this->sanity_check());
//If the vertex does not exist,
if (!$this->has_vertex($u)){
//Sanity.
assert(!array_key_exists($u,$this->vertex_data));
assert(!array_key_exists($u,$this->adjacency_list));
return null;
}
//We need to remove all edges that this vertex belongs to.
foreach ($this->get_edge_vertices($u) as $v)
{
$this->remove_edge($u,$v);
}
//After removing all such edges, u should have no neighbors.
assert($this->count_vertex_edges($u) == 0);
//sanity.
assert(array_key_exists($u,$this->vertex_data));
assert(array_key_exists($u,$this->adjacency_list));
//remember the data.
$data = $this->vertex_data[$u];
//remove the vertex from the data array.
unset($this->vertex_data[$u]);
//remove the vertex from the adjacency list.
unset($this->adjacency_list[$u]);
assert($this->sanity_check());
return $data;
}
public function get_vertex_count()
{
assert($this->sanity_check());
return count($this->vertex_data);
}
public function get_edge_count()
{
assert($this->sanity_check());
//edge_count counts both (u,v) and (v,u)
return $this->edge_count/2;
}
public function get_vertex_list($with_data=false)
{
$result = array();
if ($with_data)
foreach ($this->vertex_data as $u=>$data)
$result[$u]=$data;
else
foreach ($this->vertex_data as $u=>$data)
array_push($result,$u);
return $result;
}
public function edge_list_str_array($ordered=true)
{
$result_strings = array();
foreach($this->vertex_data as $u=>$udata)
{
foreach($this->adjacency_list[$u] as $v=>$uv_data)
{
if (!$ordered || ($u < $v))
array_push($result_strings, '('.$u.','.$v.')');
}
}
return $result_strings;
}
public function sanity_check()
{
if (count($this->vertex_data) != count($this->adjacency_list))
return false;
$edge_count = 0;
foreach ($this->vertex_data as $v=>$data)
{
if (!array_key_exists($v,$this->adjacency_list))
return false;
$edge_count += count($this->adjacency_list[$v]);
}
if ($edge_count != $this->edge_count)
return false;
if (($this->edge_count % 2) != 0)
return false;
return true;
}
/**
* This keeps an array that associates vertices with their neighbors like so:
*
* {<vertex> => {<neighbor> => <edge data>}}
*
* Thus, each $adjacency_list[$u] = array( $v1 => $u_v1_edge_data, $v2 => $u_v2_edge_data ...)
*
* The edge data can be null.
*/
private $adjacency_list = array();
/**
* This associates each vertex with its data.
*
* {<vertex> => <data>}
*
* Thus each $vertex_data[$u] = $u_data
*/
private $vertex_data = array();
/**
* This keeps tracks of the edge count so we can retrieve the count in constant time,
* instead of recounting. In truth this counts both (u,v) and (v,u), so the actual count
* is $edge_count/2.
*/
private $edge_count = 0;
}
$G = new Graph();
for ($i=0; $i<5; ++$i)
{
$G->add_vertex($i);
}
for ($i=5; $i<10; ++$i)
{
$G->add_edge($i,$i-5);
}
print 'V: {'.join(', ',$G->get_vertex_list())."}\n";
print 'E: {'.join(', ',$G->edge_list_str_array())."}\n";
$G->remove_vertex(1);
print 'V: {'.join(', ',$G->get_vertex_list())."}\n";
print 'E: {'.join(', ',$G->edge_list_str_array())."}\n";
$G->remove_vertex(1);
print 'V: {'.join(', ',$G->get_vertex_list())."}\n";
print 'E: {'.join(', ',$G->edge_list_str_array())."}\n";
?>