I have a requirement to allow my end users to input formula much like a spreadsheet. I have an array like this:
$table = array(
1=>array(
"id"=>1,
"Name"=>"Regulating",
"Quantity"=>"[2]Quantity+[3]Value",
"Value"=>"[2]Cost"
),
...)
The first level array key is always the same value as the id key in that array.
A tabulated example follows:
id Name Quantity Value
1 Regulating [2]Quantity+[3]Value [2]Cost
2 Kerbs 3 6
3 Bricks 9 7
4 Sausages [3]Cost 3
5 Bamboo [4]Quantity [7]Cost
6 Clams [4]Quantity NULL
7 Hardcore [3]Quantity*0.5 12
8 Beetles [6]Quantity*[4]Value [2]Value
The Quantity and Value keys represent formula which reference the [id] and either Quantity, Value or Cost.
Cost is derived by multiplying the Value and Quantity.
I am using:
preg_match_all("/\[(.*?)\]([A-Z]*[a-z]*)/", $string, $matches, PREG_SET_ORDER);
which outputs an array like so for[1][Quantity]:
Array
(
[0] => Array
(
[0] => [2]Quantity
[1] => 2
[2] => Quantity
)
[1] => Array
(
[0] => [3]Value
[1] => 3
[2] => Value
)
)
Iterating through the table using something similar to:
$calcString = $table[1]['Quantity'];`
foreach ($matches as $match) {
$calcString = str_replace($match[0], $table[$match[1]][$match[2]], $calcString);
}
I can get the string to be calculated and am using a matheval class to do the sum.
For example
[1]Quantity = [2]Quantity + [3]Value
[2]Quantity = 3
[3]Value = 7 // [1]Quantity = 3 + 7 = 10
[1]Value = [2]Cost
[2]Cost = [2]Quantity * [2]Value // 3 * 6 = 18
Basically the variables in the table refer to other [id]key in the same table.
But here is my issue
I need to resolve references to other parts of the table (which may or may not themselves be formula) to fill in the blanks. This is outside my comfort zone and I would appreciate any advice (or even better functional code) which provides enlightenment on how I might be able to achieve this.
Thanks
Deep down, you already know how to solve this, you're just intimidated by the task.
A recursive approach would be to expand references instantly. For example,
expand('[1]Value') # returns '[2]Cost'
expand('[2]Cost') # returns '[2]Quantity * [2]Value'
expand('[2]Quantity') # returns 3
expand('[2]Value') # returns 6
eval('3 * 6')
# returns 18
# returns 18
# returns 18
An iterative (non-recursive) approach is to expand one reference at a time and repeat until there are unresolved references in the string.
expand('[1]Value') // returns '[2]Cost'
expand('[2]Cost') // returns '[2]Quantity + [2]Value'
expand('[2]Quantity + [2]Value') // returns 3 for [2]Quantity
expand('3 * [2]Value') // returns 6 for [2]Value
eval('3 * 6')
# returns 18
Normally, I prefer iterative solutions, because they're much less prone to stack overflows. However, recursive solutions are usually easier to write.
Here's a quickly slapped-together recursive evaluator: https://gist.github.com/stulentsev/b270bce4be67bc1a96ae (written in ruby, though)
If calcString's are reasonably sized and you don't expect replacements to get too elaborate, you could use a while loop to simulate the recursion. Here's an example that outputs the string along the way as it is being modified:
$calcString = $table[8]['Quantity'];
preg_match_all("/\[(.*?)\]([A-Z]*[a-z]*)/", $calcString, $matches, PREG_SET_ORDER);
print_r($calcString . "\n");
while (!empty($matches)){
foreach ($matches as $match) {
preg_match_all("/\[(.*?)\](Cost)/", $match[0], $matchCost, PREG_SET_ORDER);
if (!empty($matchCost)){
$cost = $table[$matchCost[0][1]]['Quantity'] . "*" . $table[$matchCost[0][1]]['Value'];
$calcString = str_replace($match[0], $cost, $calcString);
} else {
$calcString = str_replace($match[0], $table[$match[1]][$match[2]], $calcString);
}
print_r($calcString . "\n");
}
preg_match_all("/\[(.*?)\]([A-Z]*[a-z]*)/", $calcString, $matches, PREG_SET_ORDER);
}
Output:
[6]Quantity*[4]Value
[4]Quantity*[4]Value
[4]Quantity*3
[3]Cost*3
9*7*3
The table variable:
$table = array(
1 => array(
"id" => 1,
"Name" => "Regulating",
"Quantity" => "[2]Quantity+[3]Value",
"Value" => "[2]Cost"
),
2 => array(
"id" => 2,
"Name" => "Kerbs",
"Quantity" => 3,
"Value" => 6
),
3 => array(
"id" => 3,
"Name"=>"Bricks",
"Quantity"=> 9,
"Value"=> 7
),
4 => array(
"id" => 2,
"Name" => "Sausages",
"Quantity" => "[3]Cost",
"Value" => 3
),
5 => array(
"id" => 2,
"Name" => "Bamboo",
"Quantity" => "[4]Quantity",
"Value" => "[7]Cost"
),
6 => array(
"id" => 2,
"Name" => "Clams",
"Quantity" => "[4]Quantity",
"Value" => NULL
),
7 => array(
"id" => 2,
"Name" => "Hardcore",
"Quantity" => "[3]Quantity*0.5",
"Value" => 12
),
8 => array(
"id" => 2,
"Name" => "Beetles",
"Quantity" => "[6]Quantity*[4]Value",
"Value" => "[2]Value"
)
);
A dangerously easy, and your-situation-specific well-performable solution!
<?php
class solver {
private
// The final output array
$arr_evaled,
// When a cell gains its final value, the corresponding entry in the following array gets marked as being done!
$arr_done;
private $solving_iterations_count;
public function solver($array) {
$this->arr_done = array();
foreach($array as $k => $arr)
$this->arr_done[$k] = array('Quantity' => false, 'Value' => false);
// Firstly,expand all of the "[x]Cost"s to "([x]Quantity*[x]Value)"s!
$this->arr_evaled = array_map(
function($v){ return preg_replace('#\[(\d*?)\]Cost#', '([$1]Quantity*[$1]Value)', $v); },
$array
);
$this->solving_iterations_count = 0;
$this->solve();
}
private function isDone() {
foreach($this->arr_done as $a)
if($a['Quantity'] == false || $a['Value'] == false)
return false;
return true;
}
private function isCellDone($id, $fieldName) {
return $this->arr_done[$id][$fieldName];
}
private function markCellAsDone($id, $fieldName, $evaluation) {
$this->arr_done[$id][$fieldName] = true;
$this->arr_evaled[$id][$fieldName] = $evaluation;
}
private function isEvaluable($str) {
return preg_match('#^[0-9*+-\/\(\)\.]*$#', $str) == 1 || strtolower($str)=='null';
}
private function replace($from, $to) {
foreach($this->arr_evaled as &$arr) {
$arr['Quantity'] = str_replace($from, $to, $arr['Quantity']);
$arr['Value'] = str_replace($from, $to, $arr['Value']);
}
}
private function solve() {
$isSolvable = true; // YOUR TODO: I believe coding this part is also fun!) (e.g: check for "reference cycles")
if(!$isSolvable) return null;
while( !$this->isDone() )
{
foreach($this->arr_evaled as $arr) {
foreach(['Quantity', 'Value'] as $fieldName) {
if(!$this->isCellDone($arr['id'], $fieldName)) {
if($this->isEvaluable($arr[$fieldName])) {
$evaluation = eval("return {$arr[$fieldName]};");
$this->markCellAsDone($arr['id'], $fieldName, $evaluation);
$this->replace("[{$arr['id']}]$fieldName", "$evaluation");
}
}
}
}
$this->solving_iterations_count++;
}
foreach($this->arr_evaled as &$row)
$row['Cost'] = $row['Quantity'] * $row['Value'];
return $this->arr_evaled;
}
public function print_tabulated() {
echo "The count of solving iterations: {$this->solving_iterations_count}<br/><br/>";
echo '<table border="1"><tr><th>id</th><th>Name</th><th>Quantity</th><th>Value</th><th>Cost</th></tr>';
foreach($this->arr_evaled as $arr)
echo "<tr><td>{$arr['id']}</td><td>{$arr['Name']}</td><td>{$arr['Quantity']}</td><td>{$arr['Value']}</td><td>{$arr['Cost']}</td></tr>";
echo '</table>';
}
}
// Testing
$arr = array(
1 => array( 'id' => 1, 'Name' => 'Regulating', 'Quantity' => '[2]Quantity+[3]Value', 'Value' => '[2]Cost' ),
2 => array( 'id' => 2, 'Name' => 'Kerbs', 'Quantity' => '3', 'Value' => '6' ),
3 => array( 'id' => 3, 'Name' => 'Bricks', 'Quantity' => '9', 'Value' => '7' ),
4 => array( 'id' => 4, 'Name' => 'Sausages', 'Quantity' => '[3]Cost', 'Value' => '3' ),
5 => array( 'id' => 5, 'Name' => 'Bamboo', 'Quantity' => '[4]Quantity', 'Value' => '[7]Cost' ),
6 => array( 'id' => 6, 'Name' => 'Clams', 'Quantity' => '[4]Quantity', 'Value' => 'NULL' ),
7 => array( 'id' => 7, 'Name' => 'Hardcore', 'Quantity' => '[3]Quantity*0.5', 'Value' => '12' ),
8 => array( 'id' => 8, 'Name' => 'Beetles', 'Quantity' => '[6]Quantity*[4]Value', 'Value' => '[2]Value' ),
);
echo '<pre>';
(new solver($arr))->print_tabulated();
Here is the output:
Related
I have this array :
(
[id] => block_5df755210d30a
[name] => acf/floorplans
[data] => Array
(
[floorplans_0_valid_for_export] => 0
[floorplans_0_title] => title 1
[floorplans_0_house_area] => 40m²
[floorplans_0_bedrooms] => 1
[floorplans_1_valid_for_export] => 1
[floorplans_1_title] => title xx
[floorplans_1_house_area] => 90m²
[floorplans_1_bedrooms] => 2
[floorplans_2_valid_for_export] => 1
[floorplans_2_title] => title 2
[floorplans_2_house_area] => 50m²
[floorplans_2_bedrooms] => 1
[floorplans] => 3
)
)
As we can see in the data, we have fields (floorplans_X_valid_for_export).
What I want to do is to get the data only when this field equal to 1.
So from the given example, I want to keep only these fields:
[floorplans_1_valid_for_export] => 1
[floorplans_1_title] => title xx
[floorplans_1_house_area] => 90m²
[floorplans_1_bedrooms] => 2
[floorplans_2_valid_for_export] => 1
[floorplans_2_title] => title 2
[floorplans_2_house_area] => 50m²
[floorplans_2_bedrooms] => 1
This is an odd schema, but it can be done by iterating through the array and searching for keys where "valid_for_export" equals 1, and then using another array of field "stubs" to get the associated items by a unique identifier of X in floorplans_X_valid_for_export
$array = [
'floorplans_0_valid_for_export' => 0,
'floorplans_0_title' => 'title 1',
'floorplans_0_house_area' => '40m²',
'floorplans_0_bedrooms' => 1,
'floorplans_1_valid_for_export' => 1,
'floorplans_1_title' => 'title xx',
'floorplans_1_house_area' => '90m²',
'floorplans_1_bedrooms' => '2',
'floorplans_2_valid_for_export' => 1,
'floorplans_2_title' => 'title 2',
'floorplans_2_house_area' => '50m²',
'floorplans_2_bedrooms' => 1,
'floorplans' => 3
];
$stubs = [
'floorplans_%s_valid_for_export',
'floorplans_%s_title',
'floorplans_%s_house_area',
'floorplans_%s_bedrooms'
];
$newArr = [];
foreach ($array as $key => $value) {
if (strpos($key, 'valid_for_export') && $array[$key] == 1) {
$intVal = filter_var($key, FILTER_SANITIZE_NUMBER_INT);
foreach ($stubs as $stub) {
$search = sprintf($stub, $intVal);
if (isset($array[$search])) {
$newArr[$search] = $array[$search];
} else {
// key can't be found, generate one with null
$newArr[$search] = null;
}
}
}
}
echo '<pre>';
print_r($newArr);
Working: http://sandbox.onlinephpfunctions.com/code/23a225e3cefa2dc9cc97f53f1cbae0ea291672c0
Use a parent loop to check that the number-specific valid_for_export value is non-empty -- since it is either 0 or non-zero.
If so, then just push all of the associated elements into the result array.
Some reasons that this answer is superior to the #Alex's answer are:
Alex's parent loop makes 13 iterations (and the same number of strpos() calls); mine makes just 3 (and only 3 calls of empty()).
$array[$key] is more simply written as $value.
Sanitizing the $key to extract the index/counter is more overhead than necessary as demonstrated in my answer.
Code (Demo)
$array = [
'floorplans_0_valid_for_export' => 0,
'floorplans_0_title' => 'title 1',
'floorplans_0_house_area' => '40m²',
'floorplans_0_bedrooms' => 1,
'floorplans_1_valid_for_export' => 1,
'floorplans_1_title' => 'title xx',
'floorplans_1_house_area' => '90m²',
'floorplans_1_bedrooms' => '2',
'floorplans_2_valid_for_export' => 1,
'floorplans_2_title' => 'title 2',
'floorplans_2_house_area' => '50m²',
'floorplans_2_bedrooms' => 1,
'floorplans' => 3
];
$labels = ['valid_for_export', 'title', 'house_area', 'bedrooms'];
$result = [];
for ($i = 0; $i < $array['floorplans']; ++$i) {
if (!empty($array['floorplans_' . $i . '_valid_for_export'])) {
foreach ($labels as $label) {
$key = sprintf('floorplans_%s_%s', $i, $label);
$result[$key] = $array[$key];
}
}
}
var_export($result);
Output:
array (
'floorplans_1_valid_for_export' => 1,
'floorplans_1_title' => 'title xx',
'floorplans_1_house_area' => '90m²',
'floorplans_1_bedrooms' => '2',
'floorplans_2_valid_for_export' => 1,
'floorplans_2_title' => 'title 2',
'floorplans_2_house_area' => '50m²',
'floorplans_2_bedrooms' => 1,
)
With that constructed data it might be hard (not impossble tho), hovewer i would suggest to change it to multidimensional arrays so you have something like:
[floorplans][0][valid_for_export] => 0
[floorplans][0][title] => title 1
[floorplans][0][house_area] => 40m²
[floorplans][0][bedrooms] => 1
[floorplans][1][valid_for_export] => 1
[floorplans][1][title] => title xx
[floorplans][1][house_area] => 90m²
Rought sollution
It is not the best approach, but it should work if you dont need anything fancy, and know that structure of data wont change in future
$keys = [];
$for($i=0;$i<$array['floorplans'];++$i) {
if(isset($array['floorplans_'.$i.'_valid_for_export']) && $array['floorplans_'.$i.'_valid_for_export']===1) {
$keys[] = $i;
}
}
print_r($keys);
What I'm trying to accomplish is to write a html string in a controller with array values being looped in it. So for example;
$content = "Your store, at location A, has these items added to them". add array loop here. "Do take note!";
My array would be as such
array (
0 =>
array (
'id' => '5db29b6d31c391731239bbdf',
'name' => 'Diamond bracelet (sample)',
'tags' =>
array (
0 => 'female',
1 => 'jewelry',
),
'category' => 'Accessories',
'sku' => '1029EHW',
'priceType' => 'Fixed',
'unitPrice' => 190,
'cost' => 90,
'trackStockLevel' => true,
'isParentProduct' => false,
),
1 =>
array (
'id' => '5db29b6d31c391731239bbdb',
'name' => 'Long-sleeved shirt(sample)(M)',
'tags' =>
array (
0 => 'tops',
1 => 'cotton',
),
'category' => 'Women\'s Apparel',
'sku' => 'ABC1234-M',
'priceType' => 'Fixed',
'unitPrice' => 47.170000000000002,
'cost' => 20,
'trackStockLevel' => true,
'isParentProduct' => false,
'parentProductId' => '5db29b6d31c391731239bbd4',
'variationValues' =>
array (
0 =>
array (
'variantGroupId' => '5db29b6d31c391731239bbd5',
'value' => 'M',
),
),
),
)
note that the array can have many instances of product_name and sku, or can have none.
How do i populate this in my string to be like;
$content = "Your store, at location A, has these items added to them, 1) asd, 2)def, 3)asf . Do take note!
Try this, I've used \sprintf for ease, check if the output serves your purpose.
<?php
function stringMethod(): string
{
$count = 0;
$arrayString = [];
$array = [['product_name' => 'abc', 'product_sku' => 'def'],['product_name' => 'abc', 'product_sku' => 'asd']];
foreach ($array as $value){
$count++;
$arrayString[] = sprintf('%s)%s', $count, $value['product_sku']);
}
$string = \implode(',', $arrayString);
return \sprintf("Your store, at location A, has these items added to them %s Do take note!", $string);
}
echo stringMethod();
Hope this will help you. try to do it in less number of lines
$content = "Your store, at location A, has these items added to them, ";
$productArray = array (0 => array ('id' => '5db29b6d31c391731239bbdf','name' => 'Diamond bracelet (sample)','tags' => array (0 => 'female',1 => 'jewelry',),'category' => 'Accessories','sku' => '1029EHW','priceType' => 'Fixed','unitPrice' => 190,'cost' => 90,'trackStockLevel' => true,'isParentProduct' => false,),1 => array ('id' => '5db29b6d31c391731239bbdb','name' => 'Long-sleeved shirt(sample)(M)','tags' => array (0 => 'tops',1 => 'cotton',),'category' => 'Women\'s Apparel','sku' => 'ABC1234-M','priceType' => 'Fixed','unitPrice' => 47.170000000000002,'cost' => 20,'trackStockLevel' => true,'isParentProduct' => false,'parentProductId' => '5db29b6d31c391731239bbd4','variationValues' => array (0 => array ('variantGroupId' => '5db29b6d31c391731239bbd5','value' => 'M'))));
foreach ($productArray as $key => $product) {
$content .= ($key+1).') '.$product['name'];
if (count($productArray)-1!=$key) {
$content .= ', ';
}
}
$content .= ". Do take note!";
$content = "Your store, at location A, has these items added to them,". $this->getItemList($collection) .". Do take note!"
# Somewhere else in the controller
protected function getItemList(Collection $collection): string
{
return $collection->pluck('name')
->merge($collection->pluck('sku'))
->map(function($item, $key) {
return ($key + 1) . ') ' . $item;
})
->implode(', ');
}
An easy solution would be
$content = "Your store, at location A, has these items added to them";
$array = array(0=>["product_name" => "abc", "product_sku" => "def"], 1=>['product_name' => 'kdkf', 'product_sku'=> 'ljbkj']);
for($i = 0; $i<count($array); $i++){
$content .= ($i+1).") ".$array[$i]['product_name].' '.$array[$i]['product_sku'].', ';
}
$content .= "Do take note.";
This way you're just constantly concatonating the string value in separate parts instead of trying to inject it in the middle of the parent string.
not tested, may be syntax errors
I have function in class, which returns array with 4 3-4 rows. I have to use each value from related row in table values. I can print builded function atrray, and can print on parent file, but can not get values for elements for each row. Here is my class file:
<?php
class Anyclass
{
public function $monthly($array)
{
.
.
.
}
public function myfunction($array)
{
foreach ($ShowMonths as $duration) {
$array['Credit']['downpayment'] = 0;
$array['Credit']['duration'] = $duration;
$monthly = $this->monthly($array);
$Table['Options'][] = array(
'downpayment' => $array['Credit']['downpayment'],
'duration' => $array['Credit']['duration'],
'monthly' => $monthly,
'total' => $monthly * $array['Credit']['duration'] + $array['Credit']['downpayment']
);
}
print_r($Table);
}
}
Here is my main file
include_once('mypathtofile/FileWithClass.php');
$Cll = new NewOne();
$array = array(
'Rule' => array(
'rule_id' => 1,
'rule_name' => 'Mobile phones',
'interest' => 0.01,
'comission' => 0.01,
'best_offer_downpayment' => 0.5,
'best_offer_duration' => 6
),
'Product' => array(
'product_id' => 1,
'category_id' => 1,
'manufacturer_id' => 1,
'product_price' => 500
),
'Credit' => array(
'downpayment' => 100,
'duration' => 6
)
);
$prtst = $Cll->myfunction($array);
How can I call each elemet for each row?
I'm not sure what you are trying to do, but if you need to get each value in a multidimensional array - you can use recursion:
function get_value($array){
foreach($array as $item){
if(is_array($item)){
get_value($item);
} else {
echo $item;
}
}
}
I have an Multidimensional array that takes a similar form to this array bellow.
$shop = array( array( Title => "rose",
Price => 1.25,
Number => 15
),
array( Title => "daisy",
Price => 0.75,
Number => 25,
),
array( Title => "orchid",
Price => 1.15,
Number => 7
)
);
I would like to see if a value I'm looking for is in the array, and if so, return the position of the element in the array.
Here's a function off the PHP Manual and in the comment section.. Works like a charm.
<?php
function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value OR (is_array($value) && recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
Found this function in the PHP docs: http://www.php.net/array_search
A more naive approach than the one showed by Zander, you can hold a reference to the outer key and inner key in a foreach loop and store them.
$outer = "";
$inner = "";
foreach($shop as $outer_key => $inner_array){
foreach($inner_array as $inner_key => $value) {
if($value == "rose") {
$outer = $outer_key;
$inner = $inner_key;
break 2;
}
}
}
if(!empty($outer)) echo $shop[$outer][$inner];
else echo "value not found";
You can use array_map with in_array and return the keys you want
$search = 1.25;
print_r(
array_filter(array_map(function($a){
if (in_array($search, $a)){
return $a;
}
}, $shop))
);
Will print:
Array
(
[0] => Array
(
[Title] => rose
[Price] => 1.25
[Number] => 15
)
)
php >= 5.5
$shop = array( array( 'Title' => "rose",
'Price' => 1.25,
'Number' => 15
),
array( 'Title' => "daisy",
'Price' => 0.75,
'Number' => 25,
),
array( 'Title' => "orchid",
'Price' => 1.15,
'Number' => 7
)
);
$titles = array_column($shop,'Title');
if(!empty($titles['rose']) && $titles['rose'] == 'YOUR_SEARCH_VALUE'){
//do the stuff
}
This question already has an answer here:
Retrieve all parent keys of a given child key in Array
(1 answer)
Closed 8 years ago.
I have array which looks like this:
$array = array(
53 => array(
'name' => 'category',
'subcats' => array(
42 => array(
'name' => 'subcategory',
),
152 => array(
'name' => 'subcategory',
'subcats' => array(
431 => array(
'name' => 'subsubcategory'
)
)
)
)
),
94 => array(
'name' => 'category'
),
12 => array(
'name' => 'category',
'subcats' => array(
67 => array(
'name' => 'subcategory',
),
237 => array(
'name' => 'subcategory',
),
109 => array(
'name' => 'subcategory',
),
32 => array(
'name' => 'subcategory',
)
)
);
As you can see keys are numbers, but those are IDs of categories which can be infinitely nested into each other. What I need is to search this array for specific ID and return array of it and its parents. We can safely assume that keys would not duplicate even in subcategories array - they are IDs from MySQL AI field;
So the expected result for searching key 431 would be:
array(53, 152, 431);
Any help would be appreciated, I just can't wrap my head around this ;)
This works -
function find_id_list($array, $key){
if(in_array($key,array_keys($array))){
return Array($key);
}
foreach($array as $k=>$v){
if(in_array("subcats",array_keys($v))){
$result = find_id_list($v["subcats"], $key);
if ($result !== null){
array_unshift($result,$k);
return $result;
}
}
}
return null;
}
OUTPUT:
$val = find_id_list($array, 431);
var_dump($val);
/* OUTPUT
array
0 => int 53
1 => int 152
2 => int 431
*/
$val = find_id_list($array, 109);
var_dump($val);
/* OUTPUT
array
0 => int 12
1 => int 109
*/
$val = find_id_list($array, 0);
var_dump($val);
/* OUTPUT
null
*/
You can use a recursive function to solve this issue.
function searchmulti($arr, $num)
{
if (!isset($arr[$num])) {
foreach ($arr as $key => $subarr)
{
if (isset($subarr['subcats']))
{
$ret = searchmulti($subarr['subcats'], $num);
if ($ret)
{
$ret[] = $key;
return $ret;
}
}
}
} else return array($num);
return null;
}
$solution = array_reverse(searchmulti($array,431));
Output
$solution => array(53,152,431);