i'm trying to figure out how to sum certain values of a multi-dimensional array if they have similar dates.
Here's my array:
<?$myArray=array(
array(
'year' => 2011,
'month ' => 5,
'day' => 13,
'value' => 2
),
array(
'year' => 2011,
'month '=> 5,
'day' => 14,
'value' => 5
),
array(
'year' => 2011,
'month ' => 5,
'day' => 13,
'value' => 1
),
array(
'year' => 2011,
'month ' => 5,
'day' => 14,
'value' => 9
)
);?>
here's how i'd like the output to look:
<?$output=array(
array(
'year' => 2011,
'month ' => 5,
'day' => 13,
'value' => 3 //the sum of 1+2
),
array(
'year' => 2011,
'month '=> 5,
'day' => 14,
'value' => 14 //the sum of 5+9
)
);?>
Notice how the 4 sub-arrays were matched on year/month/day and then only the value was summed. I've seen other SO threads on this topic but can't find one where only the value is summed and not the year/month/day values too.
Thoughts?
It may be easiest to initially index your output array with a combination of the year/month/day:
Note: Your example array above has all its month keys with a trailing space. I'm just using month here with no trailing space.
// Initialize output array...
$out = array();
// Looping over each input array item
foreach ($myArray as $elem) {
// Initialize a new element in the output keyed as yyyy-mm-dd if it doesn't already exist
if (!isset($out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']])) {
$out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']] = array(
// Set the date keys...
'year' => $elem['year'],
'month' => $elem['month '],
'day' => $elem['day'],
// With the current value...
'value' => $elem['value']
);
}
// If it already exists, just add the current value onto it...
else {
$out[$elem['year'] . "-" . $elem['month '] . "-" . $elem['day']]['value'] += $elem['value'];
}
}
// Now your output array is keyed by date. Use array_values() to strip off those keys if it matters:
$out = array_values($out);
Outputs (before calling array_values()):
array(2) {
'2011-5-13' =>
array(4) {
'year' =>
int(2011)
'month' =>
int(5)
'day' =>
int(13)
'value' =>
int(3)
}
'2011-5-14' =>
array(4) {
'year' =>
int(2011)
'month' =>
int(5)
'day' =>
int(14)
'value' =>
int(14)
}
}
Update:
To do the same thing with single-key dates (rather than 3-parts) it is easier without the concatenation:
$myArray=array(
array(
'date' => '2011-05-13',
'value' => 2
),
array(
'date' => '2011-05-14',
'value' => 5
),
array(
'date' => '2011-05-13',
'value' => 7
),
array(
'date' => '2011-05-14',
'value' => 3
),
);
foreach ($myArray as $elem) {
// Initialize a new element in the output if it doesn't already exist
if (!isset($out[$elem['date']])) {
$out[$elem['date'] = array(
// Set the date keys...
'date' => $elem['date'],
// With the current value...
'value' => $elem['value']
);
}
else {
$out[$elem['date']]['value'] += $elem['value'];
}
}
Here's how I would do it. The result will be in $newArray with datetime objects as keys. If you just want it as an indexed array it should be pretty easy to do.
// Example array
$myArray = array(
array(
'date' => new DateTime('1993-08-11'),
'value' => 3
),
array(
'date' => new DateTime('1993-08-11'),
'value' => 5
)
);
$newArray = array();
foreach($myArray as $element)
{
$iterationValue = $element['value'];
$iterationDate = $element['date'];
$dateKey = $iterationDate->format('Y-m-d');
if(array_key_exists($dateKey, $newArray))
{
// If we've already added this date to the new array, add the value
$newArray[$dateKey]['value'] += $iterationValue;
}
else
{
// Otherwise create a new element with datetimeobject as key
$newArray[$dateKey]['date'] = $iterationDate;
$newArray[$dateKey]['value'] = $iterationValue;
}
}
nl2br(print_r($newArray));
Actually ended up doing the pretty much the same thing as #MichaelBerkowski solution. Still, having DateTime objects is always more flexible when you wan't to do things with the dates later in your application.
Edit: Now tested it and fixed errors
Related
(In between Christmas meals) I am again stuck with the loop through array and group by logic.
here is the array I am given
$aTest = Array
(
Array
(
'date' => 2017-05-04,
'period' => 'period2',
'category' => 'Indoor room',
'score' => 1
),
Array
(
'date' => 2017-05-04,
'period' => 'period5',
'category' => 'Indoor room',
'score' => 1
),
Array
(
'date' => 2017-05-04,
'period' => 'period2',
'category' => 'Indoor room',
'score' => 2
),
Array
(
'date' => 2017-05-04,
'period' => 'period4',
'category' => 'Indoor room',
'score' => 1
),
Array
(
'date' => 2017-05-03,
'period' => 'period5',
'category' => 'Gym Class',
'score' => 1
),
Array
(
'date' => 2017-05-03,
'period' => 'period1',
'category' => 'Gym Class',
'score' => 1
),
Array
(
'date' => 2017-05-03,
'period' => 'period4',
'category' => 'Indoor room',
'score' => 1
)
);
This time I like group by category and sum the score group by period. Y-axis will be the category and X-axis will be the period. In the end I need this for a google chart
/*period, total indoor, total gym, 'total indoor', 'total gym'*/
array(
['Period1', 0,1,'0','1'],
['Period2', 3,0,'3','0'],
['Period3', 0, 0,'0','0'],
['Period4', 4,0,'4','0'],
['Period5', 1,1,'1','1']
)
I have this php code:
foreach ($aTest as $value) {
//echo $value['period'].' - '.$value['score'].'<br/>';
if (empty($output[$value]['period']))
$output[$value]['period'] = ['Period1' => 0, 'Period2' => 0, 'Period3' =>0, 'Period4' => 0, 'Period5' => 0];
if(empty($output[$value]['category']))
$output[$value['catgeory']] = ['Gym Class' => 0, 'Indoor room' =>0];
$output[$value['category']] += $value['score'];
}
ksort($output);
but this only totals the score by Category and not by period.
I think I need to loop through the periods as well, but how?
You have a wrong logic here.
if (empty($output[$value]['period']))
$output[$value]['period'] = ['Period1' => 0, 'Period2' => 0, 'Period3' =>0, 'Period4' => 0, 'Period5' => 0];
that $value is an array and you try to check $output[$value]
I saw that you don't have any line sum periods Value.
I have a solution for your data.
What is my code do??
Sum score of the period by the category
For each merge period and category score to an array using arraySort category to set position of these category scores values
$temp = [];
$output = [];
foreach($aTest as $value){
$period = $value['period'];
$category = $value['category'];
// Create default values
if (empty($temp[$period])){
$temp[$period] = [];
}
if(empty($temp[$period][$category])){
$temp[$period][$category] = 0;
}
//Sum score
$temp[$period][$category] += $value['score'];
}
//After Forech we have an array with ['period name' => ['category name' => score]];
//Sort values of the category change it if you want, you can add more option such as (item type for add '' to values)
$arraySort = [
"Indoor room", //total indoor,
"Gym Class", // total gym,
"Indoor room", //'total indoor',
"Gym Class" //'total gym'
];
foreach($temp as $period => $catsScore){
$periodItem = [$period];
foreach($arraySort as $cat){
$periodItem[] = $catsScore;
}
$output[] = $periodItem;
}
I have an array $post of the following format
$post[0] = [
'id' => '103',
'date' => '2016-04-17 16:30:12',
'desc' => 'content description'
];
$post[1] = [
'id' => '102',
'date' => '2016-04-17 12:30:12',
'desc' => 'content description'
];
$post[2] = [
'id' => '101',
'date' => '2016-04-17 10:30:12',
'desc' => 'content description'
];
$post[3] = [
'id' => '100',
'date' => '2016-04-16 08:30:12',
'desc' => 'content description'
];
I would like to use strtotime(date) from the $post as an unique array key, and create:
$summary['day-of-2016-04-17'] = [
'counts' => '3'
];
$summary['day-of-2016-04-16'] = [
'counts' => '1'
];
Where counts is the number of occurrence of the date used as the key.
I only need to keep the date itself as the unique key and time value is irrelevant.
I'll need the key value to be unix timestamp for further processing.
How can I implement this in the most efficient way?
Just use the date as key. The simplest way would be just use explode the date time, get the first fragment (which is the date), and assign it just like any normal array:
$summary = [];
foreach($post as $value) {
$dt = explode(' ', $value['date']); // break the date
$day_of = "day-of-{$dt[0]}"; // create the naming key
if(!isset($summary[$day_of])) { // initialize
$summary[$day_of]['counts'] = 0;
}
$summary[$day_of]['counts']++; // increment
}
I'm having problems comparing these arrays.
In a nutshell I want to check if $tid_and_date_arr exists within $curr_vals. (Have a look. It does, obviously.)
My logic is flawed, however, as the second time during the loop, $tid_and_date_arr != $value[1] so the value isn't skipped.
What am I missing? Another loop inside the loop?
$curr_vals = array(array('tid' => 22, 'date' => 1497250800), array('tid' => 22, 'date' => 1497337200));
$tid_and_date_arr = array('tid' => 22, 'date' => 1497250800));
foreach($curr_vals as $value){
if ($tid_and_date_arr == $value) {
// skip these values as we've already saved them
continue;
}
else {
// save these values as they are new
}
}
What is wrong with good old array_search?
$curr_vals = array(array('tid' => 22, 'date' => 1497250800), array('tid' => 22, 'date' => 1497337200));
//$tid_and_date_arr = array('tid' => 22, 'date' => 1497250800); -- this will output 0
$tid_and_date_arr = array('tid' => 22, 'date' => 1497337200);
$result = array_search($tid_and_date_arr, $curr_vals);
print_r($result);
This will output the key of the subarray you're looking for:
1
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:
I'm have time overlap check inside while loop and I would also like to display events title with results which is in another array.
Without double foreach loop I can echo $event->title; and it displays all event titles. If I put array $event which has $event->title, inside foreach which I have for overlap testing it only displays first title.
I'm newbie PHP and I'm banging my head with complete solution for a couple of days now. Any help would be awesome :(
while (list(,$event_row) = each($events)) {
$event->loadEvent($event_row[0]);
foreach ($events as $thisevent) {
$event_array = array($event->id,$event->title);
$conflicts = 0;
foreach ($events as $thatevent) {
if ($thisevent[0] === $thatevent[0]) {
continue;
}
$thisevent_from = $thisevent[1];
$thisevent_ends = $thisevent[2];
$thatevent_from = $thatevent[1];
$thatevent_ends = $thatevent[2];
if ($thatevent_ends > $thisevent_from AND $thisevent_ends > $thatevent_from) {
echo $event->title;
$conflicts++;
echo "Event #" . $thisevent[0] . " overlaps with Event # " . $thatevent[0] . "\n";
}
}
if ($conflicts === 0) {
echo "Event #" . $thisevent[0] . " is OK\n";
}
}
}
Without double foreach var_export($event) displays all event arrays.
array( 'title' => 'Event 1', 'description' => 'Event description 1', 'contact' => 'event contact 1', 'url' => '', 'link' => '', 'email' => 'event#eventemail.com', 'picture' => '', 'color' => '#009933', 'catName' => 'Events', 'catDesc' => 'General events', 'startDate' => 1432918800, 'endDate' => 1432926000, 'startDay' => 29, 'startMonth' => 5, 'startYear' => 2015, 'endDay' => 29, 'endMonth' => 5, 'endYear' => 2015, 'recurStartDate' => NULL, 'recurEndDate' => NULL, 'id' => '11', 'catId' => 1, 'status' => true, 'recType' => '', 'recInterval' => 0, 'recEndDate' => 1432850400, 'recEndType' => 0, 'recEndCount' => 1, ))
And var_export($events) array displays three times all arrays with id, start date and end date:
array ( 0 => array ( 0 => '11', 1 => 1432918800, 2 => 1432926000, ), 1 => array ( 0 => '13', 1 => 1432918800, 2 => 1432926000, ), 2 => array ( 0 => '16', 1 => 1432926000, 2 => 1432929600, ), )
Thank you for looking into it and giving any possible solutions hints, ideas :)