about 20% of time opendir script fails. See example - php

Hit refresh several times and see sometimes I get "null".
This script loops through a folder to get all mp3 files and randomly selects one.
What am I doing wrong? Thanks
if ($handle = opendir('../../hope/upload/php/files/')) {
while (false !== ($entry = readdir($handle))) {
$entry = trim($entry);
if(preg_match('/.mp3/', $entry))
{
$mp3[] = "$entry";
}
}
closedir($handle);
$count = count($mp3);
$rand = rand(0,$count -1); /// FIXED BY adding a -1 after count**
$mp3 = $mp3[$rand];
if($mp3)
{
echo "http://MyWebsite.com/hope/upload/php/files/$mp3";
}
else
{
echo "null";
}
}

This is happening because array indexes go from 0 to length - 1, but your script is generating a random index from 0 to length. The preferred way to fix this would be to use array_rand():
$rand = array_rand($mp3);
$mp3 = $mp3[$rand];

You random range is out (the max integer is the result of count(), and remember the count of an array is one higher than its highest index in an ordinal 0-based array), and your code looks far too verbose.
Try...
$mp3s = glob('../../hope/upload/php/files/*.mp3');
$key = array_rand($mp3s);
$randomMp3 = $mp3s[$key];

Related

php Compare two text files and output NON matching records

I have two text files of data the first file has 30 lines of data and matches with 30 lines in the second text file, but in addition the first text file has two additional lines that are added as the operator uploads file to the directory I want to find the non matching lines and out put them to be used in the same script as a mailout.
I am trying to use this code, which outputs the contents of the two files to screen.
<?php
if ($file1 = fopen(".data1.txt", "r")) {
while(!feof($file1)) { $textperline = fgets($file1);
echo $textperline;
echo "<br>";}
if ($file2 = fopen(".data.txt", "r")) {
while(!feof($file2)) {$textperline1 = fgets($file2);
echo $textperline1;
echo "<br>";}
fclose($file1);
fclose($file2);
}}
?>
But it outputs the whole list of data, can anyone help listingout only NON matching lines?
attached output of the two files from my code
I want to output only lines that are in file2 but not in file1
My suggestion would be to read each file into an array (one line = one element) and then use array_diff to compare them. Unless you have millions of lines, this approach is the easiest.
To reuse your code, this is how you can read the 2 files into two arrays
$list1 = [];
$list2 = [];
if ($file1 = fopen(".data1.txt", "r")) {
while (!feof($file1)) {
$list1[] = trim(fgets($file1));
}
fclose($file1);
}
if ($file2 = fopen(".data.txt", "r")) {
while (!feof($file2)) {
$list2[] = trim(fgets($file2));
}
fclose($file2);
}
If the files are small and you can read them in one go, you can also use a simplified syntax.
$list1 = explode(PHP_EOL, file_get_contents(".data1.txt"));
$list2 = explode(PHP_EOL, file_get_contents(".data.txt"));
Then, no matter which method you chose, you can compare them as follows
$comparison = array_diff($list2, $list1);
foreach ($comparison as $line) {
echo $line."<br />";
}
This will only output the lines of the second array that are not present in the first one.
Make sure that the one with the additional lines is the first argument of array_diff
ASSUMPTION
Both files are not huge and you can read the whole content into the memory at once. According to this, you can put following code to the top:
$file1 = "./data1.txt";
$file2 = "./data2.txt";
$linesOfFile1 = file($file1);
$linesOfFile2 = file($file2);
$newLinesInFile2 = [];
There are a couple cases, which you did not mention in your question.
CASE 1
New lines are only appended to the secode file file2. The solution for this case is the easiest one:
$numberOfRowsFile1 = count($linesOfFile1);
$numberOfRowsFile2 = count($linesOfFile1);
if($numberOfRowsFile2 > $numberOfRowsFile1)
{
$newLinesInFile2 = array_slice($linesOfFile2, $numberOfRowsFile1);
}
CASE 2
The lines with the same content may have different position in each file. Duplicate lines within the same file are ignored.
Furthermore the case sensitivity may play a role. That's why the content of each line should be hashed to make a simpler comparison. For both case sensitive and insensitive comparison the following function is needed:
function buildHashedMap($array, &$hashedMap, $caseSensitive = true)
{
foreach($array as $line)
{
$line = !$caseSensitive ? strtolower($line) : $line;
$hash = md5($line);
$hashedMap[$hash] = $line;
}
}
Case sensitive comparison
$hashedLinesFile1 = [];
buildHashedMap($linesOfFile1, $hashedLinesFile1);
$hashedLinesFile2 = [];
buildHashedMap($linesOfFile2, $hashedLinesFile2);
$newLinesInFile2 = array_diff_key($hashedLinesFile2, $hashedLinesFile1);
Case INSENSITIVE comparison
$caseSensitive = false;
$hashedLinesFile1 = [];
buildHashedMap($linesOfFile1, $hashedLinesFile1, $caseSensitive);
$hashedLinesFile2 = [];
buildHashedMap($linesOfFile2, $hashedLinesFile2, $caseSensitive);
$newLinesInFile2 = array_diff_key($hashedLinesFile2, $hashedLinesFile1);

What exactly the unset() function is doing here?

This code takes a number like 2017 and returns the NEXT highest (not total highest, like 7021) number, so in this case 2071
I understand every bit of except the if (($key = array_search($currentNumber, $tmpArray, true)) !== false) { unset($tmpArray[$key]); -- what exactly is happening here? Please help and I will accept immediately :)
I get that $key is either true or false if the current number is found in the tmpArray variable, but why is it set to !== false? Instead of === true? And what does unsetting do in this case?
<?php
function nextBigger(int $n): int
{
$numbers = str_split((string)$n);
$start = $n+ 1;
rsort($numbers);
$end = (int)implode('', $numbers);
for ($i = $start; $i <= $end; $i++) {
$tmpArray = $numbers;
$currentNumbers = str_split((string)$i);
foreach ($currentNumbers as $currentNumber) {
if (($key = array_search($currentNumber, $tmpArray, true)) !== false) {
unset($tmpArray[$key]); # What is the effect of this??
} else {
break;
}
}
if (empty($tmpArray)) {
return $i;
}
}
return -1;
}
The function takes the input (2017), and first finds the largest number than can be made with those digits, by ordering them in descending order (7210).
It then loops over every number between those two bounds*, and for each digit in that number, it sees if that digit is "available" in the input. Annotated below:
# Assign a temporary copy of the digits we have available
$tmpArray = $numbers;
// ...
# If the digit is still available in our temp copy, array_search will return the key
if (($key = array_search($currentNumber, $tmpArray, true)) !== false) {
# If we matched a key, we have to remove it from the temporary copy,
# so that we don't use it twice
unset($tmpArray[$key]);
} else {
# Otherwise, if it's not available to use then we know this number
# isn't a viable candidate, and we can break the loop
break;
}
# If we've reached this point and the temporary array is empty,
# we know we've used all the digits.
# Because we're returning from the function this can only happen once,
# at the next highest number after the input
if (empty($tmpArray)) {
return $i;
}
* As an aside, this is probably not the most efficient way of performing this task

Finding duplicate column values in a CSV

I'm importing a CSV that has 3 columns, one of these columns could have duplicate records.
I have 2 things to check:
1. The field 'NAME' is not null and is a string
2. The field 'ID' is unique
So far, I'm parsing the CSV file, once and checking that 1. (NAME is valid), which if it fails, it simply breaks out of the while loop and stops.
I guess the question is, how I'd check that ID is unique?
I have fields like the following:
NAME, ID,
Bob, 1,
Tom, 2,
James, 1,
Terry, 3,
Joe, 4,
This would output something like `Duplicate ID on line 3'
Thanks
P.S this CSV file has more columns and can have around 100,000 records. I have simplified it for a specific reason to solve the duplicate column/field
Thanks
<?php
$cnt = 0;
$arr=array();
if (($handle = fopen("1.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$num=count($data);
$cnt++;
for ($c=0; $c < $num; $c++) {
if(is_numeric($data[$c])){
if (array_key_exists($data[$c], $arr))
$arrdup[] = "duplicate value at ".($cnt-1);
else
$arr[$data[$c]] = $data[$c-1];
}
}
}
fclose($handle);
}
print_r($arrdup);
Give it a try:
$row = 1;
$totalIDs = array();
if (($handle = fopen('/tmp/test1.csv', "r")) !== FALSE)
{
while (($data = fgetcsv($handle)) !== FALSE)
{
$name = '';
if (isset($data[0]) && $data[0] != '')
{
$name = $data[0];
if (is_numeric($data[0]) || !is_string($data[0]))
echo "Name is not a string for row $row\n";
}
else
{
echo "Name not set for row $row\n";
}
$id = '';
if (isset($data[1]))
{
$id = $data[1];
}
else
{
echo "ID not set for row $row\n";
}
if (isset($totalIDs[$id])) {
echo "Duplicate ID on line $row\n";
}
else {
$totalIDs[$id] = 1;
}
$row++;
}
fclose($handle);
}
I went assuming a certain type of design, as stripped out the CSV part, but the idea will remain the same :
<?php
/* Let's make an array of 100,000 rows (Be careful, you might run into memory issues with this, issues you won't have with a CSV read line by line)*/
$arr = [];
for ($i = 0; $i < 100000; $i++)
$arr[] = [rand(0, 1000000), 'Hey'];
/* Now let's have fun */
$ids = [];
foreach ($arr as $line => $couple) {
if ($ids[$couple[0]])
echo "Id " . $couple[0] . " on line " . $line . " already used<br />";
else
$ids[$couple[0]] = true;
}
?>
100, 000 rows aren't that much, this will be enough. (It ran in 3 seconds at my place.)
EDIT: As pointed out, in_array is less efficient than key lookup. I've updated my code consequently.
Are the IDs sorted with possible duplicates in between or are they randomly distributed?
If they are sorted and there are no holes in the list (1,2,3,4 is OK; 1,3,4,7 is NOT OK) then just store the last ID you read and compare it with the current ID. If current is equal or less than last then it's a duplicate.
If the IDs are in random order then you'll have to store them in an array. You have multiple options here. If you have plenty of memory just store the ID as a key in a plain PHP array and check it:
$ids = array();
// ... read and parse CSV
if (isset($ids[$newId])) {
// you have a duplicate
} else {
$ids[$newId] = true; // new value, not a duplicate
}
PHP arrays are hash tables and have a very fast key lookup. Storing IDs as values and searching with in_array() will hurt performance a lot as the array grows.
If you have to save memory and you know the number of lines you going to read from the CSV you could use SplFixedArray instead of a plain PHP array. The duplicate check would be the same as above.

Subtraction between data in file1 and file2 using PHP

Suppose that I have 2 files:
File1.txt
10;30;15;40;12;14;15
23;32;10;50;12;54;60
File2.txt
2;4;5;6;7;8;9
3;6;7;8;9;0;7
I want to subtration between these 2 files. Ex 10 - 2........
PHP code:
$file1 = 'File1.txt';
$file2 = 'File2.txt';
if(file_exists($file1)){
$files = fopen($file1,'r');
while(!feof($files)){
$data = explode(";",fgets($files));
if($title ==""){
$title = $data[0];
}
if(!empty($data[3])){
$cate = $data[3];
$filepmta = fopen($file2,'r');
while(!feof($filepmta)){
$hourData = explode(";",fgets($filepmta));
if(!empty($hourData[3])){
if($title ==""){
$title = $hourData[0];
}
if(!empty($btnHour)){
echo $percentRed = ((int)$data[2] - (int)$hourData[2]);
//it loads page so long time so I don know what's error.
}
}
}
It loads the page is so long time.I don know how to fix this,Anyone know help me please,thanks.
I would recommend just simply opening both files and storing the contents of the file into memory. IE Create 2 simple loops (not nested) which iterates through both files respectively and saves the content to an array.
Then make one more loop after the first two. Iterate through the third loop based on the array size of the first two. (I'm assuming you can assume they are both the same size, but it will be a good idea to add checks to make sure loop 1 and loop 2 produce the same size/length arrays.
Within the 3rd loop iterate through both previously two generated arrays with the same index.
Pseudo Code // even tho I said I'd do pseudo code, I ended up doing it all. :-/
$file_values1 = $file_values2 = $answers = array();
while(!feof($file1)) $file_values1[] = explode(';', fgets($file)); // loop 1
while(!feof($file2)) $file_values2[] = explode(';', fgets($file)); // loop 2
foreach($file_values1 as $key1 => $sub_array) { // loop 3
foreach($sub_array as $key2 => $value) // loop 4; hehe, oh wellz
{
$answers[] = $file_values1[$key1][$key2] = $file_values2[$key1][$key2]
}
}
Basically, the nested loop 4, meh. There ARE faster answers. The 3rd loop can be improved upon, it's O(N^2). I'll leave that for you to optimize. No free lunches. :-)
fgets() doesn't move the file pointer after the last line, so you're staying on the last line, feof() still returns false and you're trapped in an infinite loop.
The common way of doing what you want is:
while (($line = fgets($handle)) !== false) {
echo $line;
}
Have a look at the code example in fgets() documentation, it's quite exhaustive.
You're making this way more complicated than it has to be.
Open your files and read them both in with file(), which will give you an array of lines in each file.
Iterate over both line arrays simultaneously with a MultipleIterator.
Convert each lines' contents to arrays with str_getcsv() and an array_map() trick.
Iterate over each line values with another MultipleIterator.
Compute the subtraction and print it.
That's all it takes! I've implemented the above algorithm below.
$file1 = 'File1.txt';
$file2 = 'File2.txt';
$file1 = file( $file1);
$file2 = file( $file2);
$iter = new MultipleIterator;
$iter->attachIterator( new ArrayIterator( $file1));
$iter->attachIterator( new ArrayIterator( $file2));
foreach( $iter as $element) {
list( $line1, $line2) = array_map( 'str_getcsv', $element, array( ';', ';'));
$val_iter = new MultipleIterator;
$val_iter->attachIterator( new ArrayIterator( $line1));
$val_iter->attachIterator( new ArrayIterator( $line2));
foreach( $val_iter as $value) {
list( $el1, $el2) = $value;
echo ($el1 - $el2); echo ";";
}
echo "\n";
}
You can see from this demo, which statically defines the files as arrays, that this produces:
8;26;10;34;5;6;6;
20;26;3;42;3;54;53;

Limit number of results for glob directory/folder listing

How would I go about limiting the number of directory results/pdfs to, say 8, in the following code?
$counter = 0;
foreach (glob("/directory/*.pdf") as $path) { //configure path
$docs[filectime($path)] = $path;
}
krsort($docs); // sort by key (timestamp)
foreach ($docs as $timestamp => $path) {
echo '<li>'.basename($path).'</li>';
$counter++;
}
This is probably really easy but I can't seem to be able figure it out - thanks in advance, S.
foreach (array_slice(glob("/directory/*.pdf"),0,8) as $path) {
Do a check of the counter and when it hits a certain number then break; from the loop.
Limit number of results for glob directory/folder listing
Glob does not have a flag to limit the number of results. This means you would have to retrieving all results for that directory and then reduce the array down to 8 file paths.
$glob = glob("/directory/*.pdf");
$limit = 8;
$paths = array();
if($glob){ //$glob not false or empty
if(count($glob) > $limit)){ // if number of file paths is more than 8
$paths = array_slice($glob,0,$limit);//get first 8 (alphabetically)
} else {
// we don't need to splice because there are less that 8 results)
$paths = $glob;
}
// or ternary
$paths = (count($glob) > $limit) ? array_slice($glob,0,$limit) : $glob;
}
foreach ($paths as $path){
...
}
Taking a deeper look at you example this might not be what your actually looking for as you want to sort your results.
When possible you should make use out of the GLOB FLAGS. Although there is not a flag to return the files in a specific order you can stop glob from returning them in alphabetical order(default).
If you ever need to sort the files in any way other than alphabetically then always use the GLOB_NOSORT flag.
If you did want the array limited to 8 filepaths but also have them in order of the timestamp then you would have to order them first before splicing the array.
$paths = array();
$limit = 8;
$glob = glob("/directory/*.pdf",GLOB_NOSORT); // get all files
if($glob){ // not false or empty
// Sort files by inode change time
usort($glob, function($a, $b){
return filectime($a) - filectime($b);
});
$paths = (count($glob) > $limit) ? array_slice($glob,0,$limit) : $glob;
}
$docs = array_merge($docs, $paths); // As i couldn't see where $docs was set I didn't want to overwrite the variable.
foreach ($docs as $path) {
$basename = basename($path);
echo '<li>'.$basename.'</li>';
}
<style>
li > a:nth-child(even){
color:#fff;
background-color:lightgrey;
}
</style>

Categories