Place colliding elements next to each other - php

I'm creating some kind of calendar/agenda which shows the events for a specific day. Each event is displayed as an HTML element in a vertical hours grid. There could be multiple ("colliding") events at the same time, and in those cases the elements should be placed next to each other, horizontally, and have equal widths. E.g. four colliding events get the column value 4, in this way the width of 25%.
The tricky part is these colliding events. I thought I solved it, but some elements get the wrong number of columns.
There might be a better way to calculate the column count and placement - I'm open to suggestions.
Sample image for current (wrong) result:
Relevant code:
<?php
class Calendar {
const ROW_HEIGHT = 24;
public $events = array();
public $blocks = array();
public function calculate_blocks() {
foreach($this->events as $event) {
// Calculate the correct height and vertical placement
$top = $this->time_to_pixels($event->_event_start_time);
$bottom = $this->time_to_pixels($event->_event_end_time);
$height = $bottom - $top;
// Abort if there's no height
if(!$height) continue;
$this->blocks[] = array(
'id' => $event->ID,
'columns' => 1,
'placement' => 0, // Column order, 0 = first
'css' => array(
'top' => $top,
'bottom' => $bottom, // bottom = top + height
'height' => $height
)
);
}
$done = array();
// Compare all the blocks with each other
foreach($this->blocks as &$block) {
foreach($this->blocks as &$sub) {
// Only compare two blocks once, and never compare a block with itself
if($block['id'] == $sub['id'] || (isset($done[$block['id']]) && in_array($sub['id'], $done[$block['id']])) || (isset($done[$sub['id']]) && in_array($block['id'], $done[$sub['id']]))) continue;
$done[$block['id']][] = $sub['id'];
// If the blocks are colliding
if(($sub['css']['top'] >= $block['css']['top'] && $sub['css']['top'] < $block['css']['bottom'])
|| ($sub['css']['bottom'] >= $block['css']['top'] && $sub['css']['bottom'] < $block['css']['bottom'])
|| ($sub['css']['top'] <= $block['css']['top'] && $sub['css']['bottom'] >= $block['css']['bottom'])) {
// Increase both blocks' columns and sub-block's placement
$sub['columns'] = ++$block['columns'];
$sub['placement']++;
}
}
}
}
private function time_to_int($time) {
// H:i:s (24-hour format)
$hms = explode(':', $time);
return ($hms[0] + ($hms[1] / 60) + ($hms[2] / 3600));
}
private function time_to_pixels($time) {
$block = $this->time_to_int($time);
return (int)round($block * self::ROW_HEIGHT * 2);
}
}
?>

Try this:
public function calculate_blocks()
{
$n = count($events);
$collumns = array();
$placements = array();
// Set initial values.
for ($i = 0; $i < $n; $i++)
{
$collumns[$i] = 1;
$placements[$i] = 0;
}
// Loop over all events.
for ($i = 0; $i < $n; $i++)
{
$top1 = $this->time_to_pixels($events[$i]->_event_start_time);
$bottom1 = $this->time_to_pixels($events[$i]->_event_end_time);
// Check for collisions with events with higher indices.
for ($j = $i + 1; $j < $n; $j++)
{
$top2 = $this->time_to_pixels($events[$k]->_event_start_time);
$bottom2 = $this->time_to_pixels($events[$k]->_event_end_time);
$collides = $top1 < $bottom2 && $top2 < $bottom1;
// If there is a collision, increase the collumn count for both events and move the j'th event one place to the right.
if ($collides)
{
$collumns[$i]++;
$collumns[$j]++;
$placements[$j]++;
}
}
$this->blocks[] = array(
'id' => $events[$i]->ID,
'columns' => $collumns[$i],
'placement' => $placements[$i],
'css' => array(
'top' => $top1,
'bottom' => $bottom1,
'height' => $bottom1 - $top1;
)
);
}
}
I can't actually test it, but I think it should leave you with a correct blocks array.
Edit 1: Doesn't seem to yield the required result, see comments below.
Edit 2: I think this is the exact same problem: Visualization of calendar events. Algorithm to layout events with maximum width. Someone solved it with C#, but it should be relatively easy to port that answer to PHP to solve your problem.

Related

How can I generate and validate random IDs using the Saudi ID format?

I need to generate random IDs that validate against the criteria for Saudi IDs shown in this question:
Saudi Iqama/National Identity number field validation
I've tried the following code:
$random_numbers = [];
while(count($random_numbers) < 1000000000){
do {
$random_number = mt_rand(1000000000,9000000000);
}
while (in_array($random_number, $random_numbers));{
$type = substr ( $random_number, 0, 1 );
if($type != 2 && $type != 1 ) break;
$sum = 0;
for( $i = 0 ; $i<10 ; $i++ ) {
if ( $i % 2 == 0){
$ZFOdd = str_pad ( ( substr($random_number, $i, 1) * 2 ), 2, "0", STR_PAD_LEFT );
$sum += substr ( $ZFOdd, 0, 1 ) + substr ( $ZFOdd, 1, 1 );
}else{
$sum += substr ( $random_number, $i, 1 );
}
}
return $sum%10 ? break : echo $random_number;
----------
echo "<br>";
$random_numbers[] = $random_number;}
}
Disclaimer: I'm not 100% sure on the validation required etc. for Saudi ID numbers and have only briefly looked at the answers supplied in the linked question
Okay, so, my understanding is that you need to generate a random id that:
Matches the pattern/format:
[12]\d{9}
Validates against the criteria show in the linked question:
Saudi Iqama/National Identity number field validation
To do this we need to create a couple of functions; one to generate IDs and one to validate the IDs against the given criteria.
Generate the ID
Simply generating an ID is simple enough. We can use the random_int function in PHP with a loop. If we enclose the code to generate the ID inside of a do...while... loop then we can execute the code and validate the ID repeatedly until we get a valid one.
function getRandomSaudiId() : int
{
do {
$saudiId = (string) random_int(1,2);
for($i = 0; $i < 9; $i++){
$saudiId .= random_int(0,9);
}
} while(validateSaudiId($saudiId) === false);
return (int) $saudiId;
}
Validate the ID
Note: we convert to string so that we can access the numbers based on their index.
function validateSaudiId(string $id) : bool
{
$sum = 0;
for($i = 0; $i < 9; $i++){
if( $i % 2 ){
// Even number
$sum += $id[$i];
}
else{
//Odd number
$increment = $id[$i] * 2;
while($increment > 9){
$increment = (string) $increment;
$increment = $increment[0] + $increment[1];
}
$sum += $increment;
}
}
$sum = (string) $sum;
return ($sum[1] == $id[9] || $id[9] == (10 - $sum[1])) ? true : false;
}
Example use
for($i = 0; $i < 10; $i++) var_dump(getRandomSaudiId());
/*
Output:
int(2933617506)
int(2409806096)
int(1072585118)
int(2891306413)
int(1810304558)
int(2591965856)
int(1363032527)
int(1031823269)
int(1265954048)
int(2498099472)
int(1134172537)
*/

Get lowest price on sum of combinations in given array

This code is working fine when the array length is 8 or 10 only. When we are checking this same code for more than 10 array length.it get loading not showing the results.
How do reduce my code. If you have algorithm please share. Please help me.
This program working flow:
$allowed_per_room_accommodation =[2,3,6,5,3,5,2,5,4];
$allowed_per_room_price =[10,30,60,40,30,50,20,60,80];
$search_accommodation = 10;
i am get subsets = [5,5],[5,3,2],[6,4],[6,2,2],[5,2,3],[3,2,5]
Show lowest price room and then equal of 10 accommodation; output like as [5,3,2];
<?php
$dp=array(array());
$GLOBALS['final']=[];
$GLOBALS['room_key']=[];
function display($v,$room_key)
{
$GLOBALS['final'][] = $v;
$GLOBALS['room_key'][] = $room_key;
}
function printSubsetsRec($arr, $i, $sum, $p,$dp,$room_key='')
{
// If we reached end and sum is non-zero. We print
// p[] only if arr[0] is equal to sun OR dp[0][sum]
// is true.
if ($i == 0 && $sum != 0 && $dp[0][$sum]) {
array_push($p,$arr[$i]);
array_push($room_key,$i);
display($p,$room_key);
return $p;
}
// If $sum becomes 0
if ($i == 0 && $sum == 0) {
display($p,$room_key);
return $p;
}
// If given sum can be achieved after ignoring
// current element.
if (isset($dp[$i-1][$sum])) {
// Create a new vector to store path
// if(!is_array(#$b))
// $b = array();
$b = $p;
printSubsetsRec($arr, $i-1, $sum, $b,$dp,$room_key);
}
// If given $sum can be achieved after considering
// current element.
if ($sum >= $arr[$i] && isset($dp[$i-1][$sum-$arr[$i]]))
{
if(!is_array($p))
$p = array();
if(!is_array($room_key))
$room_key = array();
array_push($p,$arr[$i]);
array_push($room_key,$i);
printSubsetsRec($arr, $i-1, $sum-$arr[$i], $p,$dp,$room_key);
}
}
// Prints all subsets of arr[0..n-1] with sum 0.
function printAllSubsets($arr, $n, $sum,$get=[])
{
if ($n == 0 || $sum < 0)
return;
// Sum 0 can always be achieved with 0 elements
// $dp = new bool*[$n];
$dp = array();
for ($i=0; $i<$n; ++$i)
{
// $dp[$i][$sum + 1]=true;
$dp[$i][0] = true;
}
// Sum arr[0] can be achieved with single element
if ($arr[0] <= $sum)
$dp[0][$arr[0]] = true;
// Fill rest of the entries in dp[][]
for ($i = 1; $i < $n; ++$i) {
for ($j = 0; $j < $sum + 1; ++$j) {
// echo $i.'d'.$j.'.ds';
$dp[$i][$j] = ($arr[$i] <= $j) ? (isset($dp[$i-1][$j])?$dp[$i-1][$j]:false) | (isset($dp[$i-1][$j-$arr[$i]])?($dp[$i-1][$j-$arr[$i]]):false) : (isset($dp[$i - 1][$j])?($dp[$i - 1][$j]):false);
}
}
if (isset($dp[$n-1][$sum]) == false) {
return "There are no subsets with";
}
$p;
printSubsetsRec($arr, $n-1, $sum, $p='',$dp);
}
$blockSize = array('2','3','6','5','3','5','2','5','4');
$blockvalue = array('10','30','60','40','30','50','20','60','80');
$blockname = array("map","compass","water","sandwich","glucose","tin","banana","apple","cheese");
$processSize = 10;
$m = count($blockSize);
$n = count($processSize);
// sum of sets in array
printAllSubsets($blockSize, $m, $processSize);
$final_subset_room = '';
$final_set_room_keys = '';
$final_set_room =[];
if($GLOBALS['room_key']){
foreach ($GLOBALS['room_key'] as $set_rooms_key => $set_rooms) {
$tot = 0;
foreach ($set_rooms as $set_rooms) {
$tot += $blockvalue[$set_rooms];
}
$final_set_room[$set_rooms_key] = $tot;
}
asort($final_set_room);
$final_set_room_first_key = key($final_set_room);
$final_all_room['set_room_keys'] = $GLOBALS['room_key'][$final_set_room_first_key];
$final_all_room_price['set_room_price'] = $final_set_room[$final_set_room_first_key];
}
if(isset($final_all_room_price)){
asort($final_all_room_price);
$final_all_room_first_key = key($final_all_room_price);
foreach ($final_all_room['set_room_keys'] as $key_room) {
echo $blockname[$key_room].'---'. $blockvalue[$key_room];
echo '<br>';
}
}
else
echo 'No Results';
?>
I'm assuming your task is, given a list rooms, each with the amount of people it can accommodate and the price, to accommodate 10 people (or any other quantity).
This problem is similar to 0-1 knapsack problem which is solvable in polynomial time. In knapsack problem one aims to maximize the price, here we aim to minimize it. Another thing that is different from classic knapsack problem is that full room cost is charged even if the room is not completely occupied. It may reduce the effectiveness of the algorithm proposed at Wikipedia. Anyway, the implementation isn't going to be straightforward if you have never worked with dynamic programming before.
If you want to know more, CLRS book on algorithms discusses dynamic programming in Chapter 15, and knapsack problem in Chapter 16. In the latter chapter they also prove that 0-1 knapsack problem doesn't have trivial greedy solution.

How to organize in chunks a not accurate division?

I have a number of participants and a number of groups, and I have to organize the participants into groups.
Example:
10/3 = 3, 3 and 4.
10/9 = 2,2,2 and 4.
23/3 = 6,6,6 and 5.
I have tried with array_chunk using the size paramether as a rounded result of participants/groups but it Did not work well.
Edit with my problem solved.
$groups = $this->request->data['phases_limit'];
$classified_lmt = $this->request->data['classified_limit'];
$participants = count($game->user_has_game);
$participants_lmt = floor($participants / $groups);
$remainders = $participants % $groups;
if ($groups > $participants) {
throw new \Exception("Há mais grupos que participantes");
}
for ($i=0; $i < $groups; $i++) {
$p = $this->Phase->newEntity();
$p->name = 'Grupo #' . $game->id;
$p->game_id = $game->id;
$p->classified_limit = $classified_lmt;
$this->Phase->save($p);
// add the number of participants per group
for ($j=0; $j < $participants_lmt; $j++) {
$user_has_game = array_pop($game->user_has_game);
$g = $this->Phase->GroupUserHasGame->newEntity();
$g->group_id = $p->id;
$g->user_has_game_id = $user_has_game->id;
$this->Phase->GroupUserHasGame->save($g);
}
// check if it is the last iteration
if (($groups - 1) == $i) {
// add the remainders on the last iteration
for ($k=0; $k < $remainders; $k++) {
$user_has_game = array_pop($game->user_has_game);
$g = $this->Phase->GroupUserHasGame->newEntity();
$g->group_id = $p->id;
$g->user_has_game_id = $user_has_game->id;
$this->Phase->GroupUserHasGame->save($g);
}
}
}
Have you tried the modulus operator? It gives you the remainder after dividing the numerator by the denominator.
For example, if you want to split 10 people into 3 groups:
floor(10 / 3) = 3; // people per group
10 % 3 = 1; // 1 person left over to add to an existing group.
Edit - I included the following function as part of my original answer. This doesn't work for OP, however I want to leave it here, as it may help others.
function group($total, $groups)
{
// Calculate participants per group and remainder
$group = floor($total / $groups);
$remainder = $total % $groups;
// Prepare groupings and append remaining participant to first group
$groupings = array_fill(0, $groups, $group);
$groupings[0] += $remainder;
return $groupings;
}
Not sure there are off-the-shelf libraries for that. I just implemented something similar in Java if you need some ideas:
public List<Integer> createDistribution(int population_size, int groups) {
List<Integer> lst = new LinkedList();
int total = 0;
for (double d : createDistribution(groups)) {
// this makes smaller groups first int i = new Double(population_size * d).intValue();
int i = (int)Math.round(population_size * d);
total += i;
lst.add(i);
}
// Fix rounding errors
while (total < population_size) {
int i = r.nextInt(groups);
lst.set(i, lst.get(i) + 1);
total += 1;
}
while (total > population_size) {
int i = r.nextInt(groups);
if (lst.get(i) > 0) {
lst.set(i, lst.get(i) - 1);
total -= 1;
}
}
return lst;
}

Summerize all fields if exists from database

I have different fields in the database and im fetching their values.
Now what I would like to do is subract a number if certain field exists in database.
Here is my code:
<?php
$percent_profile_image = 40;
$percent_cover_image = 20;
$percent_profiletext = 10;
$percent_travelintext = 10;
$percent_sparetimetext = 10;
$percent_gouttext = 10;
$avatar_status_num;
$cover_status_num;
$profiletext = $display_profile['profile_text_approved'];
$sparetimetext = $display_profile['spare_time_text_approved'];
$travelintext = $display_profile['traveling_text_approved'];
$gouttext = $display_profile['go_out_text_approved'];
if($avatar_status_num == 2) { echo $percent_profile_image; } + if($avatar_status_num == 2) { echo $percent_profile_image; }
?>
Now I know my if code is wrong. What I would like to do if example $avatar_status_num = 2 i would like to print out 40. if $cover_status_num = 2 I would like to substract these to numbers so. 40 + 20. So it should only print out the number and substract it if the value from DB is nr2.
I hope you understand my question :)
Cheerz
Use an extra variable to sum your values.
$sum = 0;
if (isset($someValue) && $someValue == 2) {
$sum += 40;
}
if (isset($someOtherValue) && $someOtherValue == 2) {
$sum += 20;
}
Or if you wanna do this dynamically in your code:
$percent = array(
'profile_image' => 40,
'cover_image' => 20,
'profile_text_approved' => 10,
'traveling_text_approved' => 10,
'spare_time_text_approved' => 10,
'go_out_text_approved' => 10,
);
$sum = 0;
foreach ($display_profile as $key => $value) {
if (array_key_exists($key, $percent) && $value == 2) {
$sum += $percent[$key];
}
}
Note In this case the $percent key names should be the same as the $display_profile key names.

Php string with separator to page list

I try to recreate what we see when we printing page on office or adobe.
For example, when you want to print page 1 to 5 you write : 1-5 and if you want to print a page outside you write : 1-5,8
At the moment I explode string by ',' :
1-5 / 8
Then explode each result by '-' and if I've got result I loop from first page to last and create variable with comma :
1,2,3,4,5,8
Finally I explode by ',' and use array unique to erase double value.
It take some times to achieve this especially when there's a lot of '-'.
Maybe someone got a easier solution to so this ?
Thank
Edit :
$pages = "1-4,6-8,14,16,18-20";
$pages_explode = explode(',',$pages);
foreach($pages_explode as $page){
$page_explode = explode('-',$page);
if(!empty($page_explode[1])){
for ($i=$page_explode[0]; $i<=$page_explode[1] ; $i++) {
$page_final .= $i.',';
}
}else{
$page_final .= $page_explode[0].',';
}
}
$page_final = explode(',',$page_final);
$page_final = array_unique ($page_final);
foreach($page_final as $value){
echo $value.'<br>';
}
Is it a code golf challenge?
Well a basic approach seems fine to me :
$input = '1-5,6-12,8';
$patterns = explode(',', $input);
$pages = [];
foreach ($patterns as $pattern) {
if (2 == count($range = explode('-', $pattern))) {
$pages = array_merge($pages, range($range[0], $range[1]));
} else {
$pages[] = (int)$pattern;
}
}
$uniquePages = array_unique($pages);
var_dump($uniquePages);
Outputs :
array (size=12)
0 => int 1
1 => int 2
2 => int 3
3 => int 4
4 => int 5
5 => int 6
6 => int 7
7 => int 8
8 => int 9
9 => int 10
10 => int 11
11 => int 12
Having to remove duplicates suggests that you have overlapping ranges in your strings.
Eg: 1-5,2-9,7-15,8,10
You seems to process all these without considering the overlapping areas and finally attempt to remove duplicates by the expensive array_unique function.
Your code should instead remember the minimum and maximum of the resulting range and not process anything that overlaps this range.
Following is a sample code which demonstrates the idea. But its certainly faster than the code you have suggested in your question. You should add parts there to process additional types of delimiters, if any, in your requirement.
<?php
$ranges = "1-5,3-7,6-10,8,11";
$min = 10000;
$max = -1;
$numbers = array(); //Your numbers go here
//Just a utility function to generate numbers from any range and update margins
function generateNumbers($minVal, $maxVal) {
global $min, $max, $numbers;
for ($i = $minVal; $i <= $maxVal; $i++) {
array_push($numbers, $i);
if ($i < $min)
$min = $i;
if ($i > $max)
$max = $i;
}
}
//Seperate ranges
$sets = explode(",", $ranges);
//Go through each range
foreach($sets as $aSet) {
//Extract the range or get individual numbers
$range = explode("-", $aSet);
if (count($range) == 1) { //its an individual number. So check margins and insert
$aSet = intval($aSet);
if ($aSet < $min){
array_push($numbers, $aSet);
$min = $aSet;
}
if ($aSet > $max){
array_push($numbers, $aSet);
$max = $aSet;
}
continue; // <----- For single numbers it ends here
}
//Its a range
$rangeLow = intval($range[0]);
$rangeHigh = intval($range[1]);
//Adjusting numbers to omit cases when ranges fall right on the margins
if ($rangeLow == $min){
$rangeLow++;
}
else
if ($rangeLow == $max) {
$rangeLow--;
}
if ($rangeHigh == $min){
$rangeHigh++;
}
else
if ($rangeHigh == $max) {
$rangeHigh--;
}
//Check if below or above the generated range
if (($rangeLow < $min && $rangeHigh < $min) || ($rangeLow > $max && $rangeHigh > $max)) {
generateNumbers($rangeLow, $rangeHigh);
};
//Check if across the lower edge of the generated range
if ($rangeLow < $min && $rangeHigh > $min && $rangeHigh < $max) {
generateNumbers($rangeLow, $min - 1);
};
//Check if across the upper edge of the generated range
if ($rangeLow > $min && $rangeLow < $max && $rangeHigh > $max) {
generateNumbers($max + 1, $rangeHigh);
};
}
//Now just sort the array
print_r($numbers);
?>

Categories