I have an array that looks something like this
$data =
[
[
'date' => '2020-10-01',
'product' => 'Product 1',
'description' => 'Product 1',
],
[
'date' => '2020-11-01',
'product' => 'Product 2',
'description' => 'Product 2',
],
[
'date' => '2020-12-01',
'product' => 'Product 3',
'description' => 'Product 3',
],
[
'date' => '2021-01-01',
'product' => 'Product 4',
'description' => 'Product 4',
]
];
And what I would like to do is grab all the information between 2 dates.
For example I want everything between 2021-01-01 and 2020-11-01 and it would then display Product 2, Product 3 and Product 4
I'm not sure how to go about this
<?php
$data =
[
[
'date' => '2020-10-01',
'product' => 'Product 1',
'description' => 'Product 1',
],
[
'date' => '2020-11-01',
'product' => 'Product 2',
'description' => 'Product 2',
],
[
'date' => '2020-12-01',
'product' => 'Product 3',
'description' => 'Product 3',
],
[
'date' => '2021-01-01',
'product' => 'Product 4',
'description' => 'Product 4',
]
];
$from = new DateTime('2020-11-11');
$to = new DateTime('2030-10-13');
foreach ($data as $d){
$productDate = new DateTime($d['date']);
if (($productDate >= $from) && ($productDate <= $to)){
echo "is between";
var_dump($d);
}
}
In case you want to do this in PHP you can create a similar function to SQL's WHERE ... BETWEEN ... AND ... using array_filter.
If you have access to the SQL query then probably you want to have the DBMS take care of this as #RiggsFolly mentioned in the comments.
$lowerBound = strtotime('2020-11-01');
$upperBound = strtotime('2021-01-01');
$result = array_filter($data, function ($item) use ($lowerBound, $upperBound) {
$itemDate = strtotime($item['date']);
return $lowerBound <= $itemDate && $itemDate <= $upperBound;
});
echo '<pre>';
print_r($result);
echo '</pre>';
As per the comments if the data is originally sourced from a database query it's a much better idea to limit the content there, for example:
// SQL query to be used in a prepared statement
SELECT date, product, description
FROM products
WHERE date >= ?
AND date <= ?
However, failing that, the key thing here is that the dates are all properly formatted: timetamps follow the pattern Y-m-d which means that both the chronological and alphabetical order are the same
Generally...
Start ===> End
A ===> Z
1 ===> 10
0000-00-00 ===> 9999-99-99
Which means...
2020-12-30 < 2020-12-31 < 2021-01-01
That being the case when we compare two properly formatted dates (in the same format) we have no need to alter the data type/format; converting the string with DateTime or strtotime adds nothing except ~10x the required time compared to comparing the values in string format.
Anyway, comparing the dates:
Using array_filter
$minDate = '2020-11-01';
$maxDate = '2021-01-01';
$result = array_filter($data, function ($item) use ($minDate, $maxDate) {
return $minDate <= $item["date"] && $item["date"] <= $maxDate;
});
Using foreach
$minDate = '2020-11-01';
$maxDate = '2021-01-01';
$result = [];
foreach($data as $item){
if($minDate <= $item["date"] && $item["date"] <= $maxDate){
$result[] = $item;
}
}
Related
I'm using the following PHP code to show a different text (just one) every week:
<?php
$items = [
[
'start' => '2020-02-03',
'end' => '2020-02-09',
'html' => 'Text #1'
],
[
'start' => '2020-02-10',
'end' => '2020-02-16',
'html' => 'Text #2'
],
[
'start' => '2020-02-17',
'end' => '2020-02-23',
'html' => 'Text #3'
],
];
$currentDate = date('Y-m-d');
foreach ($items as $item) {
if ($currentDate >= $item[start] && $currentDate <= $item[end]) echo $item[html];
}
It works.
But is there a better (i.e. cleaner, faster) way to achieve the same result? Is loop really necessary?
Thanks.
UPDATE
Inspired by Progrock's answer (which I thank), I would modify my code as follows:
$items =
[
'06' => 'Text #1',
'07' => 'Text #2',
'08' => 'Text #3',
'09' => 'Text #4'
];
$date = new DateTime(date('Y-m-d'));
echo $items[$date->format('W')];
I think it's a better solution (for what I need).
As your ranges are monday->sunday you could use ISO-8601 week numbering. Though here the data is harder to interpret without comments.
<?php
$items =
[
'06' => 'Text #1',
'07' => 'Text #2',
'08' => 'Text #3'
];
$iso_week = date('W', strtotime('2020-02-12'));
echo $items[$iso_week];
Output:
Text #2
https://www.php.net/manual/en/function.array-filter.php
$currentDate = date('Y-m-d');
$filteredItems = array_filter($items, function($item) use ($currentDate) {
return $currentDate >= $item['start'] && $currentDate <= $item['end'];
});
Though, you're still going to have to loop the filtered items for output, eventually.
This question already has answers here:
multidimensional array array_sum
(10 answers)
Closed 5 years ago.
I need help regarding foreach and arrays in PHP
Say I have the following array:
$orders = array (
0 =>
array (
'company' => 'Company 1',
'total' => '5',
),
1 =>
array (
'company' => 'Company 2',
'total' => '10',
),
2 =>
array (
'company' => 'Company 1',
'total' => '15',
),
3 =>
array (
'company' => 'Company 1',
'total' => '5',
),
4 =>
array (
'company' => 'Company 3',
'total' => '12',
)
);
Order 1 is 5 for Company 1
Order 2 is 10 for Company 2
Order 3 is 15 for Company 1
Order 4 is 5 for Company 2
Order 5 is 12 for Company 3
I want the output to show the company name and the accumulative total of each company's orders
For example:
Company 1 20
Company 2 15
Company 3 12
Just create another array that will track the orders.
$companies = array();
foreach ($orders as $order) {
if (array_key_exists($order["company"], $companies)) {
$companies[$order["company"]] += $order["total"];
} else {
$companies[$order["company"]] = $order["total"];
}
}
First, we check if the company is already in the companies array, if it is then we add the total to that company's current total.
Otherwise, we just create a new key and store the total.
Additionally, you can write (int)$order["total"] to typecast into integer.
This might be useful to ensure that you have the correct data.
array_reduce() solution:
$groups = array_reduce($orders, function($r, $a) {
$k = $a['company'];
(isset($r[$k]))? $r[$k] += $a['total'] : $r[$k] = $a['total'];
return $r;
}, []);
foreach ($groups as $k => $v) {
printf("%-20s%d\n", $k, $v);
}
The output:
Company 1 25
Company 2 10
Company 3 12
One way to do this with a foreach loop where you increment some variable :
// Your array
$array = array(...);
// Init of the sum of each total for each company
$c1 = 0;
$c2 = 0;
$c3 = 0;
// You loop through your array and test the output
foreach ($array as $order => $value_array) {
switch($value_array['company']) {
case 'Company 1' : $c1 += intval($value_array['total']); break;
case 'Company 2' : $c2 += intval($value_array['total']); break;
case 'Company 3' : $c3 += intval($value_array['total']); break;
}
}
//$c1 will be the sum for company 1;
//$c2 for the company 2;
//$c3 for the company 3.
Is it what you are looking for?
Just another way: array_walk
$result = array();
array_walk($orders, function ($element) use (&$result) {
$company = $element['company'];
if (!isset($result[$company]))
$result[$company] = 0;
$result[$company] += $element['total'];
});
For this input:
$orders = array(
0 =>
array(
'company' => 'Company 1',
'total' => '5',
),
1 =>
array(
'company' => 'Company 2',
'total' => '10',
),
2 =>
array(
'company' => 'Company 1',
'total' => '15',
),
3 =>
array(
'company' => 'Company 1',
'total' => '5',
),
4 =>
array(
'company' => 'Company 3',
'total' => '12',
)
);
The output will be:
array(3) {
["Company 1"]=>
int(25)
["Company 2"]=>
int(10)
["Company 3"]=>
int(12)
}
I need to get all "top level" keys from multidimensional array by searching the "bottom level" values. Here is an example of the array:
$list = array (
'person1' => array(
'personal_id' => '1',
'short_information' => 'string',
'books_on_hand' => array(
'Book 1',
'Book 2',
'Book 3',
)
),
'person2' => array(
'personal_id' => '2',
'short_information' => 'string',
'books_on_hand' => array(
'Book 4',
'Book 2',
'Book 5',
)
),
'person3' => array(
'personal_id' => '3',
'short_information' => 'string',
'books_on_hand' => array(
'Book 4',
'Book 2',
'Book 1',
'Book 3',
)
),
//etc...
);
I want to know all persons who have "Book 2" on hand. I can get that information by loop like this:
foreach ($list as $person => $info){
$check = array_search( 'Book 2', array_column($info, 'books_on_hand') );
if ( $check !== false ){
$results .= 'Name: '.$person;
$results .= 'ID: '.$info['personal_id'];
//get other infos other stuff, if necessary
}
}
The problem is, that foreach in this case is very heavy on memory and only grows more when array has a thousand+ entries. It needs to run through all of the persons, even if only 3 persons at the very top of the array have "Book 2".
I have been trying to optimize it by getting persons with "Book 2" using built-in functions like array_search, array_keys, array_column and only then run foreach for persons found, but I had no luck with getting "top level" keys.
Is it possible to optimize or use built-in function to search multidimensional array?
One way would be to filter it first. Now your result is structured like $list but it only contains elements with the needed book:
$find = 'Book 2';
$result = array_filter($list, function($v) use($find) {
return in_array($find, $v['books_on_hand']);
});
If all you're interested in is the person key and personal_id then this:
$find = 'Book 2';
$result = array_map(function($v) use($find) {
if(in_array($find, $v['books_on_hand'])) {
return $v['personal_id'];
}
}, $list);
Will return something like this for persons with the needed book:
Array
(
[person1] => 1
[person2] => 2
[person3] => 3
)
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 trying to pass some of the values from theOptions array and drop them into a new array called $theDefaults.
$theOptions = array(
'item1' => array('title'=>'Title 1','attribute'=>'Attribute 1','thing'=>'Thing 1'),
'item2' => array('title'=>'Title 2','attribute'=>'Attribute 2','thing'=>'Thing 2'),
'item3' => array('title'=>'Title 3','attribute'=>'Attribute 3','thing'=>'Thing 3')
);
So, $theDefaults array should look like this:
$theDefaults = array(
'Title 1' => 'Attribute 1',
'Title 2' => 'Attribute 2',
'Title 3' => 'Attribute 3'
);
However, I cannot figure out how to do this.
Have tried this but it is clearly not quite working.
$theDefaults = array();
foreach($theOptions as $k=>$v) {
array_push($theDefaults, $v['title'], $v['attribute']);
}
but when I run this...
foreach($theDefaults as $k=>$v) {
echo $k .' :'.$v;
}
It returns this.
0 :Title 11 :Attribute 12 :Title 23 :Attribute 24 :Title 35 :Attribute 3
Looks to be soooo close, but why are the numbers in the array?
It's even simpler than that:
$theDefaults = array();
foreach($theOptions as $v) {
$theDefaults[$v['title']] = $v['attribute'];
}