I have a situation in which there is an ID which I receive in a function. This ID when I pass to an API it provides me a relation list, for example if I pass as an ID A, I get:
From
To
Type
A
B
RelationType1
A
C
RelationType2
C
D
RelationType3
D
A
RelationType4
Now I need to find in a recursive manner the relations for all unique ID I get (A,B,C,D) and for each I need to list down the IDs with types until I cannot find any more relations .
Finally I need to save all this data within the database which is not an issue but it will be a combination of From,To and Type.
This is being attempted in php in which I am working on using a class as the basis to start off, thus implementing DFS to do . Is there a better alternative with efficient calls to the API and faster processing.
Thanks for your help.
simple recursive. something like this
basic
class Relations {
public static function getLinksFromDB(relation_id){
return $db->array(); // return an array of matches based on the passed in $relation_id from the database, using your normal query here.
}
public static function getLinks(relation_id){
$ret = [];
$r_ids = Relations::getLinksFromDB(r_id); // when this returns nothing, you will have reached the end of your links, with an exception, if you have any amount which is self contained like A->B, B->C and C->A, then you will have an infinite loop. this could be solved by passing in a counter and once it reaches the counter of depth, just return.
foreach($r_ids as $r_id){
$ret[] = Relations::getLinks($r_id);
}
return $ret;
}
}
with depth limitor
class Relations {
public static function getLinksFromDB(relation_id){
return $db->array(); // return an array of matches based on the passed in $relation_id from the database, using your normal query here.
}
public static function getLinks(relation_id, $counter){
if($counter <= 0){
return [];
}
$ret = [];
$r_ids = Relations::getLinksFromDB(r_id);
foreach($r_ids as $r_id){
$ret[] = Relations::getLinks($r_id, $counter - 1);
}
return $ret;
}
}
both can be called as such:
$ids = ['A', 'B', 'C'];
$links = [];
foreach($ids as $id){
$links[] = Relations::getLinks($id);
}
or with the depth limit
$ids = ['A', 'B', 'C'];
$links = [];
foreach($ids as $id){
$links[] = Relations::getLinks($id, 20);
}
Related
My team lead some time ago asked this question, which I didn't understood:
Implement function calc()
Please, implement function calc so code below works:
$sum = function($a, $b) { return $a + $b; };
calc(5)(3)(2)($sum); // 10
calc(1)(2)($sum); // 3
calc(2)(3)('pow'); // 8
So, can someone explain me what is it, and maybe some link on this functionality
The following will satisfy your example use:
<?php
function calc($input)
{
static $args = [];
if(is_callable($input)) {
$carry = array_shift($args);
$result = array_reduce($args, $input, $carry);
$args = []; // Clear the arguments.
return $result;
} else {
$args[] = $input; // Add arguments to the stack.
return __FUNCTION__;
}
}
$sum = function($a, $b) {
return $a + $b;
};
echo
calc(5)(3)(2)($sum), "\n",
calc(1)(2)($sum), "\n",
calc(2)(3)('pow'), "\n",
calc(5)(2)(2)('pow');
Output:
10
3
8
625
Explanation:
When calc is called with a single argument (that is not a callable), the input is pushed to an array and the name of the function, here 'calc', is returned.
calc(2)(3) will add 2 to the static array (this will persist between subsequent function calls), and return the function name. So this becomes calc(3), where the previous call has the side effect of storing 2 in $args.
If the argument passed is a callable. array_reduce will pass pairs of arguments from $args left to right to the callable, seeding subsequent calls with the result of each iteration.
array_reduce([1,2,3], 'pow', $initial) is similar to the following:
$result = pow($initial, 1);
$result = pow($result, 2);
$result = pow($result, 3);
However we need to use array_shift to remove the first item from the $args array as a seed for the first iteration of the pow call:
So that becomes array_reduce([2,3], 'pow', 1).
We then clear the argument list, otherwise subsequent calls to calc will use these arguments again, and the result of the array_reduce is returned.
You could have it as a calc function that takes a value and:
if it's a not a function, adds it to a buffer array, then return the function itself,
if it's a function, calls that function subsequently on every value of that buffer.
So if we take calc(6)(3)($sum):
calc(6) call will add 6 to the buffer array then return the calc function,
(3) will therefore pass 3 as a param to that same calc function that was just returned, therefore adding 3 to the buffer and returning the calc
function again,
finally, ($sum) will generate a call to that $sum function, passing it every value from the buffer (therefore, 3 then 6), reducing it into the final result.
Code:
function calc($value_or_function, array $buffer = [])
{
// If the argument is callable (a function), check that the buffer has
// at least one value and call this function subsequently on each value,
// reducing it into a final value
if (is_callable($value_or_function)) {
if (count($buffer) === 0) {
throw new \InvalidArgumentException('Not enough parameters.');
}
return array_reduce(array_slice($buffer, 1), $value_or_function, $buffer[0]);
}
// Otherwise (not a callable arg), add it to the buffer and return the
// function itself so that its result can be chain-called
return static function ($otherValue) use ($buffer, $value_or_function) {
return calc($otherValue, array_merge($buffer, [$value_or_function]));
};
}
// Example usage
$sum = function ($a, $b) { return $a + $b; };
echo calc(5)(4)(3)($sum), PHP_EOL;
echo calc(5)(2)(2)('pow');
Additional notes:
this is a decent exercise but probably a bad idea to have in a real codebase, this is quite unintuitive, the function does too many things, doesn't have strictly typed params, etc.,
those inline comments are a bit much for real code (they're that detailed for explanation purposes).
Demo: https://3v4l.org/AoKJO
I am working with a system where inventory is stored in a single database table. I need help to find the best way to add/remove items from this.
It is stored as follows;
3:1|8:2|5:3|4:4
the first number represents quantity and the second number is the item ID. The Pipe | splits these items.
So 3:1 = quantity 3 of item 1.
I am trying to use PHP to look at this string, find if the item is there. If it is add to or remove from it. OR if the item isn't there then create it.
I know I would have to use an array to achieve this but I'm a little lost at how this would be done, any help with this would be greatly appreciated.
Thanks.
I would make a small class that holds an item -> quantity map (stored as an array) that is created from that string, and builds the string back when requested.
This makes use of:
explode to split the string parts,
implode combined with array_map to join them back.
Code:
class DataStructure
{
private $data = [];
public function __construct(string $data)
{
foreach (explode('|', $data) as $item_quantity) {
list($quantity, $item) = explode(':', $item_quantity);
$this->data[$item] = (int)$quantity;
}
}
public function getItemQuantity(int $item): ?int
{
return $this->data[$item] ?? null;
}
public function setItemQuantity(int $item, int $quantity)
{
$this->data[$item] = $quantity;
}
public function __toString(): string
{
return implode('|', array_map(function ($item) {
return $this->data[$item] . ':' . $item;
}, array_keys($this->data)));
}
}
Note that this doesn't include error handling, for the purpose of this example.
Demo: https://3v4l.org/pqRQh
Demo (PHP 5.6 compatible): https://3v4l.org/tK9Q9
In my migration file, I gave my table pages a enum field with 2 possible values (as seen below). My question is, if it's possible to select these values with Laravels Eloquent?
$table->enum('status', array('draft','published'));
There are several Workarounds that I found, but there must be some "eloquent-native" way to handle this. My expected output would be this (that would be perfect!):
array('draft','published')
Thank you in advance!
Unfortunately, Laravel does not offer a solution for this. You will have to do it by yourself. I did some digging and found this answer
You can use that function and turn it into a method in your model class...
class Page extends Eloquent {
public static function getPossibleStatuses(){
$type = DB::select(DB::raw('SHOW COLUMNS FROM pages WHERE Field = "type"'))[0]->Type;
preg_match('/^enum\((.*)\)$/', $type, $matches);
$values = array();
foreach(explode(',', $matches[1]) as $value){
$values[] = trim($value, "'");
}
return $values;
}
}
And you use it like this
$options = Page::getPossibleStatuses();
If you want you can also make it a bit more universally accessible and generic.
First, create a BaseModel. All models should then extend from this class
class BaseModel extends Eloquent {}
After that, put this function in there
public static function getPossibleEnumValues($name){
$instance = new static; // create an instance of the model to be able to get the table name
$type = DB::select( DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$name.'"') )[0]->Type;
preg_match('/^enum\((.*)\)$/', $type, $matches);
$enum = array();
foreach(explode(',', $matches[1]) as $value){
$v = trim( $value, "'" );
$enum[] = $v;
}
return $enum;
}
You call this one like that
$options = Page::getPossibleEnumValues('status');
Made a small improvement to lukasgeiter's function. The foreach loop in his answer is parsing the string. You can update the regex to do that for you.
/**
* Retrieves the acceptable enum fields for a column
*
* #param string $column Column name
*
* #return array
*/
public static function getPossibleEnumValues ($column) {
// Create an instance of the model to be able to get the table name
$instance = new static;
// Pulls column string from DB
$enumStr = DB::select(DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$column.'"'))[0]->Type;
// Parse string
preg_match_all("/'([^']+)'/", $enumStr, $matches);
// Return matches
return isset($matches[1]) ? $matches[1] : [];
}
This throws an error if the column does not exist. So I added a small check in the code
public static function getPossibleEnumValues ($column) {
// Create an instance of the model to be able to get the table name
$instance = new static;
$arr = DB::select(DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$column.'"'));
if (count($arr) == 0){
return array();
}
// Pulls column string from DB
$enumStr = $arr[0]->Type;
// Parse string
preg_match_all("/'([^']+)'/", $enumStr, $matches);
// Return matches
return isset($matches[1]) ? $matches[1] : [];
}
As of L5.17 Eloquent does not include this functionality, instead you need to fall back to native QL. Here's an example that will work with SQL and in one line - returning an array like you asked.
In the spirit of one liner complexity ;)
I threw this in one of my view composers - it fetches the column from the table, explodes it and assembles the values in an array.
I iterate over that in my views using a foreach.
explode (
"','",
substr (
DB::select(" SHOW COLUMNS
FROM ".(new \Namespace\Model)->getTable()."
LIKE 'colName'"
)[0]->Type,
6,
-2
)
);
I'm having trouble getting the results of a has_many query using php idiorm/paris. Following the example from the paris site the has_many result for posts returns as an object.
That's great, and I can run through the object and access individual methods, but what I want to be able to do is pass the result set as an associative array off to my template engine for display.
Example:
class Post extends Model {
}
class User extends Model {
public function posts() {
return $this->has_many('Post'); // Note we use the model name literally - not a pluralised version
}
}
The api works this way:
// Select a particular user from the database
$user = Model::factory('User')->find_one($user_id);
// Find the posts associated with the user
$posts = $user->posts()->find_many();
I am able to access the posts object and print the result set like this:
// echo each post id
foreach ($posts as $post) {
echo $post->id;
}
What I'd really like to be to do though, is use as_array() to get the entire resultset as an associative array, limited by certain fields in the way as_array works for an individual row, e.g.
$post_list = $posts()->as_array(id,title,post,date);
This, or a call on something like $user->posts()->find_many()->as_array() don't work.
What is the correct way to access this type of result set using paris?
Adding this method to idiorm.php gives me the desired functionality.
public function find_array() {
if (func_num_args() === 0) {
return $this->_run();
}
$args = func_get_args();
$array = array();
foreach ($this->_run() as $r) {
$array[] = array_intersect_key($r, array_flip($args));
}
return $array;
}
Now I can call $post_list = $posts()->find_array(); or $posts()->find_array('id','title'); etc.
find_one returns a Model object, find_many returns an array of Models.
If you want to get the entire result set as an array of associative array, one solution should be to use array_map
function model_as_array($model) {
return $model->as_array();
}
$posts = $user->posts()->find_many();
$my_view->posts = array_map(model_as_array, $posts);
var_dump($my_view->posts);
or in php 5.3+ (not tested)
$aa_posts = array_map(function($model) {return $model->as_array();} , $posts);
As stated in the title, How do you perform an array_unshift() on a arrayObject, array_push() is obtained by doing an arrayObject->append() but what about unshift ?
Edit: What I've forgot to mention is that i also need in this particular case to preserve existing keys.
The API of ArrayObject does not have any function to accomplish this directly. You have several other options:
Manually move each element by 1, and set your new value at index 0 (only if you have a numerically index ArrayObject).
$tmp = NULL;
for ($i = 0; $arrayObject->offsetExists($i + 1); $i++) {
$tmp = $arrayObject->offsetGet($i + 1);
$arrayObject->offsetSet($i + 1, $arrayObject->offsetGet($i));
}
$arrayObject->offsetSet($i, $tmp);
$arrayObject->offsetSet(0, $new_value);
Write a class deriving from ArrayObject and add a function prepend (implementation could be the one below).
Extract an array, call array_unshift() and create a new ArrayObject with the modified array:
$array = $arrayObject->getArrayCopy();
array_unshift($array, $new_value);
$arrayObject->exchangeArray($array);
There is no such functionality in ArrayObject, but you can subclass it to add whatever you need. Try this:
class ExtendedArrayObject extends ArrayObject {
public function prepend($value) {
$array = (array)$this;
array_unshift($array, $value);
$this->exchangeArray($array);
}
}
function prefix($value) {
return $this->exchangeArray(array_merge([$value], $this->getArrayCopy()));
}
#Dmitry, #soulmerge your answers was good regarding the initial question but was missing the requirement in my edit, but they point me in the right direction to achieve what i was expected here's the solution we came with here at work: (work for php>=5.1)
public function unshift($value){
$tmp = $this->getArrayCopy();
$tmp = array($value) + $tmp;
$this->exchangeArray($tmp);
return $this;
}
these exemple is not exactly the same as the final solution we needed as for our particular arrayObject. We use a given key in array values as key for the values (think of using database rowId as index for each value in the collection). let's call this value "key" here's what the actual array struct looks like:
array(
key1 => array(key=>key1,val1=>val1,val2=>val2...),
key2 => array(key=>key2,val1=>val1_2,val2=>val2_2...),
...
);
so our solution looks more something like this:
public function unshift($value){
$tmp = $this->getArrayCopy();
$tmp = array($value['key'],$value) + $tmp;
$this->exchangeArray($tmp);
return $this;
}
thank's for your answers, if you find a way that work in php5.0 too i'm still interested.
this works for me. with array_merge()
$old_arr[]=array(
'val1'=>'sample1',
'val2'=>'1'
);
$new_arr[] = array(
'val1'=>'sample2',
'val2'=>'2'
);
array_merge($new_arr,$old_arr);