Parsing Text File - php

I need an advice.
I need to scrap and parse text file (using for currency exchange rates). Here we are, a small snippet from this file:
c057z110323
h057z110323
a057z110323
b012z110323
c058z110324
h058z110324
a058z110324
c059z110325
h059z110325
a059z110325
c060z110328
h060z110328
a060z110328
c061z110329
h061z110329
a061z110329
c062z110330
h062z110330
a062z110330
b013z110330
c063z110331
h063z110331
a063z110331
c064z110401
h064z110401
a064z110401
c065z110404
h065z110404
a065z110404
c066z110405
h066z110405
a066z110405
c067z110406
h067z110406
a067z110406
b014z110406
c068z110407
h068z110407
a068z110407
c069z110408
h069z110408
a069z110408
As you may see there's a lot of lines (in original file there are about 80000 of lines (few lines per day are being added).
String format is as following:
A000112233
where
A - type
000 - number of the file (created this year)
11 - year
22 - month
33 - day
I'm getting 25 latest lines from from the file using following snippet:
$file = "http://www.nbp.pl/kursy/xml/dir.txt";
$data = file($file);
$count = count($data);
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$latest[] = $data[$i];
}
}
I need to get only lines starting with "a". The output array looks as following:
array(8) {
[0]=>
string(13) "a062z110330
"
[1]=>
string(13) "a063z110331
"
[2]=>
string(13) "a064z110401
"
[3]=>
string(13) "a065z110404
"
[4]=>
string(13) "a066z110405
"
[5]=>
string(13) "a067z110406
"
[6]=>
string(13) "a068z110407
"
[7]=>
string(13) "a069z110408
"
}
Now I need to compare every array element to get the latest item from the latest working day before current date. I'm acheiving it this way:
$i = 1;
foreach($latest as $row)
{
$plural = ($i > 1) ? 's' : null;
if( substr(trim($row), -6) === date("ymd", strtotime("-" . $i . " day" . $plural) )
{
$filename = $row;
break;
}
$i++;
}
It's working quite OK, however I'm facing one big problem. I'm unable to sort $latest array by the latest six characters. I tried doing this using sort(), rsort(). None of them worked good for me.
Can anybody help me with this issue or maybe has better approach to do what I'm looking for.

When you do
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$latest[] = $data[$i];
}
}
use date as a key in $latest array:
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$key = (int) substr($data[$i], -6);
$latest[$key] = $data[$i];
}
}
Then you can sort by key like:
ksort($latest);

You need to use a custom sort method. You can use usort to write your own comparison function : http://php.net/manual/en/function.usort.php
From the manual
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
$a = array(3, 2, 5, 6, 1);
usort($a, "cmp");
The comparison function must return an
integer less than, equal to, or
greater than zero if the first
argument is considered to be
respectively less than, equal to, or
greater than the second.

Since you're only asking how to sort an array of strings by their last six characters:
Use usort:
function sortfunc($a, $b) {
return strcmp(substr($a, -6), substr($b, -6));
}
usort($latest, 'sortfunc');
You may need to trim() your lines first or the newlines and/or carriage return characters will be part of the last 6 characters.

Related

Calculate opening times with a part of closed time

This is what is given to me:
$openinghours = ['09:00-18:00'];
Now there can be multiple pieces of "closed time", for example:
$closed[] = '11:30-12:15';
$closed[] = '16:00-16:30';
I need to get the new opening times, like so:
$openinghours = ['09:00-11:30,'12:15-16:00','16-30-18:00'];
So it now has the gaps of the closed time in it.
Where do I start? I'm quite at loss on how to calculate this and get the expected result.
By exploding all time ranges on hyphens, you can manually piece together the open/close times.
My solution may or may not be robust enough for your project data. My solution performs no validation, doesn't sort the closed spans, and doesn't check if a span matches an open/close time OR exceeds the open/close time. My snippet is relying heavily on trustworthy data.
Code: (Demo)
$openinghours = ['09:00-18:00'];
$closed[] = '11:30-12:15';
$closed[] = '16:00-16:30';
[$start, $end] = explode('-', $openinghours[0]);
foreach ($closed as $span) {
[$shut, $reopen] = explode('-', $span);
$result[] = $start . '-' . $shut;
$start = $reopen;
}
$result[] = $start . '-' . $end;
var_export($result);
Output:
array (
0 => '09:00-11:30',
1 => '12:15-16:00',
2 => '16:30-18:00',
)
If your php version doesn't support array destructuring, you can call list() with explode().
This might not be the quickest way to achieve this, but you can see it working here.
$openingHours = ['09:00-18:00'];
$closedHours = ['11:30-12:15', '16:00-16:30'];
$openStart = explode("-", $openingHours[0])[0]; # 09:00
$openFinish = explode("-", $openingHours[0])[1]; # 18:00
$hours = [];
foreach($closedHours as $closed)
$hours[] = explode('-', $closed);
$dynamicHours = ["{$openStart}-"];
for($i = 0; $i <= count($hours) -1; $i++) {
$dynamicHours[$i] .= $hours[$i][0];
$dynamicHours[] = "{$hours[$i][1]}-";
}
$dynamicHours[count($dynamicHours) -1] .= $openFinish;
var_dump($dynamicHours);
This gives you an output of
array(3) {
[0]=>
string(11) "09:00-11:30"
[1]=>
string(11) "12:15-16:00"
[2]=>
string(11) "16:30-18:00"
}

Normalize array values in natural order PHP

I have faced with the problem, I need to normalize/sort in natural order values in array after some item has been removed.
Consider following example. Initial array
{ [313]=> int(2) [303]=> int(1) [295]=> int(3) [290]=> int(4) }
Sorted array
{ [303]=> int(1) [313]=> int(2) [295]=> int(3) [290]=> int(4) }
Consider case when we are removing first item, array should look like this now
{ [313]=> int(1) [295]=> int(2) [290]=> int(3) }
In case of item inside the array range for example 295 (3) it should be
{ [303]=> int(1) [313]=> int(2) [290]=> int(3) }
I hope you get an idea.
But my function doesn't do this correctly.
I've implemented part of this sorting, here is the code, but maybe there are other ways to do this easier ?
const MIN_VALUE = 1;
public function sort_items(&$items_map)
{
if (!empty($items_map)) {
asort($items_map);
var_dump($items_map);
$first_item = reset($items_map);
if ($first_item > self::MIN_VALUE) {
$normalize_delta = $first_item - self::MIN_VALUE;
$prev_item_id = null;
foreach ($items_map as $id => $part) {
$items_map[$id] = $part - $normalize_delta;
if (!empty($prev_item_id)) {
$difference = $items_map[$id] - $items_map[$prev_item_id];
if ($difference > 1) {
$items_map[$id] = $items_map[$id] - ($difference - 1);
}
}
$prev_item_id = $id;
}
}
}
return $items_map;
}
I would be grateful for any help.
Thanks
UPDATE
To clarify.
I want items not to be just sorted in the correct order, but to be in natural order, for example
Sequence 1,3,5,6,7,9 should be transformed into 1,2,3,4,5,6 but keeping keys the same.
2,3,7,9 => 1,2,3,4
Please see my example above with real word case.
If you need to use a custom sort algorithm, use usort to do so. From PhP the documentation :
The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
So you just need to provide those integers if you are in case an item is "greater" or "lower", and usort will do the job for you.
In your case, it could lead to this function :
<?php
function sort_items_map($a, $b)
{
$value = 0;
if( $a < $b )
{
$value = -1;
}
else if( $a > $b )
{
$value = 1;
}
else if( $a == $b )
{
$value = 0;
}
return $value;
}
$items_map = [1, 3, 1, 7]; // or fill it with your own values
usort($items_map, "sort_items_map");
?>

PHP break string into two parts

Note: I can't use break or next line functions as i am using FPDF
I am having a problem with php strings. I am having a string where i want to show atmost 12 characters in first row and remaining in second row. So basically i want to break string into two parts and assign to two variables so that i can print those two variables. I have tried following code :-
if($length > 12)
{
$first400 = substr($info['business_name'], 0, 12);
$theRest = substr($info['business_name'], 11);
$this->Cell(140,22,strtoupper($first400));
$this->Ln();
$this->Cell(140,22,strtoupper($theRest));
$this->Ln();
}
But using this I am getting as shown below :
Original String : The Best Hotel Ever
Output :
The Best Hot
Tel Ever
It is breaking a word, i don't want to break the word, just check the length and if within 12 characters all the words are complete then print next word in next line. Like this :
Desired OutPut:
The Best
Hotel Ever
Any suggestions ?
I see no built-in function to do it, however you could explode on spaces, and re-build your string until the length with the next words get over 12, everything else going to the second part :
$string = 'The Best Hotel Ever';
$exp = explode(' ', $string);
if (strlen($exp[0]) < 12) {
$tmp = $exp[0];
$i = 1;
while (strlen($tmp . ' ' . $exp[$i]) < 12) {
$tmp .= " " . $exp[$i];
$i++;
}
$array[0] = $tmp;
while (isset($exp[$i])) {
$array[1] .= ' ' . $exp[$i];
$i++;
}
$array[1] = trim($array[1]);
} else {
$array[0] = '';
$array[1] = trim(implode (' ', $exp));
}
var_dump($array);
// Output : array(2) { [0]=> string(8) "The Best" [1]=> string(10) "Hotel Ever" }
// $string1 = 'The';
// array(2) { [0]=> string(3) "The" [1]=> string(0) "" }
// $string2 = 'Thebesthotelever';
// array(2) { [0]=> string(0) "" [1]=> string(16) "Thebesthotelever" }
Im not too crash hot on PHP but it seems to be a simple case of which element of the string you are accessing is futher across from where you want to be:
Try:
if($length > 12)
{
$first400 = substr($info['business_name'], 0, 8);
$theRest = substr($info['business_name'], 11);
$this->Cell(140,22,strtoupper($first400));
$this->Ln();
$this->Cell(140,22,strtoupper($theRest));
$this->Ln();
}
For further help check out because you need to remember to count from zero up:
http://php.net/manual/en/function.substr.php

How to get list of decimal numbers "hidden" in binary number in PHP?

firstly, I very much apologize for my poorly written title of this question. So please someone with native English, change the title appropriately. My question is rather simple, it follows:
I am using integer to store multiple types of one item. For example:
TYPE A = 1
TYPE B = 2
TYPE C = 4
TYPE D = 8
TYPE E = 16
etc...
Now the item in DB has type value 14, that means that it has been assigned to TYPE B+C+D. If it would have type value for example 9, it would mean it has been assigned to TYPE A+D.
I need a function which I would supply with single type integer and this function would return array of integer types.
I could iterate through all the integers and compare them with the number, but that's what I am using now, but I am looking for some more effective way, if it exists ?
Thanks in advance for your help.
Here's a function without any loops (mostly done for the fun of it :) ):
function getTypes($int)
{
$types = array('Type A','Type B','Type C','Type D','Type E');//defining types
$t = array_reverse(str_split(decbin($int)));//converting $int to an array of bits
$types = array_slice($types,0,ceil(log($int,2)));//slicing the array to the same size as number of bits in the $int
$t = array_combine($types,$t);// Creating a new array where types are keys and bits are values
return array_keys($t,1);// returning an array of keys which have a value of 1
}
However it doesn't mean that it is efficient. If you use a bitmask you better of checking values using bitwise operators like bitwise and (&).
For example if you want to check if your integer contains Type D and Type E you should do
if ($integer & 8 & 16)
To check for each individual type I would us a loop with bitshift operator
function getTypes($int)
{
$result = array();
$types = array('Type A','Type B','Type C','Type D','Type E');
foreach($types as $type)
{
if ($int & 1)//checking if last bit is 1 (exactly the same as if($int%2) )
$result[]=$type;
$int>>=1;//shifting integer's bits to the right (exactly the same as $int = $int / 2)
}
return $result;
}
How's this? http://codepad.org/AzgdPsL1
To explain what's going on:
I create a $types array of all the valid types that exist between the range of 1 to $max_bit bits.
Looping while the number is greater than 0, it gets bitwise ANDed with 1. If it turns out that evaluates to true, this means that the LSB is set, so the type at the head of the $type array applies to this number. The current type is added to the return array.
The number is then shifted to the right by one bit.
<?php
function num2type( $num)
{
$max_bit = 5;
$types = array_combine( range( 1, $max_bit), range( ord( 'A'), ord( 'A') + $max_bit - 1));
$return = array();
while( $num > 0)
{
$current_type = array_shift( $types);
if( $num & 0x1)
{
$return[] = chr( $current_type);
}
$num = $num >> 1;
}
return $return;
}
var_dump( num2type( 8)); // array(1) { [0]=> string(1) "D" }
var_dump( num2type( 31));
var_dump( num2type( 14));
Output (for 31):
array(5) {
[0]=>
string(1) "A"
[1]=>
string(1) "B"
[2]=>
string(1) "C"
[3]=>
string(1) "D"
[4]=>
string(1) "E"
}
Output for 14:
array(3) {
[0]=>
string(1) "B"
[1]=>
string(1) "C"
[2]=>
string(1) "D"
}
function check_flag($field, $bit)
{
return (($field | $bit) === $field) ? TRUE : FALSE;
}
$types = array('A' => 1, 'B' => 2, 'C' => 4, 'D' => 8, 'E' => 16);
$db_value = 14;
var_dump(check_flag($db_value, $types['B']));
... just make sure that you cast the value fetched from the database to integer.
Edit: Now that I read you need all of the types that are set, here's some more logic:
$set = array();
foreach ($types as $key => $value)
if (check_flag($db_value, $value)) $set[] = $key;
$a = 10;
$scan = 1;
$result = array();
while ($a >= $scan){
if ($a & $scan)
$result[] = $scan;
$scan<<=1; //just a bit shift
}
var_dump($result);

Random But Unique Pairings, with Conditions

I need some help/direction in setting up a PHP script to randomly pair up items in an array.
The items should be randomly paired up each time.
The items should not match themselves ( item1-1 should not pair up with item1-1 )
Most of the items have a mate (ie. item1-1 and item1-2). The items should not be paired with their mate.
I've been playing around with the second script in this post but, I haven't been able to make any progress. Any help is appreciated.
Very simple approach, but hopefully helpful to you:
(mates, if grouped in an array (e.g. array('a1', 'a2')), will not be paired.)
function matchUp($array) {
$result = array();
while($el = array_pop($array)) {
shuffle($array);
if (sizeof($array) > 0) {
$candidate = array_pop($array);
$result[] = array(
array_pop($el),
array_pop($candidate)
);
if (sizeof($el) > 0) {
$array[] = $el;
}
if (sizeof($candidate) > 0) {
$array[] = $candidate;
}
}
else {
$result[] = array(array_pop($el));
}
}
return $result;
}
$array = array(
array('a1', 'a2'),
array('b1', 'b2'),
array('c1'),
array('d1'),
array('e1', 'e2'),
array('f1'),
array('g1', 'g2'),
);
Update:
foreach(matchUp($array) as $pair) {
list($a, $b) = $pair + array(null, null);
echo '<div style="border: solid 1px #000000;">' . $a . ' + ' . $b . '</div>';
}
With the randomness, there is no guarantee that a full correct solution will be reached.
Certain problem sets are more likely to be solved than others. Some will be impossible.
You can configure how many times it will try to achieve a good solution. After the specified number of tries it will return the best solution it could find.
function pairUp (array $subjectArray) {
// Config options
$tries = 50;
// Variables
$bestPaired = array();
$bestUnpaired = array();
for($try = 1; $try <= 50; $try++) {
$paired = array();
$unpaired = array();
$toBePaired = $subjectArray;
foreach($subjectArray as $subjectIndex => $subjectValue) {
// Create array without $thisValue anywhere, from the unpaired items
$cleanArray = array();
foreach($toBePaired as $index => $value) {
if($value != $subjectValue) {
array_push($cleanArray, array(
'index' => $index,
'value' => $value
));
}
}
sort($cleanArray); // reset indexes in array
// See if we have any different values left to match
if(count($cleanArray) == 0) {
array_push($unpaired, $subjectValue);
continue;
}
// Get a random item from the clean array
$randomIndex = rand(0,count($cleanArray)-1);
// Store this pair
$paired[$subjectIndex] = $subjectValue . '-' . $cleanArray[$randomIndex]['value'];
// This item has been paired, remove it from unpairedItems
unset($toBePaired[$cleanArray[$randomIndex]['index']]);
sort($toBePaired);
}
// Decide if this is our best try
if(count($paired) > count($bestPaired)) {
$bestPaired = $paired;
$bestUnpaired = $unpaired;
}
// If we had no failures, this was a perfect try - finish
if(count($unpaired) == 0) { $break; }
}
// We're done, send our array of pairs back.
return array(
'paired' => $bestPaired,
'unpaired' => $bestUnpaired
);
}
var_dump(pairUp(array('a','b','c','d','e','a','b','c','d','e')));
/*
Example output:
array(2) {
["paired"]=>
array(10) {
[0]=>
string(3) "a-b"
[1]=>
string(3) "b-c"
[2]=>
string(3) "c-d"
[3]=>
string(3) "d-e"
[4]=>
string(3) "e-a"
[5]=>
string(3) "a-b"
[6]=>
string(3) "b-e"
[7]=>
string(3) "c-d"
[8]=>
string(3) "d-c"
[9]=>
string(3) "e-a"
}
["unpaired"]=>
array(0) {
}
}
*/
Case 1: if all elements had a mate
If all elements had a mate, the following solution would work, although I don't know if it would be perfectly random (as in, all possible outputs having the same probability):
Shuffle the list of elements, keeping mates together
original list = (a1,a2),(b1,b2),(c1,c2),(d1,d2)
shuffled = (c1,c2),(d1,d2),(a1,a2),(b1,b2)
Shift the second mate to the right. The matches have been formed.
shifted = (c1,b2),(d1,c2),(a1,d2),(b1,a2)
(Edit1: if applied exactly as described, there is no way a1 ends up matched with b1. So, before shifting, you may want to throw a coin for each pair of mates to decide whether they should change their order or not.)
Case 2: if only some elements have a mate
Since in your question only some elements will have a mate, I guess one could come up with the following:
Arbitrarily pair up those elements who don't have a mate. There should be an even number of such elements. Otherwise, the total number of elements would be odd, so no matching could be done in the first place.
original list = (a1,a2),(b1,b2),c1,d1,e1,f1 // c1,d1,e1 and f1 don't have mates
list2 = (a1,a2),(b1,b2),(c1,d1),(e1,f1) // pair them up
Shuffle and shift as in case 1 to form the matches.
shuffled = (e1,f1),(a1,a2),(c1,d1),(b1,b2)
shifted = (e1,b2),(a1,f1),(c1,a2),(b1,d1)
Again, I don't know if this is perfectly random, but I think it should work.
(Edit2: simplified the solution)
(Edit3: if the total number of elements is odd, someone will be left without a match, so pick an element randomly at the beginning to leave it out and then apply the algorithm above).

Categories