Create CSV with dynamic grouped headers in PHP from array - php

Could someone please assist in achieving that follow tasks please?
How to create a CSV export from a multidimensional array, but to have dynamic grouped column headings
Array (
[0] => Array ( [months] => 06/2020 [hours] => 202 [skill] => 5 )
[1] => Array ( [months] => 06/2020 [hours] => 563.5 [skill] => 6 )
[2] => Array ( [months] => 07/2020 [hours] => 140.5 [skill] => 6 )
[3] => Array ( [months] => 07/2020 [hours] => 522.5 [skill] => 5 )
)
So the output on the CSV would be like
+----------------------------+------------+--------+
| | Skill 6 |Skill 5 |
+----------------------------+------------+--------+
| 06/2020 | 563.5 | 202 |
+----------------------------+------------+--------+
| 07/2020 | 140.5 | 522.5 |
+----------------------------+------------+--------+
Added CSV output I have so far
Current CSV Element of the code
header("Content-type: text/csv");
header("Content-Disposition: attachment; filename=result_file.csv");
header("Pragma: no-cache");
header("Expires: 0");
// Building $data_array from DB
foreach ($data_array as $subarray) {
$tempKey = $subarray['skill'].$subarray['months'];
$subarray['hours'] = str_replace(',', '', $subarray['hours']);
if (isset($result[$tempKey])) {
$result[$tempKey]['hours'] += $subarray['hours'];
} else {
$result[$tempKey] = $subarray;
}
}
// CSV Output
outputCSV($result);
function outputCSV($result) {
$output = fopen("php://output", "w");
foreach ($result as $row) {
fputcsv($output, $row);
}
fclose($output);
}
any help would be greatly appreciated, TIA
Question edited

Pretty sure if I thought about this for a bit I could improve it but it seems like it gets the right answer
$in = [
[ 'months' => '06/2020', 'hours' => 202, 'skill' => 5 ],
[ 'months' => '06/2020', 'hours' => 563.5, 'skill' => 6 ],
[ 'months' => '07/2020', 'hours' => 140.5, 'skill' => 6 ],
[ 'months' => '07/2020', 'hours' => 522.5, 'skill' => 5 ]
];
$firstTitle = 'Month';
$months = [];
$skills = [$firstTitle=>1];
// make an array keyed on the date
foreach ( $in as $t) {
$months[$t['months']]['skill'.$t['skill']] = $t['hours'];
$skills['skill'.$t['skill']] = 1;
}
// sort skills into assending order
ksort($skills);
// open a file
$xl = fopen('excelfile.csv', 'w');
// echo title line from the skills array
fputcsv($xl, array_keys($skills));
// build csv line with skills in the correct order
foreach ($months as $date => $m){
// build array in correct sorted order
$t = [];
$t[] = $date;
foreach ($skills as $skill => $x) {
if ( $skill != $firstTitle) $t[] = $m[$skill];
}
fputcsv($xl,$t);
}
RESULT
Month,skill5,skill6
06/2020,202,563.5
07/2020,522.5,140.5

You're basically just aggregating the skill values by month and then outputting those aggregated values. This is not difficult, but you've got to be clear about what you're doing. One common reason I see new players get confused is that they're trying to use the most compact code possible, which makes it hard to keep track of what's happening. Be verbose, name things clearly, and comment your code relentlessly. You'll have a much easier time seeing why something isn't working, and your code will be much more maintainable. Write your code like someone else is going to be maintaining it. That someone else may be you in five years.
<?php
$dataArray = [
['months' => '06/2020', 'hours' => '202', 'skill' => '5'],
['months' => '06/2020', 'hours' => '563.5', 'skill' => '6'],
['months' => '06/2020', 'hours' => '303.7', 'skill' => '6'],
['months' => '08/2020', 'hours' => '123.5', 'skill' => '8'],
['months' => '07/2020', 'hours' => '140.5', 'skill' => '6'],
['months' => '07/2020', 'hours' => '522.5', 'skill' => '5'],
['months' => '08/2020', 'hours' => '123.5', 'skill' => '6']
];
/*
* Break out your formatting into functions so that it's re-usable and doesn't clutter up your logic
*/
function formatHours($hourString)
{
$hourString = str_replace(',', '', $hourString);
return floatval($hourString);
}
function buildSkillKey($skillValue)
{
return 'Skill '.$skillValue;
}
// Set up buffers for our skills and month values
$skills = [];
$buffer = [];
foreach($dataArray as $currRow)
{
//Format the hour value
$currHours = formatHours($currRow['hours']);
//Create key for the skill.
$skillKey = buildSkillKey($currRow['skill']);
/*
* Add the skill to the skill buffer. Using the value as the key is an easy way to both prevent duplicates
* without having to implement any logic, and have automatic alpha sorting
*/
$skills[$skillKey] = $skillKey;
// Set up an array for the month value if we don't have one already
if(!array_key_exists($currRow['months'], $buffer))
{
$buffer[$currRow['months']] = [];
}
/*
* If you don't have multiple month/skill entries that you need to aggregate, remove this condition
* and simply set the value in the buffer rather than adding with +=
*/
if(!array_key_exists($skillKey, $buffer[$currRow['months']]))
{
$buffer[$currRow['months']][$skillKey] = 0;
}
$buffer[$currRow['months']][$skillKey] += $currHours;
}
// Define a string for the months column header
$monthColumnTitle = '';
// Create the header row by combining the month header and the skills buffer
$header = array_merge([$monthColumnTitle], $skills);
// Open an output handle and send the header
$outputHandle = fopen("skills.csv", "w");
fputcsv($outputHandle, $header);
// Spin through the buffer
foreach($buffer as $currMonth=>$currSkillValues)
{
// Initialize an output array with the month in the first position
$currOutput = [$currMonth];
// Iterate through the skill buffer
foreach($skills as $currSkillLabel)
{
/*
* If we have a value for this skill, add it to the output row, otherwise insert an empty string.
*
* If you prefer to send zeros rather than empty strings, you can just set the field value to
* $currSkillValues[$currSkillLabel], since we initialized all skills with zeroes when building
* the value buffer.
*/
$currFieldValue = (!empty($currSkillValues[$currSkillLabel])) ? $currSkillValues[$currSkillLabel]:'';
$currOutput[] = $currFieldValue;
}
// Send the row
fputcsv($outputHandle, $currOutput);
}

Related

get the least Frequent item from array of objects

I'm trying to output [device_id] of the least frequent [device_ip_isp] from this array.
Also, If the array only has two SharedDevice Object available, and having different [device_ip_isp], it should output the 2nd SharedDevice Object's [device_id]
array (
0 =>
SharedDevice::__set_state(array(
'device_no' => 1,
'device_id' => '82',
'device_ip_isp' => 'Verizon Fios',
)),
1 =>
SharedDevice::__set_state(array(
'device_no' => 2,
'device_id' => '201',
'device_ip_isp' => 'Spectrum',
)),
2 =>
SharedDevice::__set_state(array(
'device_no' => 3,
'device_id' => '312',
'device_ip_isp' => 'Verizon Fios',
)),
3 =>
SharedDevice::__set_state(array(
'device_no' => 4,
'device_id' => '9715',
'device_ip_isp' => 'Verizon Fios',
)),
4 =>
SharedDevice::__set_state(array(
'device_no' => 5,
'device_id' => '11190',
'device_ip_isp' => 'Verizon Fios',
)),
)
The output should be 201 because "Spectrum" is the least frequent.
I tried the following and had issues:
I'm not sure how I can sort the object variables before comparing to find the least frequent.
/*
$user->getUser_devices() will output the array shown above.
*/
leastFrequent($user->getUser_devices(), 5);
function leastFrequent($arr, $n){
// find the min frequency
// using linear traversal
$min_count = $n + 1;
$res = -1;
$curr_count = 1;
for ($i = 1; $i < $n; $i++) {
if ($arr[$i]['device_ip_isp'] == $arr[$i - 1]['device_ip_isp']) {
$curr_count++;
} else {
if ($curr_count < $min_count) {
$min_count = $curr_count;
$res = $arr[$i - 1]['device_id'];
}
$curr_count = 1;
}
}
// If last element is
// least frequent
if ($curr_count < $min_count) {
$min_count = $curr_count;
$res = $arr[$n - 1]['device_id'];
}
return $arr[$n]$res['device_id'];
}
Ok, below is the explanation of the snippet inside out.
array_column to get all the device_ip_isp values in a single array.
array_count_values to get the frequency of each value.
array_reverse to reverse the count frequency array since you need the latest share device ID incase of count collision for a minimum count value.
min to get the lowest value among all the frequency counts.
array_search to get the key of the first frequency min element.
In the end, we reverse the input array to immediate return the device IP the moment we find the key from the above step matching the current device_ip_isp.
Snippet:
<?php
function getLeastFrequentElement($arr){
$freqs = array_reverse(array_count_values(array_column($arr, 'device_ip_isp')));
$deviceIPISP = array_search(min($freqs), $freqs);
foreach(array_reverse($arr) as $sharedDevice){
if($sharedDevice->device_ip_isp === $deviceIPISP){
return $sharedDevice->device_id;
}
}
throw new Exception("No shared object found!");
}
echo getLeastFrequentElement($arr);
Online Demo

Searching for a value in an array using PHP

I have a list of users in my mongodb database, which can then follow each other -pretty standard. Using php I want to check if a specific user is following another user. My data looks like this.
array (
'_id' => ObjectId("56759157e1095db549d63af1"),
'username' => 'Joe',
'following' =>
array (
0 =>
array (
'username' => 'John',
),
1 =>
array (
'username' => 'Tom',
),
),
)
array (
'_id' => ObjectId("56759132e1095de042d63af4"),
'username' => 'Tom',
'following' =>
array (
0 =>
array (
'username' => 'Joe',
),
1 =>
array (
'username' => 'John',
),
2 =>
array (
'username' => 'Sam',
),
),
)
I want a query that will check if Joe is following Sam (which he's not) - so it would produce no results. However, if I was to query the database to check if Tom was following Sam, then it would produce a result to indicate he is (because he is). Do you know how I would do this in PHP? I've experimented with Foreach loops, but I haven't been able to get the results I want.
Make it by DB query, by php it will take more resources
Still if you want by php you can make it so
$following=false;
foreach($data as $v) if ($v['username'] == 'Joe') {
foreach($v['following'] as $v1) if (in_array('Sam', $v1)) {
$following=true;
break 2;
}
}
echo $following;
Such queries are best done in SQL, but if you insist on a PHP-based solution I would suggest to turn the data structure into items keyed by name. Once you have that it is a piece of cake to find relationships:
function organisePersons($data) {
$persons = array();
foreach($data as $person) {
$list = array();
foreach($person["following"] as $following) {
$list[$following["username"]] = $following["username"];
}
$person["following"] = $list;
$person["followedBy"] = array();
$persons[$person["username"]] = $person;
}
// link back to get all "followedBy":
// You can skip this block if you don't need "followedBy":
foreach ($persons as $person) {
foreach($person["following"] as $following) {
echo $person["username"] . " f. $following<br>";
if (!isset($persons[$following])) {
$persons[$following] = array(
"_id" => null, // unknown
"username" => $following,
"following" => array(),
"followedBy" => array()
);
}
$persons[$following]["followedBy"][] = $person["username"];
}
}
// return the new structure
return $persons;
}
So first call the function above with the data you have:
$persons = organisePersons($data);
And then you can write this:
if (isset($persons["Joe"]["following"]["Sam"])) {
echo "Joe is following Sam"; // not in output
};
if (isset($persons["Tom"]["following"]["Sam"])) {
echo "Tom is following Sam"; // in output
};
But also:
echo "Tom is following " . implode($persons["Tom"]["following"], ", ");
// Output: Tom is following Joe, John, Sam
And even the reverse question "Tom is followed by who?":
echo "Tom is followed by " . implode($persons["Tom"]["followedBy"], ", ");
// Output: Tom is followed by Joe

How to use array values as keys without loops? [duplicate]

This question already has answers here:
Generate an associative array from an array of rows using one column as keys and another column as values
(3 answers)
Closed 7 months ago.
Looping is time consuming, we all know that. That's exactly something I'm trying to avoid, even though it's on a small scale. Every bit helps. Well, if it's unset of course :)
On to the issue
I've got an array:
array(3) {
'0' => array(2) {
'id' => 1234,
'name' => 'blablabla',
},
'1' => array(2) {
'id' => 1235,
'name' => 'ababkjkj',
},
'2' => array(2) {
'id' => 1236,
'name' => 'xyzxyzxyz',
},
}
What I'm trying to do is to convert this array as follows:
array(3) {
'1234' => 'blablabla',
'1235' => 'asdkjrker',
'1236' => 'xyzxyzxyz',
}
I guess this aint a hard thing to do but my mind is busted right now and I can't think of anything except for looping to get this done.
Simply use array_combine along with the array_column as
array_combine(array_column($array,'id'), array_column($array,'name'));
Or you can simply use array_walk if you have PHP < 5.5 as
$result = array();
array_walk($array, function($v)use(&$result) {
$result[$v['id']] = $v['name'];
});
Edited:
For future user who has PHP > 5.5 can simply use array_column as
array_column($array,'name','id');
Fiddle(array_walk)
UPD: Warning the slowest solution! See benchmarks below.
Try this code:
$a = array(array('id' => 1234,
'name' => 'blablabla'),
array('id' => 1235,
'name' => 'ababkjkj'),
array('id' => 1236,
'name' => 'xyzxyzxyz'));
var_export(array_reduce($a, function($res, $item) {
$res[$item['id']] = $item['name'];
return $res;
}));
Works fine even in PHP 5.3. And uses only one function array_reduce.
UPD:
Here are some benchmarks (PHP 5.6 over Debian 7 on a medium quality server):
$a = [];
for ($i = 0; $i < 150000; $i++) {
$a[$i] = ['id' => $i,
'name' => str_shuffle('abcde') . str_shuffle('01234')];
}
$start = microtime(true);
if (false) {
// 7.7489550113678 secs for 15 000 itmes
$r = array_reduce($a, function($res, $item) {
$res[$item['id']] = $item['name'];
return $res;
});
}
if (false) {
// 0.096649885177612 secs for 150 000 items
$r = array_combine(array_column($a, 'id'),
array_column($a, 'name'));
}
if (true) {
// 0.066264867782593 secs for 150 000 items
$r = [];
foreach ($a as $subarray) {
$r[$subarray['id']] = $subarray['name'];
}
}
if (false) {
// 0.32427287101746 secs for 150 000 items
$r = [];
array_walk($a, function($v) use (&$r) {
$r[$v['id']] = $v['name'];
});
}
echo (microtime(true) - $start) . ' secs' . PHP_EOL;
So, as a conclusion: plain iteration with simple for loop is a winner (as mentioned in this answer). On a second place there is array_combine allowed only for new versions of PHP. And the worst case is using my own solution with closure and array_reduce.
If you have php >= 5.5:
$res = array_combine(array_column($source, 'id'), array_column($source, 'name'));
If not - make a loop.
Make use of array_map function, (PHP 4 >= 4.0.6, PHP 5)
[akshay#localhost tmp]$ cat test.php
<?php
$array = array(
array('id' => 1234,'name' => 'blablabla'),
array('id' => 1235,'name' => 'ababkjkj'),
array('id' => 1236,'name' => 'xyzxyzxyz')
);
$output = array();
array_map(function($_) use (&$output){ $output[$_['id']] = $_['name']; },$array);
// Input
print_r($array);
// Output
print_r($output);
?>
Output
[akshay#localhost tmp]$ php test.php
Array
(
[0] => Array
(
[id] => 1234
[name] => blablabla
)
[1] => Array
(
[id] => 1235
[name] => ababkjkj
)
[2] => Array
(
[id] => 1236
[name] => xyzxyzxyz
)
)
Array
(
[1234] => blablabla
[1235] => ababkjkj
[1236] => xyzxyzxyz
)
This is the fastest and simplest code here so far ...
$result = [];
foreach ($input as $subarray) {
$result[$subarray["id"]] = $subarray["name"];
}
Try this function:
array_combine(array_column($array,'id'), array_column($array,'name'));

Multidimensional Array modify values

I'd changed, the data in this line.
$fileConv = $_GET['file']; // It retrieves data to add.
$file = intval($fileConv); // Conversion
$ArrayNotes = array (
0 => array ('titre' => 'aaa','ref' => 'aaa','date' => 'aaa','like' => aaa,'url' => 'aaa'),
1 => array ('titre' => 'aaa1','ref' => 'aaa1','date' => 'aaa1','like' => aaa1,'url' => 'aaa1') // my array
);
$like = $ArrayNotes[$file]['like'] + 1; // The only data that changes.
$donnee = array("titre" => $ArrayNotes[$file]['titre'], "ref" => $ArrayNotes[$file]['ref'],"date" => $ArrayNotes[$file]['date'], "like" => $like, "url" => $ArrayNotes[$file]['url']); // Change Data
As stated, I want to know how to change this data directly.
Example : To add a new line :
array_push($ArrayNotes, $donnee);
$var_str = var_export($ArrayNotes, true);
$var = "<?php\n\n\$ArrayNotes = $var_str;\n\n?>";
file_put_contents('content.php', $var);
Thanks you for your help.
Should be as easy as the following:
$ArrayNotes[$file]['like'] = 1234;
And if you just wish to add one to the existing field, you may do:
$ArrayNotes[$file]['like'] += 1;

Using a loop to perform multiple SQL inserts using Codeigniter/Activerecord

I have an array that looking like this:
Array
(
[currency] => GBP
[shipping] => 3.5
[tax] => 0
[taxRate] => 0
[itemCount] => 3
[item_name_1] => Proletarian Sparrow
[item_quantity_1] => 2
[item_price_1] => 75
[item_name_2] => Guillemot Colony
[item_quantity_2] => 5
[item_price_2] => 20
[item_name_3] => Dandelion Clock
[item_quantity_3] => 2
[item_price_3] => 50
)
I'm trying to use a loop to extract the individual item details and insert a row in a database for each one. I'm using Codeigniter.
My model looks like this:
public function set_order($cust_id, $order_data)
{
// Breaks up order information and creates a new row in the pn_orders tables for each item
// Get the last row in the orders table so we can work out the next order_id
$this->db->order_by('order_id', 'desc');
$this->db->limit(1);
$query = $this->db->get('pn_orders');
$row = $query->row_array();
// If the resulting array is empty, there are no orders so we can set the order_id to 1. If there is already an order_id, just add 1 to it.
if (empty($row)) {
$order_id = 1;
} else {
$order_id = $row['order_id'] + 1;
}
//echo "<pre>";
//print_r($order_data);
//echo "</pre>";
// The delivery message input has a placeholder. if the user's input is different to this, assign it to $delivery_message.
if ($this->input->post('delivery_message') == "e.g. if out, leave in porch") {
$delivery_message = NULL;
} else {
$delivery_message = $this->input->post('delivery_message');
}
// Set today's date for insertion into the DB
$date = date('Y-m-d');
// The order information is clumped together in a single array. We have to split out single items by looping through the array before inserting them into the DB.
for ($i = 1; $i <= $order_data['itemCount']; $i++) {
$item = array(
'order_id' => $order_id,
'cust_id' => $cust_id,
'date_ordered' => $date,
'item_name' => $order_data["item_name_{$i}"],
'item_quantity' => $order_data["item_quantity_{$i}"],
'item_price' => $order_data["item_price_{$i}"],
'payment_method' => $_POST['payment_method'],
'delivery_message' => $delivery_message
);
$this->db->insert('pn_orders', $item);
}
}
Everything seems to be in place, however, only 1 row is ever inserted and I can't work out why. It seems very simple.
Is it something to do with the Activerecord pattern?
Thanks.
First print out the array to see if the array structure is correct or not. If its Okay then just use insert_batch like this:
for ($i = 1; $i <= $order_data['itemCount']; $i++) {
$items[] = array(
'order_id' => $order_id,
'cust_id' => $cust_id,
'date_ordered' => $date,
'item_name' => $order_data["item_name_{$i}"],
'item_quantity' => $order_data["item_quantity_{$i}"],
'item_price' => $order_data["item_price_{$i}"],
'payment_method' => $_POST['payment_method'],
'delivery_message' => $delivery_message
);
}
//echo "<pre>";print_r($item);echo "</pre>";die; uncomment to see the array structure
$this->db->insert_batch('pn_orders', $items);

Categories