PHP group array by date with interval - php

I have this array
Array
(
[0] => Array
(
[date] => 2020-10-07 10:49:48
[content] => 1
)
[1] => Array
(
[date] => 2020-10-08 13:49:48
[content] => 2
)
[2] => Array
(
[date] => 2020-10-08 13:50:03
[content] => 3
)
)
I want to get:
Array
(
[0] => Array
(
[date] => 2020-10-07 10:49:48
[content] => 1
)
[1] => Array
(
[date] => 2020-10-08 13:50:03
[content] => 2, 3
)
)
Like you see, if date between elements is less than hour (for example), content of both elements should be grouped with the greater date value. I just don't understand how to do it properly
I got this, but idk why i have extra element in array
foreach ($result as $key => $value) {
foreach ($result as $key2 => $value2) {
if (abs(strtotime($value['date_added']) - strtotime($value2['date_added'])) <= 3600 && $key != $key2) {
$result[$key]['content'] = $value['content'] . ',' . $value2['content'];
unset($result[$key2]);
}
}
$merged_array[] = $result[$key];
}

It is not clearly described which time differences should be used. The solution here always calculates the time differences to the highest date of the group.
The array will be sorted in descending order of the date. The highest date is set as $groupDate.
A new $result array is created with a foreach loop.
If the date difference to $groupDate is less than $intervalHour, then 'content' is accumulated. In the other case a new $groupDate is set. At the end the result array is sorted again according to increasing date.
$data = [
['date' => '2020-10-07 10:49:48', 'content' => 1],
['date' => '2020-10-08 13:49:48', 'content' => 2],
['date' => '2020-10-08 13:50:03', 'content' => 3],
['date' => '2020-10-08 14:50:04', 'content' => 4],
];
$intervalHour = 1.0;
usort($data,function($a,$b){return $b['date'] <=> $a['date'];});
$groupDate = date_create($data[0]['date']);
$content = $data[0]['content'];
$result = [$data[0]];
$k = 0;
foreach($data as $i => $row){
if($i == 0) continue;
$diff = date_create($row['date'])->diff($groupDate);
$diffHour = $diff->days * 24 + $diff->h + $diff->i/60 + $diff->s/3600;
if($diffHour < $intervalHour) {
$content = $row['content'].','.$content;
}
else {
++$k;
$groupDate = date_create($row['date']);
$result[$k]['date'] = $groupDate->format('Y-m-d H:i:s');
$content = $row['content'];
}
$result[$k]['content'] = $content;
}
usort($result,function($a,$b){return $a['date'] <=> $b['date'];});
echo '<pre>',var_export($result);
Output with $intervalHour = 1.0
array (
0 =>
array (
'date' => '2020-10-07 10:49:48',
'content' => 1,
),
1 =>
array (
'date' => '2020-10-08 13:50:03',
'content' => '2,3',
),
2 =>
array (
'date' => '2020-10-08 14:50:04',
'content' => 4,
),
)
Output with $intervalHour = 2.0
array (
0 =>
array (
'date' => '2020-10-07 10:49:48',
'content' => 1,
),
1 =>
array (
'date' => '2020-10-08 14:50:04',
'content' => '2,3,4',
),
)

The problem is that your unset command does not work. When you use a foreach on an array, a copy of that array will be created and iterated. If you want to iterate over an instance that you can change on the fly, you would need to pass an iterable object.
As an example, the following would output 12, despite the unset:
$x = [1, 2];
foreach ($x as $key => $value) {
echo $value;
unset($x[1]);
}
I think best approach is using OOP. But if you want to work only with forloops and arrays, it could be done like this:
$usedKeys = [];
$dateContent =[];
foreach ($result as $key => $value) {
if (in_array($key, $usedKeys)) {
continue;
}
$usedKeys[] = $key;
$dateContent[$key]['content'] = [$value['content']];
foreach ($result as $key2 => $value2) {
if (in_array($key2, $usedKeys)) {
continue;
}
if (abs(strtotime($value['date']) - strtotime($value2['date'])) <= 3600 && $key != $key2) {
$dateContent[$key]['content'][] = $value2['content'];
$usedKeys[] = $key2;
}
}
}
$dateContent = array_map(function ($value) {
return implode(',', $value['content']);
}, $dateContent);

Related

php remove empty 'columns' in multidimensional, associative array

Goal: Generate an array that includes only those 'columns' with data, even though a 'header' may exist.
Example Data:
Array (
[HeaderRow] => Array (
[0] => Employee [1] => LaborHours [2] => 0.1 [3] => 0.25 [4] => 0.5 [5] => 0.8
)
[r0] => Array (
[0] => Joe [1] => 5 [2] => [3] => [4] => 50 [5] =>
)
[r1] => Array (
[0] => Fred [1] => 5 [2] => 10 [3] => [4] => [5] =>
)
)
Desired Output:
Array (
[HeaderRow] => Array (
[0] => Employee [1] => LaborHours [2] => 0.1 [4] => 0.5
)
[r0] => Array (
[0] => Joe [1] => 5 [2] => [4] => 50
)
[r1] => Array (
[0] => Fred [1] => 5 [2] => 10 [4] =>
)
)
So, in this very dumbed down example, the HeaderRow will always have data, but if both c0 and c1 are empty (as is the case for [3] and [5]) then I want to remove. I tried iterating through with for loops like I would in other languages, but that apparently doesn't work with associative arrays. I then tried doing a transpose followed by two foreach loops, but that failed me as well. Here's a sample of my for loop attempt:
Attempt with For Loop
for ($j = 0; $j <= count(reset($array))-1; $j++) {
$empty = true;
for ($i = 1; $i <= count($array)-1; $i++) {
if(!empty($array[$i][$j])) {
$empty = false;
break;
}
}
if ($empty === true)
{
for ($i = 0; $i <= count($array); $i++) {
unset($array[$i][$j]);
}
}
}
return $array;
Attempt with transpose:
$array = transpose($array);
foreach ($array as $row)
{
$empty = true;
foreach ($row as $value)
{
if (!empty($value))
{
$empty = false;
}
}
if ($empty) {
unset($array[$row]);
}
}
$array = transpose($array);
return $array;
function transpose($arr) {
$out = array();
foreach ($arr as $key => $subarr) {
foreach ($subarr as $subkey => $subvalue) {
$out[$subkey][$key] = $subvalue;
}
}
return $out;
}
I know the transpose one isn't terribly fleshed out, but I wanted to demonstrate the attempt.
Thanks for any insight.
We can make this more simpler. Just get all column values using array_column.
Use array_filter with a custom callback to remove all empty string values.
If after filtering, size of array is 0, then that key needs to be unset from all
subarrays.
Note: The arrow syntax in the callback is introduced since PHP 7.4.
Snippet:
<?php
$data = array (
'HeaderRow' => Array (
'0' => 'Employee','1' => 'LaborHours', '2' => 0.1, '3' => 0.25, '4' => 0.5, '5' => 0.8
),
'r0' => Array (
'0' => 'Joe', '1' => 5, '2' => '','3' => '', '4' => 50, '5' => ''
),
'r1' => Array (
'0' => 'Fred', '1' => 5,'2' => 10, '3' => '', '4' => '', '5' => ''
)
);
$cleanup_keys = [];
foreach(array_keys($data['HeaderRow']) as $column_key){
$column_values = array_column($data, $column_key);
array_shift($column_values); // removing header row value
$column_values = array_filter($column_values,fn($val) => strlen($val) != 0);
if(count($column_values) == 0) $cleanup_keys[] = $column_key;
}
foreach($data as &$row){
foreach($cleanup_keys as $ck){
unset($row[ $ck ]);
}
}
print_r($data);
It figures, I work on this for a day and have a moment of clarity right after posting. The answer was that I wasn't leveraging the Keys.:
function array_cleanup($array)
{
$array = transpose($array);
foreach ($array as $key => $value)
{
$empty = true;
foreach ($value as $subkey => $subvalue)
{
if ($subkey != "HeaderRow") {
if (!empty($subvalue))
{
$empty = false;
}
}
}
if ($empty) {
unset($array[$key]);
}
}
$array = transpose($array);
return $array;
}

PHP: substract values of 2 arrays of different length

Well, I've got two following arrays:
$incomesArr = [
['1605564000' => 121],
['1605736800' => 3.50],
];
$expensesArr = [
['1605736800' => 3.50],
['1605736800' => 3.50],
['1605564000' => 28],
['1605936000' => 50]
];
As you can see from the variable names, these are the expenses and incomes for the current date (it is a timestamp). All I want to do is to generate the "Revenue" data by substracting value of $expensesArr from $incomesArr with corresponding date, and if there is no date, then substract from 0. Also, if there is no corresponding date in the $expensesArr, then substract nothing (or 0).
Basically, I want to achieve the following result:
['1605564000', 93]; // 121 - 28 = 93
['1605736800', -3.50] // 3.50 - 3.50 - 3.50 (since both expenses are for the same date)
['1605936000', -50] // 0 - 50, since there is no income for that day.
Please note, that the output result should be in the ['key', 'value'] format, not the ['key' => 'value'], since I send these data to jQuery Flot (displaying graphs).
I carry about the performance, since there may be kind of significant amount of data and the best way for me to achieve this is without foreach loop, however if it is the only possible option, then foreach it is!
Any help is highly appreciated. Thank you in advance!
P.S. If it would help, then I do this in Laravel, however I don't think that there is a special helper exactly for that, since I already examined the Laravel documentation multiple times :)
My previous attempt was a bit confusing and scary, but here it is:
$newArr = array_merge([$incomesArr, $expensesArr]);
foreach ($incomesArr as $k => $v){
$sub = $v - $expensesArr[$k];
// this is like : array1[1]-array2[1]
}
$finalArr = [];
foreach($newArr as $key => $value) {
var_dump($value);
foreach ($value as $key => $val) {
$finalArr[$key] = ($val[$key] ?? 0) - ($val[$key] ?? 0);
}
}
$incomesArr = [
['1605564000' => 121],
['1605736800' => 3.50],
];
$expensesArr = [
['1605736800' => 3.50],
['1605736800' => 3.50],
['1605564000' => 28],
['1605936000' => 50]
];
// get data
$dataArr = [];
foreach([-1 => $expensesArr, 1 => $incomesArr] as $ratio => $list){
foreach($list as $value){
$time = key($value);
$dataArr[$time] = ($dataArr[$time] ?? 0) + ($value[$time] * $ratio);
}
}
// convert to your format
$finalArr = [];
foreach($dataArr as $time => $value){
$finalArr []= [$time, $value];
}
print_r($finalArr);
Output
Array
(
[0] => Array
(
[0] => 1605736800
[1] => -3.5
)
[1] => Array
(
[0] => 1605564000
[1] => 93
)
[2] => Array
(
[0] => 1605936000
[1] => -50
)
)
I would split it into two functions like so:
$incomesArr = [
['1605564000' => 121],
['1605736800' => 3.50],
];
$expensesArr = [
['1605736800' => 3.50],
['1605736800' => 3.50],
['1605564000' => 28],
['1605936000' => 50]
];
function getTotalByKey($inputArray, $inputKey)
{
$sum = 0;
foreach ($inputArray as $subArray)
foreach ($subArray as $key => $value) {
$sum += $key === $inputKey ? $value : 0;
}
return $sum;
}
function getProfitByKey($incomesArray, $expensesArray, $key){
return getTotalByKey($incomesArray, $key) - getTotalByKey($expensesArray, $key);
}
$profit = getProfitByKey($incomesArr, $expensesArr, 1605736800); // Outputs -3.5
$incomesArr = [
['1605564000' => 121],
['1605736800' => 3.50],
];
$expensesArr = [
['1605736800' => 3.50],
['1605736800' => 3.50],
['1605564000' => 28],
['1605936000' => 50]
];
$tmp = [];
$final = [];
foreach ($incomesArr as $pair)
{
foreach ($pair as $date => $amount)
{
$tmp[$date] = ($tmp[$date] ?? 0) + $amount;
}
}
foreach ($expensesArr as $pair)
{
foreach ($pair as $date => $amount)
{
$tmp[$date] = ($tmp[$date] ?? 0) - $amount;
}
}
print_r($tmp);
foreach ($tmp as $date => $amount)
{
$final[] = [$date, $amount];
}
print_r($final);
Output
Array
(
[1605564000] => 93
[1605736800] => -3.5
[1605936000] => -50
)
Array
(
[0] => Array
(
[0] => 1605564000
[1] => 93
)
[1] => Array
(
[0] => 1605736800
[1] => -3.5
)
[2] => Array
(
[0] => 1605936000
[1] => -50
)
)

Simplify Array Duplicate Element in Array PHP

How can we find the count of duplicate elements in a multidimensional array,
I have an array like this:
Array
(
[0] => Array
(
[brti] => 29
)
[1] => Array
(
[voda] => 6
)
[2] => Array
(
[btel] => 8
)
[3] => Array
(
[btel] => 10
)
)
Question: How to simplify the structure of array, i mean can be counting the value if there is indicate that have same key ?
just like this:
Array
(
[0] => Array
(
[brti] => 29
)
[1] => Array
(
[voda] => 6
)
[2] => Array
(
[btel] => 18
)
)
So far, i've tried this way, but it didn't help me. My array is store in $test
$test = [sample array]
$count = array();
foreach ($test as $key => $value) {
foreach ($value as $k => $val) {
if (isset($count[$val])) {
++$count[$val];
} else {
$count[$value] = 1;
}
}
}
print_r($count);
<?php
$array = [
"0" => ["brti" => 29],
"1" => ["voda" => 6],
"2" => ["btel" => 8],
"3" => ["btel" => 10],
];
$final = array();
array_walk_recursive($array, function($item, $key) use (&$final){
$final[$key] = isset($final[$key]) ? $item + $final[$key] : $item;
});
print_r($final);
});
check demo
You can do it in very simple way,
$test = [];
foreach ($array as $value)
{
foreach ($value as $k => $v)
{
// $test[$k] = ($test[$k] ?? 0); // initialised if not php 7+
$test[$k] = (empty($test[$k]) ? 0: $test[$k]); // below php 7
$test[$k] += $v;
}
}
print_r($test);
Output:
Array
(
[brti] => 29
[voda] => 6
[btel] => 18
)
Working demo.

PHP Counting inside an Array

I want to create a list where if its already in the array to add to the value +1.
Current Output
[1] => Array
(
[source] => 397
[value] => 1
)
[2] => Array
(
[source] => 397
[value] => 1
)
[3] => Array
(
[source] => 1314
[value] => 1
)
What I want to Achieve
[1] => Array
(
[source] => 397
[value] => 2
)
[2] => Array
(
[source] => 1314
[value] => 1
)
My current dulled down PHP
foreach ($submissions as $timefix) {
//Start countng
$data = array(
'source' => $timefix['parent']['id'],
'value' => '1'
);
$dataJson[] = $data;
}
print_r($dataJson);
Simply use an associated array:
$dataJson = array();
foreach ($submissions as $timefix) {
$id = $timefix['parent']['id'];
if (!isset($dataJson[$id])) {
$dataJson[$id] = array('source' => $id, 'value' => 1);
} else {
$dataJson[$id]['value']++;
}
}
$dataJson = array_values($dataJson); // reset the keys - you don't nessesarily need this
This is not exactly your desired output, as the array keys are not preserved, but if it suits you, you could use the item ID as the array key. This would simplify your code to the point of not needing to loop through the already available results:
foreach ($submissions as $timefix) {
$id = $timefix['parent']['id'];
if (array_key_exists($id, $dataJson)) {
$dataJson[$id]["value"]++;
} else {
$dataJson[$id] = [
"source" => $id,
"value" => 1
];
}
}
print_r($dataJson);
You should simplify this for yourself. Something like:
<?
$res = Array();
foreach ($original as $item) {
if (!isset($res[$item['source']])) $res[$item['source']] = $item['value'];
else $res[$item['source']] += $item['value'];
}
?>
After this, you will have array $res which will be something like:
Array(
[397] => 2,
[1314] => 1
)
Then, if you really need the format specified, you can use something like:
<?
$final = Array();
foreach ($res as $source=>$value) $final[] = Array(
'source' => $source,
'value' => $value
);
?>
This code will do the counting and produce a $new array as described in your example.
$data = array(
array('source' => 397, 'value' => 1),
array('source' => 397, 'value' => 1),
array('source' => 1314, 'value' => 1),
);
$new = array();
foreach ($data as $item)
{
$source = $item['source'];
if (isset($new[$source]))
$new[$source]['value'] += $item['value'];
else
$new[$source] = $item;
}
$new = array_values($new);
PHP has a function called array_count_values for that. May be you can use it
Example:
<?php
$array = array(1, "hello", 1, "world", "hello");
print_r(array_count_values($array));
?>
Output:
Array
(
[1] => 2
[hello] => 2
[world] => 1
)

PHP grouping arrays within array by value

Hi I've got a bit of a problem and I can't seem to find the answer. I have an array with arrays in it and want to group these sub arrays into groups based on the same value for a field. This is my code and allows me to sort my arrays based on the arrays inside values. How do I group the result based on shared values? I probably won't know all the values as some are based on date and I want to for instance group all per day/month
if($filter == "ORDER BY TODO_REF DESC"){
$type_sort = 0;
};
if($filter == "ORDER BY TODO_PRIO DESC"){
$type_sort = 2;
};
if($filter == "ORDER BY TODO_DEAD DESC"){
$type_sort = 3;
};
function aasort (&$array, $key) {
$sorter=array();
$ret=array();
reset($array);
foreach ($array as $ii => $va) {
$sorter[$ii]=$va[$key];
}
asort($sorter);
foreach ($sorter as $ii => $va) {
$ret[$ii]=$array[$ii];
}
$array=$ret;
}
aasort($test_array, $type_sort);
print_r($test_array);
Current output:
priority
Array
(
[3] => Array
(
[0] => 2
[1] => sdfsdgdfgdfgdfg
[2] => 3
[3] => 2013-05-30 13:53:23
)
[2] => Array
(
[0] => 1
[1] => This must also be done
[2] => 4
[3] => 2013-03-28 12:13:34
)
[1] => Array
(
[0] => 1
[1] => testing this show me 2
[2] => 5
[3] => 2029-02-23 17:27:20
)
[0] => Array
(
[0] => 1
[1] => Do this task and make sure it gets done
[2] => 5
[3] => 2013-06-28 12:12:41
)
)
What I want would be something like this, where they are split into seperate arrays based on sub array key 2:
Array
(
[3] => Array
(
[0] => 2
[1] => sdfsdgdfgdfgdfg
[2] => 3
[3] => 2013-05-30 13:53:23
)
)
Array
(
[2] => Array
(
[0] => 1
[1] => This must also be done
[2] => 4
[3] => 2013-03-28 12:13:34
)
)
Array
(
[1] => Array
(
[0] => 1
[1] => testing this show me 2
[2] => 5
[3] => 2029-02-23 17:27:20
)
[0] => Array
(
[0] => 1
[1] => Do this task and make sure it gets done
[2] => 5
[3] => 2013-06-28 12:12:41
)
)
$array_1 = array();
$array_2 = array();
foreach($test_array as $item) {
if($item[0] == 1) {
$array_1[] = $item;
} elseif($item[0] == 2) {
$array_2[] = $item;
}
}
$final_array = array($array_1, $array_2);
There must be more optimized codes, but this should serve your needs.
array_multisort() is what you're after it will allow you to sort primary array by sub array and maintain the key value link
I use the below for sorting a rather hefty multi-dimensional array based off sort order arrows asc/desc on a table with many columns and sortable columns. Its abit of a mess but you should be able to follow it.
if (strlen($_GET['col'])<1) {
foreach ($dataarray as $key => $row) {
$spend[$key] = $row[6];
}
array_multisort($spend, SORT_DESC, $dataarray);
} else if ($_GET['col']=="spend"){
foreach ($dataarray as $key => $row) {
$spend[$key] = $row[3];
}
if ($_GET['order']=="asc") {
array_multisort($spend, SORT_ASC, $dataarray);
} else {
array_multisort($spend, SORT_DESC, $dataarray);
}
} else if ($_GET['col']=="name"){
foreach ($dataarray as $key => $row) {
$name[$key] = $row[1];
}
if ($_GET['order']=="asc") {
array_multisort($name, SORT_ASC, $dataarray);
} else {
array_multisort($name, SORT_DESC, $dataarray);
}
} else if ($_GET['col']=="avg"){
foreach ($dataarray as $key => $row) {
$avg[$key] = $row[4];
}
if ($_GET['order']=="asc") {
array_multisort($avg, SORT_ASC, $dataarray);
} else {
array_multisort($avg, SORT_DESC, $dataarray);
}
} else if ($_GET['col']=="sites"){
foreach ($dataarray as $key => $row) {
$sites[$key] = $row[5];
}
if ($_GET['order']=="asc") {
array_multisort($sites, SORT_ASC, $dataarray);
} else {
array_multisort($sites, SORT_DESC, $dataarray);
}
} else if ($_GET['col']=="rate"){
foreach ($dataarray as $key => $row) {
$rate[$key] = $row[6];
}
if ($_GET['order']=="asc") {
array_multisort($rate, SORT_ASC, $dataarray);
} else {
array_multisort($rate, SORT_DESC, $dataarray);
}
} else {
foreach ($dataarray as $key => $row) {
$spend[$key] = $row[2];
}
array_multisort($spend, SORT_DESC, $dataarray);
}
Rather than a block of if-elseif-elseif-elseif...else conditions to determine the sorting/grouping column, I always recommend a lookup array followed by a very quick isset() call. I find this syntax to be more succinct, more readable, and easier to maintain.
So that grouped data is sorted properly, sort the data ahead of time (while it is still in its simpler form). usort() with the spaceship operator (php7) will do all of the hardwork for you. Just be sure to pass the $groupsort variable into the function scope with use.
As you iterate the input array, isolate the identifying column value (and prepare it as necessary), then use it as a temporary key to determine the group in which the $row data should be stored.
Finally, use array_values() to remove the temporary identifying keys (if desired).
Code: (Demo)
$filter = "ORDER BY TODO_PRIO DESC";
$lookup = [
"ORDER BY TODO_REF DESC" => 0,
"ORDER BY TODO_PRIO DESC" => 2,
"ORDER BY TODO_DEAD DESC" => 3
];
$groupsort = isset($lookup[$filter]) ? $lookup[$filter] : 0;
$array = [
[1, "Do this task and make sure it gets done", 5, "2013-06-28 12:12:41"],
[1, "testing this show me 2", 5, "2029-02-23 17:27:20"],
[1, "This must also be done", 4, "2013-03-28 12:13:34"],
[2, "sdfsdgdfgdfgdfg", 3, "2013-05-30 13:53:23"],
[2, "Another priority", 3, "2013-03-28 11:11:11"]
];
// sort DESC on $groupsort column values
usort($array, function($a, $b)use($groupsort) { return $b[$groupsort] <=> $a[$groupsort]; });
// group by $groupsort column values
foreach ($array as $row) {
$id = $row[$groupsort]; // just to improve readability
if ($groupsort == 3) { $id = substr($id, 0, 10); } // remove time from datetime stamp
$result[$id][] = $row; // store the current value
}
var_export(array_values($result)); // reindex first level (remove temp keys);
Output:
array (
0 =>
array (
0 =>
array (
0 => 1,
1 => 'Do this task and make sure it gets done',
2 => 5,
3 => '2013-06-28 12:12:41',
),
1 =>
array (
0 => 1,
1 => 'testing this show me 2',
2 => 5,
3 => '2029-02-23 17:27:20',
),
),
1 =>
array (
0 =>
array (
0 => 1,
1 => 'This must also be done',
2 => 4,
3 => '2013-03-28 12:13:34',
),
),
2 =>
array (
0 =>
array (
0 => 2,
1 => 'sdfsdgdfgdfgdfg',
2 => 3,
3 => '2013-05-30 13:53:23',
),
1 =>
array (
0 => 2,
1 => 'Another priority',
2 => 3,
3 => '2013-03-28 11:11:11',
),
),
)

Categories