Counting unique values in flatfile and updating them to an array - php

What is the most sensible way to count unique names and the last occurence (from the data) in a pipe-separated flatfile, where the data is formed like this:
Artist|YYYY-MM-DD|Location|\n
So when the data is for example (with newline in the end):
The Band|2011-04-01|Club Bulc|
Le Artist|1999-12-24|Reindeer Row|
Soundmakers|2012-03-22|Pub Pizza|
The Band|2010-12-01|Someplace|
Soundmakers|2009-07-07|Faraway|
Soundmakers|2010-08-18|ShowEr|
And the desired format would be:
Artist|Times listed|Latest year
How would you loop through the flatfile to get the following data into a second flatfile:
Le Artist|1|1999|
Soundmakers|3|2012|
The Band|2|2011|
At first I thought it would be quite a simple undertaking, but it turned out a bit more challenging!
I have one implementation that half-works (some items are written twice, and sometimes the year is in the wrong line!), so I would like to see if there are some better methods for accomplishing this.
I've tried both an OOP and procedural approach for this, and so far I've gotten better results with procedural. But for me the method really doesn't matter, as this is for my personal learning (and use).
Update:
The file is roughly 1 MB and will be far less than 10 MB in any foreseeable future.

Seems pretty simple indeed. You'll want the results to be stored an array like this:
$bands = array(
'Le Artist' => array('count' => 1, 'year' => 1999),
...
);
You can use fgetcsv to read in the lines and break them apart at the same time:
$line = fgetcsv($fh, 0, '|');
You can then check whether you already have this artist and increment the count:
$artist = $line[0];
if (!isset($bands[$artist])) {
$bands[$artist] = array('count' => 0, 'year' => null);
}
$bands[$artist]['count']++;
For the year, just explode $line[1] to extract the year, check if it's greater than $bands[$artist]['year'] and replace it if so.

First, make an array with artist name as key, and a list of years as values:
$grouped = array();
while (!feof($fd) && $line = fgets($fd)) {
list($artist, $date) = explode('|', $line);
list($year) = explode('-', $date);
$grouped[$artist][] = $year;
}
Then output the result:
foreach ($grouped as $artist => $years) {
printf("%s|%s|%s|\n", $artist, count($years), max($years));
}

Here is the version I ended up using, and it works as I hoped for:
<?php
$file = 'seen.txt';
$shows = array();
$sourceData = file($file);
foreach($sourceData as $row) {
list($date, $artist, $venue, $city, $country) = explode('|', $row);
$year = date('Y', strtotime($date));
if(!isset($shows[$artist])) {
$shows[$artist] = array('count' => 1, 'year' => $year);
} else {
$shows[$artist]['count']++;
if($shows[$artist]['year'] < $year) {
$shows[$artist]['year'] = $year;
}
}
}
echo '<h1>Bands and Shows</h1>';
ksort($shows);
foreach($shows as $band => $data) {
echo 'I have seen <b>',$band,'</b> perform ',$data['count'],' times, most recently in ',$data['year'],'<br/>';
}
?>

Related

combine array entries with every other entry

Sorry for the title as it looks like most of the other questions about combining arrays, but I don't know how to write it more specific.
I need a PHP function, which combines the entries of one array (dynamic size from 1 to any) to strings in every possible combination.
Here is an example with 4 entries:
$input = array('e1','e2','e3','e4);
This should be the result:
$result = array(
0 => 'e1',
1 => 'e1-e2',
2 => 'e1-e2-e3',
3 => 'e1-e2-e3-e4',
4 => 'e1-e2-e4',
5 => 'e1-e3',
6 => 'e1-e3-e4',
7 => 'e1-e4'
8 => 'e2',
9 => 'e2-e3',
10 => 'e2-e3-e4',
11 => 'e2-e4',
12 => 'e3',
13 => 'e3-e4',
14 => 'e4'
);
The sorting of the input array is relevant as it affects the output.
And as you see, there should be an result like e1-e2 but no e2-e1.
It seems really complicated, as the input array could have any count of entries.
I don't even know if there is a mathematical construct or a name which describes such a case.
Has anybody done this before?
You are saying that there might be any number of entries in the array so I'm assuming that you aren't manually inserting the data and there would be some source or code entering the data. Can you describe that? It might be easier to directly store it as per your requirement than having an array and then changing it as per your requirement
This might be helpful Finding the subsets of an array in PHP
I have managed to bodge together a code that creates the output you want from the input you have.
I think I have understood the logic of when and why each item looks the way it deos. But Im not sure, so test it carefully before using it live.
I have a hard time explaining the code since it's really a bodge.
But I use array_slice to grab the values needed in the strings, and implode to add the - between the values.
$in = array('e1','e2','e3','e4');
//$new =[];
$count = count($in);
Foreach($in as $key => $val){
$new[] = $val; // add first value
// loop through in to greate the long incrementing string
For($i=$key; $i<=$count-$key;$i++){
if($key != 0){
$new[] = implode("-",array_slice($in,$key,$i));
}else{
if($i - $key>1) $new[] = implode("-",array_slice($in,$key,$i));
}
}
// all but second to last except if iteration has come to far
if($count-2-$key >1) $new[] = Implode("-",Array_slice($in,$key,$count-2)). "-". $in[$count-1];
// $key (skip one) next one. except if iteration has come to far
If($count-2-$key >1) $new[] = $in[$key] . "-" . $in[$key+2];
// $key (skip one) rest of array except if iteration has come to far
if($count-2-$key > 1) $new[] = $in[$key] ."-". Implode("-",Array_slice($in,$key+2));
// $key and last item, except if iteration has come to far
if($count-1 - $key >1) $new[] = $in[$key] ."-". $in[$count-1];
}
$new = array_unique($new); // remove any duplicates that may have been created
https://3v4l.org/uEfh6
here is a modificated version of Finding the subsets of an array in PHP
function powerSet($in,$minLength = 1) {
$count = count($in);
$keys = array_keys($in);
$members = pow(2,$count);
$combinations = array();
for ($i = 0; $i < $members; $i++) {
$b = sprintf("%0".$count."b",$i);
$out = array();
for ($j = 0; $j < $count; $j++) {
if ($b{$j} == '1') {
$out[] = $keys[$j];
}
}
if (count($out) >= $minLength) {
$combinations[] = $out;
}
}
$result = array();
foreach ($combinations as $combination) {
$values = array();
foreach ($combination as $key) {
$values[$key] = $in[$key];
}
$result[] = implode('-', $values);
}
sort($result);
return $result;
}
This seems to work.

Searching an array of objects for specific key value pairs

After pulling in some data from a mysql database saving it to a variable, I'm wondering if it's possible to "query" the variable instead of doing another request to the database? I realise I need to search an array of objects based on key and value. So here is an example of what I have.
<?php
[{"customer":1,"item":1,"bought_at":"2016-12-15 11:41:11"},
{"customer":2,"item":1,"bought_at":"2016-12-15 11:43:21"},
{"customer":3,"item":1,"bought_at":"2016-12-16 13:31:11"},
{"customer":1,"item":2,"bought_at":"2016-12-16 12:12:21"},
{"customer":1,"item":3,"bought_at":"2016-12-17 15:13:58"}]
?>
So lets say I need to search it based on the item number and the date (but not time) when the item was bought. The next step would be to return the result as another array of objects. So if I were to search for item 1 bought at 2016-12-15 it would return.
[{"customer":1,"item":1,"bought_at":"2016-12-15 11:41:11"},
{"customer":2,"item":1,"bought_at":"2016-12-15 11:41:21"},]
Is this possible? If so how would I go about doing it?
Regards
EDIT: The reason I originally asked this question was because I had a query inside a nested foreach loop which bothered me. It's a piece of code that builds up a a json table at the back-end to pass information to the front end to draw a google line graph. Also I changed the data slightly in my original question to try to make it easier to read. It's also built in Laravel. The complete code is pretty large so I'm just posting the nested foreach loops. The query is in the second loop and is given the variable $activations.
foreach ($timeRange as $time){
$temp = array();
$timeTwentyFour = date("G", strtotime($time));
$temp[] = array('v' => "Date(01,01,2000,$timeTwentyFour)");
foreach($data as $row){
$count = 0;
$activations = DB::table('customer_display')->where('display_id',$row->id)->where(DB::raw('DATE(created_at)'),$day)->get();
foreach($activations as $activation){
$timestamp = $activation->created_at;
$activationTime = explode(" ", $timestamp)[1];
if (strtotime($activationTime) >= strtotime($time) && strtotime($activationTime) < strtotime($time) + 3600){
$count++;
};
}
$temp[] = array('v' => (float) $count);
//The custom tooltip
$temp[] = array('v' => $time . ' ' . $row->location . '. ' . $count . ($count == 1 ? ' Activation' : ' Activations'));
}
$rows[] = array('c' => $temp);
}
If those are objects in an array and you only wanted the entries where item is 1 you could use array_filter;
$filtered = array_filter($items, function($item){
// only return objects where this is true
return $item->item == 1;
});
If you wanted only items purchased on the 15th use
return date('d', strtotime($item->bought_at)) == 15;
and if you want to see items 1 bought on the 15th you'd use
$filtered = array_filter($items, function($item){
return $item->item === 1
&& date('d', strtotime($item->bought_at)) == 15;
});
Also check out this answer on comparing dates for more information on how to better do that.
Another database request will be the better approach in most cases. A database is optimized for querying data. It can use indexes, etc. Well known databases like MySQL have a query optimalisation. Doing it by hand will be less efficient.
First downloading too much data and then use something like array_filter to linearly search through all the data is far less efficient than just querying the data with the search criteria in the query.
One way to do it is:
//Prepare statement once
$statement = $pdo->prepare("SELECT * FROM table WHERE item = ? AND bought_at = ?");
$statement->execute(array(1, "2016-12-15"));
foreach ($statement->fetchAll() as $array)
//Do something with $array
//reuse prepared statement with another selection criteria
$statement->execute(array(3, "2016-12-16"));
foreach ($statement->fetchAll() as $array)
//Do something with $array

How to select randomly from array exluding one value?

With the holidays slowly nearing, It's time to pick straws again. We always pick a piece of paper from a box containing everbody's name. However, this year I wanted to solve the issue of picking your own name from the box by using PHP.
I've got an array with names ex:
$names = [
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
];
For each of these names, I wanna pick 4 random names, so that at the end every person has 4 names for which they will have to buy a present.
The issue
Well, I'm stuck. I've thought of a million ways on how to do this but I just can't come up with anything.
What I've tried so far
Using a foreach
Using array_rand()
Shifting the array
The problem with these is that a person shouldn't be picking their own name and that everyone should be picked an even amount of times (4).
Update
Some great answers already posted. I'm not exactly sure though if this is fair for everyone. Everyone should at the end get 4 presents from 4 different persons.
I was thinking of adding each name to the $names array 4 times and then applying one of your answers. Am I on the right track with this?
Update 2
What I've tried now:
function select_rand($array, $exclude_array) {
$diff = array_diff($array, $exclude_array);
return $diff[array_rand($diff, 1)];
}
$workArray = [
'0' => $names,
'1' => $names,
'2' => $names,
'3' => $names,
];
foreach ($names as $k => $name) {
$i = 0;
$new[$name] = array();
while ($i < 4) {
$value = select_rand($workArray[$i], array_merge($new[$name], array($name)));
if (($key = array_search($value, $workArray[$i])) !== false) {
unset($workArray[$i][$key]);
}
$new[$name][] = $value;
$i++;
}
}
This works only in a few caes.
I would use shuffle, and array_slice for this job:
$names = [
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
];
foreach ($names as $name) {
// working array
$working = $names;
// remove current name from array,no one wants to buy presents for him/her self..
unset($working[$name]);
shuffle($working);
$people = array_slice($working, 0, 4);
echo $name . ' has to buy presents for:';
var_dump($people);
}
You can create custom function like as
$names = [
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
];
$my_name = "John";
$limit = 4;
function get_name($arr,$your_name,$limit){
$key = array_flip($arr)[$your_name];
unset($arr[$key]);
$rand_keys = array_rand($arr,$limit);
$result = array_intersect_key($arr, array_flip($rand_keys));
return implode(',',$result);
}
echo get_name($names,$my_name,$limit);
Demo
This can help -
function select_rand($array, $exclude_array) {
$diff= array_diff($array, $exclude_array);
return $diff[array_rand($diff, 1)];
}
$names = array(
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
);
foreach($names as $name)
{
$i = 0;
$new[$name] = array();
while($i < 4) {
$new[$name][] = select_rand($names, array_merge($new[$name], array($name)));
$i++;
}
}
This will generate a new array for each name (as key) in that array containing 4 unique names.
FIDDLE
Update
$new[$name][] = select_rand($names, array_merge($new[$name], array($name, 'Karin')));
I think if you want to have an even distribution of presents per person, you need to have more control over it. Anyhow, in this code I keep track of number of presents per person. I initialise it with zero: $presents = array_fill_keys($names, 0);. Then after each selection I updated this number $presents[$key]++;.
<?php
$names = array(
'John',
'Jane',
'Joe',
'Matt',
'Steve',
'Anne',
'Karin'
);
$presents_number = 4;
// initialization
$presents = array_fill_keys($names, 0);
$names_names = array();
foreach ($names as $i => $picker) {
$box = $names;
unset($box[$i]);
// filter out the people with maximum presents:
foreach ($presents as $key => $number) {
if (($presents[$key] > $presents_number-1) && in_array($key, $box)) {
$box = array_diff($box, array($key));
}
}
// shuffle the box and select 4 top
shuffle($box);
$selection = array_slice($box, 0, $presents_number);
foreach ($selection as $key) {
$presents[$key]++;
}
$names_names[$picker] = $selection;
}
echo "<pre>";
print_r($names_names);
echo "</pre>";
There can be more to be considered mathematically. Specially since loops can happen, this algorithm can go wrong. I haven't spend time on it. But as an example of 3 people and one present per person. (A, B, C)
correct answer:
A => {B}
B => {C}
C => {A}
wrong answer:
A => {B}
B => {A}
C => {}
Basically, the perfect algorithm should to avoid these wrong answers. Maybe later I fixed the problem as an update for this post.

Given an array, find zero values and replace them with the average of immediately bordering values

I have an array of temperature data by hour. Some hours have zero data instead of a temp. When graphing using Google Charts, the zero causes the line graph to plummet. My temporary fix was to replace the zero values with null, causing a break in the line graph. The ideal solution would be to take the values on either side of the zero, and average them. The array is in order by hour. Help?
$array = array(
"1AM" => "65",
"2AM" => "66",
"3AM" => "68",
"4AM" => "68",
"5AM" => "68",
"6AM" => "0",
"7AM" => "70",
"8AM" => "71",
"9AM" => "71",
"10AM" => "73",
);
Here's my script replacing the 0's with nulls:
$array = array ();
foreach($parsed_json->history->observations as $key => $value) {
$temp = (int)$value->tempi;
if ($temp==0) {
str_replace(0, null, $temp);
}
$hour = $value->date->hour;
$array[$hour] = $temp;
};
This Example would work great if the data was mine, but alas, it's from a JSON feed.
Would I use an array_walk() sort of deal? How would I reference the current place in the array? Any help is appreciated!
I would scratch out the null portion, and just foreach-loop through the final array.
So, change your current code to:
$array = array ();
foreach($parsed_json->history->observations as $key => $value) {
$temp = (int)$value->tempi;
}
$hour = $value->date->hour;
$array[$hour] = $temp;
And add this below it:
foreach($array as $hour => $temp){
if($temp == "0"){
$numHour = $hour[0];
$hourPlus = ($numHour + 1) . "AM";
$hourMinus = ($numHour - 1) . "AM";
$valuePlus = $array[$hourPlus];
$valueMinus = $array[$hourMinus];
$average = ($valuePlus + $valueMinus)/2;
$array[$hour] = $average;
}
}
?>
This of course assumes that the values on either side of the zero are also not zero. You may want to add a check for that somewhere in there.
Tested and proven method.
Couldn't you do something along the lines of:
str_replace(0, ((($key-1)+($key+1))/2), $temp);
Where $key is array position, take the value before 0 and after 0 add them and divide them by 2 to get the average.
I'll let you sort out what happens if first, last or consecutive values are 0.
$the_keys=array_keys($array);
foreach($the_key as $index=>$key)
{
if($array[$key]==0)
{
$array[$key]=($array[$the_key[$index-1]]+$array[$the_key[$index+1]]/2);
}
}

Best performance strategy for converting dates from MySQL format to Excel with PHP

I'm still sort of new to PHP, MySQL (and Zend Framework). I need to convert dates to strings in MM/DD/YYYY format in order to interface with another system.
I have this function that I have pinpointed as the major performance killer of this particular script. When the result set is fairly small (40 or 50 rows), this runs quickly, less than a second. But when there are more records, say 4000, this function takes about 4 minutes. Not good.
What is the best strategy for cases like this to increase performance?
public function convertDatesToExcelFormat() {
$sql = "Select fil_id,
BIRTH_DATE,
WILL_DATE,
LAST_CODICIL_DATE,
TRUST_DATE,
POA_DATE
FROM adds";
$result = $this->getAdapter()->fetchAll($sql);
foreach ($result as $rowset => $row) {
foreach ($row as &$val) {
if (strpos($val, ':')) {
$val = preg_replace('/[0-9]+:[0-9]+:[0-9]+/', '' , $val);
$val = preg_replace('/-/', '/', $val);
$val = substr($val, -6, 5) . '/' . substr($val, 0, 4);
}
}
$data = array(
'BIRTH_DATE' => $row['BIRTH_DATE'],
'WILL_DATE' => $row['WILL_DATE'],
'LAST_CODICIL_DATE' => $row['LAST_CODICIL_DATE'],
'TRUST_DATE' => $row['TRUST_DATE'],
'POA_DATE' => $row['POA_DATE'],
);
$where = $this->getAdapter()->quoteInto('fil_id = ?', $row['fil_id']);
$this->update($data, $where);
}
return $this;
}
php has a very useful function called date_format which takes the form of
$newDateVar = date_format('d-M-Y', $inputDateVar);
The string 'd-M-Y' above can be cast in various formats. Have a look on the php site which gives a full breakdown of the formats available
http://php.net/manual/en/function.date-format.php

Categories