I am using flat file db, not mysql so I can't use $limit
I need to limit the number of records to 1, if more than 1 then echos something else:
$result = $db->getall(lmonth);
foreach($result as $item)
show_record($item);
}
Function getall()
/*!
* #function getall
* #abstract retrieves all records in the database, each record in an array
* element.
* #param orderby order the results. Set to the field name to order by
* (as a string). If left unset, sorting is not done and it is a lot faster.
* If prefixed by "!", results will be ordered in reverse order.
* If orderby is an array, the 1st element refers to the field to order by,
* and the 2nd, a function that will take two take two parameters A and B
* - two fields from two records - used to do the ordering. It is expected
* that the function would return -ve if A < B and +ve if A > B, or zero
* if A == B (to order in ascending order).
* #param includeindex if true, an extra field called 'FFDB_IFIELD' will
* be added to each record returned. It will contain an int that specifies
* the original position in the database (zero based) that the record is
* positioned. It might be useful when an orderby is used, and an future
* operation on a record is required, given it's index in the table.
* #result all database records as an array
*/
function getall($orderby = NULL, $includeindex = false)
{
if (!$this->isopen)
{
user_error("Database not open.", E_USER_ERROR);
return false;
}
// If there are no records, return
if ($this->records == 0)
return array();
if (!$this->lock_read())
return false;
// Read the index
$index = $this->read_index();
// Read each record and add it to an array
$rcount = 0;
foreach($index as $offset)
{
// Read the record
list($record, $rsize) = $this->read_record($this->data_fp, $offset);
// Add the index field if required
if ($includeindex)
$record[FFDB_IFIELD] = $rcount++;
// Add it to the result
$result[] = $record;
}
$this->unlock();
// Re-order as required
if ($orderby !== NULL)
return $this->order_by($result, $orderby);
else
return $result;
}
Function show_record()
function show_record($record){
$month = $record["lmonth"];
$status = $record["lstatus"];
$year = $record["lyear"];
}
if (($status == ON) && ($month >= $current_month) && ($year >= $current_year)){
echo "foo";
}
I tried to use break but it comes back 0(zero) records.
I tried using $i = 0...but it returned all or nothing
Any ideas?
Thanks
What about something like this?
function getall($orderby = null, $includeindex = false, $limit = null) {
...
if ($orderby !== null) {
$result = $this->order_by($result, $orderby);
}
if ($limit) {
return array_slice($result, 0, $limit);
}
return $result;
}
SOLUTION
Print_r did the trick, and I was using echo:
print_r (show_record($row));
Here is how the final code is working for me:
$result = $db->getall(lp_month,lp_year);
$i = 0;
foreach ($result as $row){
print_r (show_record($row));
if ($i >= 1)
break;
$i++;
}
Now looking forward to solve other minor problems, thanks
Related
I have a function which contains a query and a percentile calculation (below) on the query's results/collection.
This function is needed in the following way:
I have an Athlete model, with many Tests. Each Test also has many Results. For every result (displayed in a table), I need to display a calculated percentile (which is not stored). It's at this point (the table view) where I need the function to calculate the percentile for every result.
I have detailed the models here. The answer of that post also shows the query used.
If I stick that function in the Result model, it allows me easy access to it from the view in this manner {{$result->getThisRank($test->age, $an_athlete->gender, $result->battery_id, $result->score)}}
If I put it in a Controller, I would need some dynamic and complex way to call a route for each specific Result in the table. Not even sure how to do this.
Question: Is it correct to have this query and calculation in the Result model?
An earlier post here, seems to also go with model.
The calculation performed on the query collection:
for ($i = 1; $i < 100; $i++ ){
$rank = 0.0;
$p = 0.0;
$rank = $i/100 * ($count + 1);
$p = $rank;
//get integer and decimal for interpolation http://onlinestatbook.com/lms/introduction/percentiles.html
$intpart = floor($p);
$fraction = $p - $intpart;
//dd($intpart . ' '.$fraction);
if($fraction > 0){ //part of percentile formula - see article
//test for min array index to not be out of bound. Test negative, most used case.
if($intpart != 0){
$scoreLow = $all_scores[$intpart - 1];
if($intpart == $count){ //test for max array index to not go over bound
$scoreHigh = $all_scores[$intpart - 1];
} else{
$scoreHigh = $all_scores[$intpart];
}
} else{
$scoreLow = $all_scores[0];
$scoreHigh = $all_scores[$intpart];
}
//scoreLow and scoreHigh has been determined, now final step for decimal rank
$scoreHigh = $scoreHigh * 1.0;
$scoreLow = $scoreLow * 1.0;
$rank = ($fraction * ($scoreHigh - $scoreLow)) + $scoreLow;
} else{
$rank = ($all_scores[$intpart - 1] * 1.0);//no decimal rank, plain rank calculation
}
if($sortorder == 'asc'){
$rankings[$i.'th %'] = $rank;
}else{
$rankings[100 - $i.'th %'] = $rank;
}
//$rankings->add(['rank'.$i => $rank]);
}
//dd($rankings);
//reverse rankings
$rev_rankings = array_reverse($rankings);
if ($battery == 111){
dd($rev_rankings);
}
$view_rank = null;
foreach($rev_rankings as $key => $rank){
if($athlete_score == $rank){
$view_rank = $key;
break;
}
if($athlete_score > $rank){
$view_rank = $key;
break;
}
}
return($view_rank);
}
else{
return ('Not available');
}
The type of calculations you describe certainly belong in the domain (i.e. - model). Depending on how you're structuring your application you may place the function within an existing Eloquent model or even as part of a new entity which doesn't extend the ORM. Additionally, I would standardize the type of return value it provides, perhaps restricting it to numerical values or possibly numerical values as well as NULL. Then I would replace in the view (blade template) any NULL values, etc. with "Not available" as I notice at the bottom of your function.
So basically i'm trying to create a complex timetable and i have these two methods that each perform a different check function for me:
Checks if i have a unique array
function tutorAllot($array,$check,$period){
//check for clashes and return non colliding allotment
shuffle($array);
$rKey = array_rand($array);
if(array_key_exists($array[$rKey]['teacher_id'], $check[$period])) {
return $this->tutorAllot($array,$check,$period);
}
return $tutor = array($array[$rKey]['teacher_id'] => $array[$rKey]['subject_code']);
}
checks that each subject does not appear more than twice in a day
function checkDayLimit($data,$check){
//check double day limit
$max = 2;
$value = array_values($check);
$tempCount = array_count_values($data);
return (array_key_exists($value[0], $tempCount) && $tempCount[$value[0]] <= $max) ? true : false;
}
I'm calling the functions from a loop and populating timetable array only if all conditions area satisfied:
$outerClass = array();
foreach ($value as $ky => $val) {
$innerClass = array(); $dayCount = array();
foreach ($periods[0] as $period => $periodData) {
$innerClass[$period] = array();
if(!($periodData == 'break')){
$return = $this->Schedule->tutorAllot($val,$clashCheck,$period);
if($return){
//check that the returned allocation hasnt reached day limit
if($this->Schedule->checkDayLimit($dayCount,$return)){
$innerClass[$period] += $return;
$clashCheck[$period] += $return;
}else{
}
}
}else{
$innerClass[$period] = '';
}
}
//debug($innerClass);
$outerClass[$ky] = $innerClass;
}
My requirements
If the checkDayLimit returns false , i want to go back and call tutorAllot function again to pick a new value.
I need to do this without breaking the loop.
I was thinking maybe i could use goto statement but only when am out of options.
Is there a way i can achieve this without using goto statement.
PHP v5.5.3 Ubuntu
Your architecture seems overly complex. Instead of
pick at random >> check limit >> if at limit, go to re-pick...
Why not incorporate both checks into a single function? It would
Filter out data that is not eligible to be picked, and return an array of legitimate choices
Pick at random from the safe choices and return the pick
addendum 1
I don't think there is any need for recursion. I would use array_filter to pass the data through a function that returns true for eligible members and false for the rest. I would then take the result of array_map and make a random selection from it
I have a game script thing set up, and when it creates a new character I want it to find an empty address for that players house.
The two relevant table fields it inserts are 'city' and 'number'. The 'city' is a random number out of 10, and the 'number' can be 1-250.
What it needs to do though is make sure there's not already an entry with the 2 random numbers it finds in the 'HOUSES' table, and if there is, then change the numbers. Repeat until it finds an 'address' not in use, then insert it.
I have a method set up to do this, but I know it's shoddy- there's probably some more logical and easier way. Any ideas?
UPDATE
Here's my current code:
$found = 0;
while ($found == 0) {
$num = (rand()%250)+1; $city = (rand()%10)+1;
$sql_result2 = mysql_query("SELECT * FROM houses WHERE city='$city' AND number='$num'", $db);
if (mysql_num_rows($sql_result2) == 0) { $found = 1; }
}
You can either do this in PHP as you do or by using a MySQL trigger.
If you stick to the PHP way, then instead of generating a number every time, do something like this
$found = 0;
$cityarr = array();
$numberarr = array();
//create the cityarr
for($i=1; $i<=10;$i++)
$cityarr[] = i;
//create the numberarr
for($i=1; $i<=250;$i++)
$numberarr[] = i;
//shuffle the arrays
shuffle($cityarr);
shuffle($numberarr);
//iterate until you find n unused one
foreach($cityarr as $city) {
foreach($numberarr as $num) {
$sql_result2 = mysql_query("SELECT * FROM houses
WHERE city='$city' AND number='$num'", $db);
if (mysql_num_rows($sql_result2) == 0) {
$found = 1;
break;
}
}
if($found) break;
}
this way you don't check the same value more than once, and you still check randomly.
But you should really consider fetching all your records before the loops, so you only have one query. That would also increase the performance a lot.
like
$taken = array();
for($i=1; $i<=10;$i++)
$taken[i] = array();
$records = mysql_query("SELECT * FROM houses", $db);
while($rec = mysql_fetch_assoc($records)) {
$taken[$rec['city']][] = $rec['number'];
}
for($i=1; $i<=10;$i++)
$cityarr[] = i;
for($i=1; $i<=250;$i++)
$numberarr[] = i;
foreach($cityarr as $city) {
foreach($numberarr as $num) {
if(in_array($num, $taken[]) {
$cityNotTaken = $city;
$numberNotTaken = $number;
$found = 1;
break;
}
}
if($found) break;
}
echo 'City ' . $cityNotTaken . ' number ' . $numberNotTaken . ' is not taken!';
I would go with this method :-)
Doing it the way you say can cause problems when there is only a couple (or even 1 left). It could take ages for the script to find an empty house.
What I recommend doing is insert all 2500 records in the database (combo 1-10 with 1-250) and mark with it if it's empty or not (or create a combo table with user <> house) and match it on that.
With MySQL you can select a random entry from the database witch is empty within no-time!
Because it's only 2500 records, you can do ORDER BY RAND() LIMIT 1 to get a random row. I don't recommend this when you have much more records.
I am using flat file db, not mysql so I can't use $limit
Function getbyfieldsw()
/*!
* #function getbyfieldsw
* #abstract retrieves records in the database whose field matches the
* given regular expression.
* #param fieldname the field which to do matching on
* #param regex the regular expression to match a field on.
* Note: you should include the delimiters ("/php/i" for example).
* #param orderby order the results. Set to the field name to order by
* (as a string). If left unset, sorting is not done and it is a lot faster.
* If prefixed by "!", results will be ordered in reverse order.
* If orderby is an array, the 1st element refers to the field to order by,
* and the 2nd, a function that will take two take two parameters A and B
* - two fields from two records - used to do the ordering. It is expected
* that the function would return -ve if A < B and +ve if A > B, or zero
* if A == B (to order in ascending order).
* #param includeindex if true, an extra field called 'FFDB_IFIELD' will
* be added to each record returned. It will contain an int that specifies
* the original position in the database (zero based) that the record is
* positioned. It might be useful when an orderby is used, and an future
* operation on a record is required, given it's index in the table.
* #result matching records in an array, or false on failure
*/
function getbyfieldsw($fieldname, $orderby = NULL, $includeindex = false) {
if (!$this->isopen)
{
user_error("Database not open.", E_USER_ERROR);
return false;
}
// Check the field name
if (!$this->key_exists_array($fieldname, $this->fields))
{
user_error(
"Invalid field name for getbyfield: $fieldname",
E_USER_ERROR
);
return false;
}
// If there are no records, return
if ($this->records == 0)
return array();
if (!$this->lock_read())
return false;
// Read the index
$index = $this->read_index();
// Read each record and add it to an array
$rcount = 0;
foreach($index as $offset)
{
// Read the record
list($record, $rsize) = $this->read_record($this->data_fp, $offset);
// See if the record matches the regular expression
// Add the index field if required
if ($includeindex)
$record[FFDB_INDEX_RECORDS_OFFSET] = $rcount;
$result[] = $record;
++$rcount;
}
$this->unlock();
// Re-order as required
if ($orderby !== NULL)
return $this->order_by($result, $orderby);
else
return $result;
}
Function show_record()
function show_record($record){
$month = $record["lmonth"];
$status = $record["lstatus"];
$year = $record["lyear"];
}
if (($status == ON) && ($month >= $current_month) && ($year >= $current_year)){
echo "foo";
}
Call records - here is the problem - this works except for the break; - I need to explode the flat file to read each record individually, then add to the foreach():
$result = $db->getbyfieldsw(lp_month);
$i = 0;
foreach ($result as $item){
show_record($item);
if ($i >= 2)
break;
}
Since this is flat file, when calling the function getbyfieldsw() the file comes flat as a single file, no matter how many records on it, records = 1 or records = 100 is the same at this point.
Break; does not work because all records come as single record - therefore nothing to break.
What I want to do is split/explode the records, count them, and based on my if statement post:
if $i == 0 echo "content1";
if $i == 1 echo "content2";
if $i > 1 echo "content 3";
I got held here for the past 3 days...exhausted all solutions.
HELP glad appreciated
Thanks
Sorry for not helping your code directly, but you should consider switching to SQLITE instead of using your own database system. SQLITE is flat, open-source, efficient and easier to work with. PHP's sqlite module has everything you need. You should check it out!
http://www.sqlite.org/about.html
http://php.net/manual/en/book.sqlite.php
i have a function (below) which is used in my mysql abstraction class and converts table names in fields like "tableName.fieldName" and replaces them with the specified variables (it's useful for joins). the array of fields is very mixed, and so i need it to support recursion so that it can change the table names in an array(array(array("tableName.fieldName"))) but also for the standard array("tableName.fieldName","tableName.field2Name",...)
however, after debugging, i see that the variables $i and $fields_arr are maintaining the same values, even though i pass on a new value for $fields_arr and $i is set at the begining of the loop. how can i make this work so that $i and $fields_arr take the new values i pass for them?
/**
* #param mixed $fields_arr standard array ("table.field1","table.field2",...)
* #param mixed $tables_and_vars eg. ("table1" => "tableVar1", "table2" => "tableVar2", ...)
* replaces all tablenames with desired tablevarnames
*/
private function tablesToTableVars($fields_arr, $tables_and_vars) {
// loop through every string
$numFields = count($fields_arr);
for($i = 0; $i < $numFields; $i++) {
$field = $fields_arr[$i];
if(is_numeric($field)) continue; // don't replace numbers
if(is_array($field)) {
$fields_arr[$i] = $this->tablesToTableVars($field, $tables_and_vars); // *** RECURSION ***
} else {
$tableNameLen = strpos($field, "."); // pos of period in string
if(strpos($field, ".") === false) continue; // don't replace fields that dont have tablenames
$searchTableName = substr($field, 0, $tableNameLen); // take 'table' from 'table.field'
// see if field contains a table
foreach($tables_and_vars as $tableName => $tableVar) {
if($searchTableName === $tableName) { // if it is the table name we're looking for
$fields_arr[$i] = $tableVar . substr($field, $tableNameLen); // change it to the variable name
break;
}
}
}
}
return $fields_arr;
}
can't use integer indexes for an associative array =)