I'm writing a recursive function like below:
private function getManager($employee)
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
return;
}
This function receive an employee and find his manage. If find a manage, then push manager id into an array ($this->managers on line 5). Then call this function recursively and pass manager as an employee. If no manager found on line 3, then this function just return (line 8).
So my question is, is their any problem if i'm not return the recursive call at line 6 ($this->getManager($manager);)
Not sure if this is what you think, but it works.
function getManagers($employee)
{
$managers = [];
if (isset($employee->manager)) {
array_push($managers, $employee->manager->id);
array_push($managers, ...getManagers($employee->manager));
}
return $managers;
}
No, there is absolutely no benefit in writing the empty return. The method will halt regardless of the existence of the return.
Please observe the two methods below which show identical, error-less outcomes regardless of the return.
Code: (Demo)
class Management
{
private $managers = [];
function __construct($managerTree)
{
$this->getManager($managerTree);
var_export($this->managers);
echo "\n---\n";
$this->managers = [];
var_export($this->managers);
echo "\n---\n";
$this->getManager2($managerTree);
var_export($this->managers);
}
private function getManager(?object $employee): void
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
return;
}
private function getManager2(?object $employee): void
{
$manager = $employee->manager;
if ($manager) {
array_push($this->managers, $manager->id);
$this->getManager($manager);
}
}
}
new Management(
(object) [
'id' => 3,
'manager' => (object) [
'id' => 2,
'manager' => (object) [
'id' => 1,
'manager' => null
]
]
]
);
Output:
array (
0 => 2,
1 => 1,
)
---
array (
)
---
array (
0 => 2,
1 => 1,
)
Related
I know there is a very simple solution requiring minor adjustment to my code but I'm stuck and I have wasted a lot of time trying to find the solution.
Using Laravel Excel I am able to export successfully except that the row numbers are off.
I was able to deduce that the numbering begins with the total number rows within the collection, but they are supposed to begin at 1.
Any help is greatly appreciated.
protected $table_data;
private $row = 0;
public function __construct(array $table_data)
{
$this->table_data = $table_data;
}
public function model(array $row)
{
++$this->row;
}
public function columnFormats(): array
{
return [
'E' => '0',
];
}
public function map($table_data): array
{
$department = (empty($table_data['department'])) ? 'Cast' : $table_data['department']['name'];
return [
++$this->row,
$department,
$table_data['name'],
$table_data['name_eng'],
$table_data['phone_number'],
$table_data['email'],
];
}
public function startCell(): string
{
return 'A6';
}
public function drawings()
{
$drawing = new Drawing();
$drawing->setName('Logo');
$drawing->setPath(public_path('/images/form_logo.png'));
$drawing->setHeight(90);
$drawing->setCoordinates('A1');
return $drawing;
}
public function headings(): array
{
return [
[
'#',
'Department',
'Position/Role',
'Name',
'Phone',
'Email',
]
];
}
public function styles(Worksheet $sheet)
{
$sheet->getStyle('A6:F6')->getFill()->applyFromArray(['fillType' => 'solid','rotation' => 0, 'color' => ['rgb' => '7BC1FA'],]);
$styleArray = array(
'font' => array(
'bold' => true,
'color' => array('rgb' => 'FFFFFF'),
'size' => 12,
'name' => 'Arial'
));
$sheet->getStyle('A6:F6')->applyFromArray($styleArray)->getAlignment()->setWrapText(true)->setHorizontal('left');
}
public function array(): array
{
return $this->table_data;
}
The problem is probably ++$this->row being executed at least twice as often as you expect. I'm not sure if that's because you have it both in model and map method but it might as well go wrong if it's only in map or you are not using import features and it's in model.
So I'd suggest a different solution:
If you are only exporting Data and specifically using the array approach for your data you could add the row index on the data set and use it in map and so on:
public function __construct(array $table_data)
{
$newTableData = [];
foreach($table_data as $index => $data) {
// add row index
$newTableData[] = array_merge(['row' => $index], $data);
}
$this->table_data = $newTableData;
}
//...
public function map($table_data): array
{
$department = (empty($table_data['department'])) ? 'Cast' : $table_data['department']['name'];
return [
// use row index
$table_data['row'],
$department,
$table_data['name'],
$table_data['name_eng'],
$table_data['phone_number'],
$table_data['email'],
];
}
For some calculation i need to remove all keys in whole collection if he has in any array NULL value.
for example
[
'butter'=>['iron'=>5, 'magnesium'=>3.5],
'salt'=>['iron'=>2, 'magnesium'=>2],
'egg'=>['iron'=>4, 'magnesium'=>NULL]
]
Because one of item is empty i need new array to be like this
[
'butter'=>['iron'=>5],
'salt'=>['iron'=>2],
'egg'=>['iron'=>4]
]
I'm not sure i can accomplish this with Laravel collection, maybe there is better way with pure php.
P.S. Sorry my english is not so good
I think your approach is reasonable but if you want to explore other solution here it is:
public function index()
{
$foods = [
'butter' => ['iron' => 5, 'magnesium' => 3.5, 'calcium' => 3],
'salt' => ['iron' => 2, 'magnesium' => 2, 'calcium' => 6],
'egg' => ['iron' => 4, 'magnesium' => NULL, 'calcium' => 5]
];
$newFoods = $this->removeNullKeys($foods);
echo '<pre>';
print_r($newFoods);die;
}
public function removeNullKeys(array $array): array
{
$innerKeys = array_keys(current($array));
$toRemove = array_reduce($innerKeys, function ($carry, $key) use ($array) {
if (in_array(null, array_column($array, $key), true)) {
$carry[$key] = null;
}
return $carry;
}, []);
return array_map(function ($e) use ($toRemove) {
return array_diff_key($e, $toRemove);
}, $array);
}
the result will be:
Array
(
[butter] => Array
(
[iron] => 5
[calcium] => 3
)
[salt] => Array
(
[iron] => 2
[calcium] => 6
)
[egg] => Array
(
[iron] => 4
[calcium] => 5
)
)
I hope it helps.
OK i make this code and this work like i want, but i think its ugly is there some better way with Laravel collection or in pure php
$foods=[
'butter'=>['iron'=>5, 'magnesium'=>3.5, 'calcium'=>3],
'salt'=>['iron'=>2, 'magnesium'=>2, 'calcium'=>6],
'egg'=>['iron'=>4, 'magnesium'=>NULL, 'calcium'=>5]
];
$nutrientsWithNull=[];
foreach($foods as $food)
{
foreach($food as $key=>$value){
if(is_null($value)&&!in_array($key, $nutrientsWithNull))
{
$nutrientsWithNull[]=$key;
}
}
}
foreach($foods as $key=>$food)
{
foreach($nutrientsWithNull as $withNull ) {
unset($foods[$key][$withNull]);
}
}
print_r($foods);
and result is
$foods=[
'butter'=>['iron'=>5, 'calcium'=>3],
'salt'=>['iron'=>2, 'calcium'=>6],
'egg'=>['iron'=>4, 'calcium'=>5]
];
Currently I'm working on a simple OOP script in PHP, it needs to compare the ID and the DATE of the array and sort them in the right order.
I was wondering why my constructor in the first class doesn't pass the $elements array properly.
The error I'm getting:
Notice: Undefined variable: elements in /Applications/XAMPP/xamppfiles/htdocs/strategy-pattern-.php on line 58
Catchable fatal error: Argument 1 passed to ObjectCollection::__construct() must be of the type array, null given, called in ... on line 58 and defined in ... on line 12
Code:
<?php
class ObjectCollection
{
var $elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
var $comparator;
function __construct(array $elements)
{
$this->elements = $elements;
}
function sort()
{
if (!$this->comparator) {
throw new \LogicException('Comparator is not set');
}
uasort($this->elements, [$this->comparator, 'compare']);
return $this->elements;
}
function setComparator(ComparatorInterface $comparator)
{
$this->comparator = $comparator;
}
}
interface ComparatorInterface
{
function compare($a, $b);
}
class DateComparator implements ComparatorInterface
{
function compare($a, $b)
{
$aDate = new \DateTime($a['date']);
$bDate = new \DateTime($b['date']);
return $aDate <> $bDate;
}
}
class IdComparator implements ComparatorInterface
{
function compare($a, $b)
{
return $a['id'] <> $b['id'];
}
}
$collection = new ObjectCollection($elements);
$collection->setComparator(new IdComparator());
$collection->sort();
echo "Sorted by ID:\n <br>";
print_r($collection->elements);
$collection->setComparator(new DateComparator());
$collection->sort();
echo "<br>Sorted by date:\n <br>";
print_r($collection->elements);
?>
I know there may just be a rookie mistake somewhere but I'm really curious what I'm doing wrong haha.
Thanks in advance! :)
At the bottom of your script you have:
$collection = new ObjectCollection($elements);
However, the $elements variable is not defined. This is why you are getting the error.
The specific error is related to the fact that you used a type declaration in your class constructor requiring that an 'array' be passed. Prior to the addition of type declarations to php, the php runtime engine did not care what variables you passed, so long as you passed a number of variables equal to the number of required parameters to a function or method.
As also pointed out in another answer, many of us are assuming that your placement of the --
var $elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
was never meant to be inside the class. With that said, doing so creates and initializes the $elements class variable, which is a valid technique that has many uses in OOP. However, the syntax used is obsolete, and if you really did want to initialize a class variable to a set value at object creation time, you should be using the syntax that includes a variable visibility keyword like:
protected $elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
In conclusion, the answer to your question is that either you should define $collection to be an array at the bottom of the script, or pass an array in when you create the ObjectCollection object.
$collection = new ObjectCollection(array(
array('id' => 2, 'date' => '2017-01-01'),
array('id' => 1, 'date' => '2017-02-01'));
class ObjectCollection
{
// define as property
private $elements;
private $comparator;
function __construct(array $elements)
{
$this->elements = $elements;
}
function sort()
{
if (!$this->comparator) {
throw new \LogicException('Comparator is not set');
}
uasort($this->elements, [$this->comparator, 'compare']);
return $this->elements;
}
function setComparator(ComparatorInterface $comparator)
{
$this->comparator = $comparator;
}
}
...
// you need to define $elements to pass
$elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
// them to the constructor
$collection = new ObjectCollection($elements);
// the way you did it, your $elements definition was in class scope so you got the error they are "NULL" / Not defined
You have declared the elements variable inside the class instead outside
$elements = array(
array('id' => 2, 'date' => '2017-01-01',),
array('id' => 1, 'date' => '2017-02-01'));
class ObjectCollection
{
I have an array, that looks like this,
$users = array(
0 => array(
'user_id' => 'user_1',
'likes' => 50,
),
1 => array(
'user_id' => 'user_2',
'likes' => 72
),
2 => array(
'user_id' => 'user_3',
'likes' => 678
)
);
All I want to do is to implement ratings system according to amount of likes, so that it would look like as:
#rank 1 - user_3
#rank 2 - user_2
#rank 3 - user_1
I ended up with,
$user_counter = new User_Counter();
$user_counter->setData($users);
echo $user_counter->fetchRankByLikes(678);
class User_Counter
{
protected $array;
public function setData(array $array)
{
$this->array = $this->sort($array);
}
protected function sort(array $array)
{
$result = array();
foreach ($array as $index => $_array) {
$result[] = $_array['likes'];
}
// This will reset indexes
sort($result, SORT_NUMERIC);
$result = array_reverse($result);
$return = array();
$count = 0;
foreach ($result as $index => $rank) {
$count++;
$return[$count] = $rank;
}
$return = array_unique($return);
return $return;
}
public function getAll()
{
return $this->array;
}
public function fetchRankByLikes($likes)
{
$data = array_flip($this->array);
return $data[$likes];
}
public function fetchLikesByRank($rank)
{
return $this->array[$rank];
}
}
My problem is that, this approach lies sometimes - for example it gives incorrect info when there are no likes at all ( === i.e all members have 0 likes) - in that case it gives first rank to them all.
Is there another efficient approach to count user's rating by amount of their likes?
Or what am I doing wrong in my computations?
Thanks.
If you're looking for efficiency, I would look at the usort() function, native to PHP:
http://us1.php.net/manual/en/function.usort.php
What usort does is take an array and iteratively walk through it, providing an outside function with 2 items from it's input at a time. It then waits for the function to return either 1, -1 or 0 and determines the following truths:
-1 means left < right
+1 means left > right
0 means both parameters are equal
Here's a practical example:
$users = array(
0 => array(
'user_id' => 'user_1',
'likes' => 50,
),
1 => array(
'user_id' => 'user_2',
'likes' => 72
),
2 => array(
'user_id' => 'user_3',
'likes' => 678
)
);
usort($users, "sortLikesAscending");
function sortLikesAscending($a, $b) {
if ($a['likes'] > $b['likes']) {
return 1;
} elseif ($a['likes'] < $b['likes']) {
return -1;
} else {
return 0;
}
}
Hope that helps!
EDIT:
If you want to implement this usort() methodology from inside your User_Counter class, then call usort like this:
// From somewhere inside User_Counter, assumes User_Counter::mySortingFunction() is defined
usort($this->array, array(&$this, "mySortingFunction"));
The function callback is passed as an array with two entries: a &reference to an object containing the function and the function name as a string.
Ok, I'm really stucked with this. I hope you can help me.
I have my class, used to manage hierarchical data. The input is a plain array with the following structure (just an example):
$list = array(
(object) array('id' => 1, 'nombre' => 'Cámaras de fotos', 'parentId' => null),
(object) array('id' => 2, 'nombre' => 'Lentes', 'parentId' => null),
(object) array('id' => 3, 'nombre' => 'Zoom', 'parentId' => 2),
(object) array('id' => 4, 'nombre' => 'SLR', 'parentId' => 1),
(object) array('id' => 5, 'nombre' => 'Primarios', 'parentId' => 2),
(object) array('id' => 6, 'nombre' => 'Sensor APS-C', 'parentId' => 4),
(object) array('id' => 7, 'nombre' => 'Full-frame', 'parentId' => 4),
(object) array('id' => 8, 'nombre' => 'Flashes', 'parentId' => null),
(object) array('id' => 9, 'nombre' => 'Compactas', 'parentId' => 1)
);
I input the data to the class this way:
$Hierarchical = new Hierarchical;
$Hierarchical->plain = $list;
Then I have a public function (createTree) to create a multidimensional array representation of the list. It works perfectly. It can return the result or store it inside $this->tree.
As you can see, this is very simple. It calls private function iterateTree, which is the recursive function.
class Hierarchical {
public $plain = array();
public $tree = array();
public function createTree($parentId=0, $return=false) {
$tree = $this->iterateTree($parentId);
if(!$return) {
$this->tree = $tree;
} else {
return $tree;
}
}
private function iterateTree($parentId) {
$resArray = array();
foreach($this->plain as $item) {
if($item->parentId == $parentId) {
$children = $this->iterateTree($item->id);
if( count($children) > 0 ) {
$item->children = $children;
}
$resArray[] = $item;
}
}
return $resArray;
}
}
So far so good. It works fine.
BUT... The problem appears when I want to use $this->plain after calling createTree(). Instead of returning the original dataset, it returns some kind of mix between the original input, with all their children appended (similar to $this->tree).
I can't figure out why the content of $this->plain is being changed, neither in the both functions used I'm changing it's content.
I've tried unseting the variables inside the foreach, after the foreach, even passing the original array as an argument and not using $this->plain at all inside the recursive function. Nothing worked.
I'm also not using any other function inside the class that could change it's value.
It's a total mistery!
In your foreach loop $item will be a reference to the object in the array, so you are changing that same object in the line
$item->children = $children;
This will affect the object referred to in the original arrays $list and $this->plain.
One solution may be to clone $item within your foreach loop.
According to Doug's answer, the correct function is: (added $itemAux = clone $item)
private function iterateTree($parentId) {
$resArray = array();
foreach($this->plain as $item) {
$itemAux = clone $item;
if($itemAux->parentId == $parentId) {
$children = $this->iterateTree($itemAux->id);
if( count($children) > 0 ) {
$itemAux->children = $children;
}
$resArray[] = $itemAux;
}
}
return $resArray;
}
To add to Doug's answer, although the manual says that "objects are not passed by reference" (http://www.php.net/manual/en/language.oop5.references.php), it may instead help to think of objects as a completely separate entity from any variables that may "contain" them, and that they are actually passed everywhere by reference...
class testClass
{
public $var1 = 1;
}
function testFunc($obj)
{
$obj->var1 = 2;
}
$t = new testClass;
testFunc($t);
echo $t->var1; // 2
So when you do $item->children = $children;, you are in fact affecting each original object in that $plain array.