Given two arrays; $births containing a list of birth years indicating when someone was born, and $deaths containing a list of death years indicating when someone died, how can we find the year on which the population was highest?
For example given the following arrays:
$births = [1984, 1981, 1984, 1991, 1996];
$deaths = [1991, 1984];
The year on which the population was highest should be 1996, because 3 people were alive during that year, which was the highest population count of all those years.
Here's the running math on that:
| Birth | Death | Population |
|-------|-------|------------|
| 1981 | | 1 |
| 1984 | | 2 |
| 1984 | 1984 | 2 |
| 1991 | 1991 | 2 |
| 1996 | | 3 |
Assumptions
We can safely assume that the year on which someone is born the population can increase by one and the year on which someone died the population can decrease by one. So in this example, 2 people were born on 1984 and 1 person died on 1984, meaning the population increased by 1 on that year.
We can also safely assume that the number of deaths will never exceed the number of births and that no death can occur when the population is at 0.
We can also safely assume that the years in both $deaths and $births will never be negative or floating point values (they're always positive integers greater than 0).
We cannot assume that the arrays will be sorted or that there won't be duplicate values, however.
Requirements
We must write a function to return the year on which the highest population occurred, given these two arrays as input. The function may return 0, false, "", or NULL (any falsey value is acceptable) if the input arrays are empty or if the population was always at 0 throughout. If the highest population occurred on multiple years the function may return the first year on which the highest population was reached or any subsequent year.
For example:
$births = [1997, 1997, 1997, 1998, 1999];
$deaths = [1998, 1999];
/* The highest population was 3 on 1997, 1998 and 1999, either answer is correct */
Additionally, including the Big O of the solution would be helpful.
My best attempt at doing this would be the following:
function highestPopulationYear(Array $births, Array $deaths): Int {
sort($births);
sort($deaths);
$nextBirthYear = reset($births);
$nextDeathYear = reset($deaths);
$years = [];
if ($nextBirthYear) {
$years[] = $nextBirthYear;
}
if ($nextDeathYear) {
$years[] = $nextDeathYear;
}
if ($years) {
$currentYear = max(0, ...$years);
} else {
$currentYear = 0;
}
$maxYear = $maxPopulation = $currentPopulation = 0;
while(current($births) !== false || current($deaths) !== false || $years) {
while($currentYear === $nextBirthYear) {
$currentPopulation++;
$nextBirthYear = next($births);
}
while($currentYear === $nextDeathYear) {
$currentPopulation--;
$nextDeathYear = next($deaths);
}
if ($currentPopulation >= $maxPopulation) {
$maxPopulation = $currentPopulation;
$maxYear = $currentYear;
}
$years = [];
if ($nextBirthYear) {
$years[] = $nextBirthYear;
}
if ($nextDeathYear) {
$years[] = $nextDeathYear;
}
if ($years) {
$currentYear = min($years);
} else {
$currentYear = 0;
}
}
return $maxYear;
}
The algorithm above should work in polynomial time given it is at worst O(((n log n) * 2) + k) where n is number of elements to be sorted from each array and k is number of birth years (since we know that k is always k >= y) where y is number of death years. However, I'm not sure if there is a more efficient solution.
My interests are purely in an improved Big O of computational complexity upon the existing algorithm. Memory complexity is of no concern. Nor is the runtime optimization. At least it's not a primary concern. Any minor/major runtime optimizations are welcome, but not the key factor here.
We can solve this in linear time with bucket sort. Let's say the size of the input is n, and the range of years is m.
O(n): Find the min and max year across births and deaths.
O(m): Create an array of size max_yr - min_yr + 1, ints initialized to zero.
Treat the first cell of the array as min_yr, the next as min_yr+1, etc...
O(n): Parse the births array, incrementing the appropriate index of the array.
arr[birth_yr - min_yr] += 1
O(n): Ditto for deaths, decrementing the appropriate index of the array.
arr[death_yr - min_yr] -= 1
O(m): Parse your array, keeping track of the cumulative sum and its max value.
The largest cumulative maximum is your answer.
The running time is O(n+m), and the additional space needed is O(m).
This is a linear solution in n if m is O(n); i.e., if the range of years isn't growing more quickly than the number of births and deaths. This is almost certainly true for real world data.
I think we can have O(n log n) time with O(1) additional space by first sorting, then maintaining a current population and global maximum as we iterate. I tried to use the current year as a reference point but the logic still seemed a bit tricky so I'm not sure it's completely worked out. Hopefully, it can give an idea of the approach.
JavaScript code (counterexamples/bugs welcome)
function f(births, deaths){
births.sort((a, b) => a - b);
deaths.sort((a, b) => a - b);
console.log(JSON.stringify(births));
console.log(JSON.stringify(deaths));
let i = 0;
let j = 0;
let year = births[i];
let curr = 0;
let max = curr;
while (deaths[j] < births[0])
j++;
while (i < births.length || j < deaths.length){
while (year == births[i]){
curr = curr + 1;
i = i + 1;
}
if (j == deaths.length || year < deaths[j]){
max = Math.max(max, curr);
console.log(`year: ${ year }, max: ${ max }, curr: ${ curr }`);
} else if (j < deaths.length && deaths[j] == year){
while (deaths[j] == year){
curr = curr - 1;
j = j + 1;
}
max = Math.max(max, curr);
console.log(`year: ${ year }, max: ${ max }, curr: ${ curr }`);
}
if (j < deaths.length && deaths[j] > year && (i == births.length || deaths[j] < births[i])){
year = deaths[j];
while (deaths[j] == year){
curr = curr - 1;
j = j + 1;
}
console.log(`year: ${ year }, max: ${ max }, curr: ${ curr }`);
}
year = births[i];
}
return max;
}
var input = [
[[1997, 1997, 1997, 1998, 1999],
[1998, 1999]],
[[1, 2, 2, 3, 4],
[1, 2, 2, 5]],
[[1984, 1981, 1984, 1991, 1996],
[1991, 1984, 1997]],
[[1984, 1981, 1984, 1991, 1996],
[1991, 1982, 1984, 1997]]
]
for (let [births, deaths] of input)
console.log(f(births, deaths));
If the year range, m, is on the order of n, we could store the counts for each year in the range and have O(n) time complexity. If we wanted to get fancy, we could also have O(n * log log m) time complexity, by using a Y-fast trie that allows successor lookup in O(log log m) time.
First aggregate the births and deaths into a map (year => population change), sort that by key, and calculate the running population over that.
This should be approximately O(2n + n log n), where n is the number of births.
$births = [1984, 1981, 1984, 1991, 1996];
$deaths = [1991, 1984];
function highestPopulationYear(array $births, array $deaths): ?int
{
$indexed = [];
foreach ($births as $birth) {
$indexed[$birth] = ($indexed[$birth] ?? 0) + 1;
}
foreach ($deaths as $death) {
$indexed[$death] = ($indexed[$death] ?? 0) - 1;
}
ksort($indexed);
$maxYear = null;
$max = $current = 0;
foreach ($indexed as $year => $change) {
$current += $change;
if ($current >= $max) {
$max = $current;
$maxYear = $year;
}
}
return $maxYear;
}
var_dump(highestPopulationYear($births, $deaths));
I solved this problem with a memory requirement of O(n+m) [in worst case, best case O(n)]
and, time complexity of O(n logn).
Here, n & m are the length of births and deaths arrays.
I don't know PHP or javascript. I've implemented it with Java and the logic is very simple. But I believe my idea can be implemented in those languages as well.
Technique Details:
I used java TreeMap structure to store births and deaths records.
TreeMap inserts data sorted (key based) as (key, value) pair, here key is the year and value is the cumulative sum of births & deaths (negative for deaths).
We don't need to insert deaths value that happened after the highest birth year.
Once the TreeMap is populated with the births & deaths records, all the cumulative sums are updated and store the maximum population with year as it progressed.
Sample input & output: 1
Births: [1909, 1919, 1904, 1911, 1908, 1908, 1903, 1901, 1914, 1911, 1900, 1919, 1900, 1908, 1906]
Deaths: [1910, 1911, 1912, 1911, 1914, 1914, 1913, 1915, 1914, 1915]
Year counts Births: {1900=2, 1901=1, 1903=1, 1904=1, 1906=1, 1908=3, 1909=1, 1911=2, 1914=1, 1919=2}
Year counts Birth-Deaths combined: {1900=2, 1901=1, 1903=1, 1904=1, 1906=1, 1908=3, 1909=1, 1910=-1, 1911=0, 1912=-1, 1913=-1, 1914=-2, 1915=-2, 1919=2}
Yearwise population: {1900=2, 1901=3, 1903=4, 1904=5, 1906=6, 1908=9, 1909=10, 1910=9, 1911=9, 1912=8, 1913=7, 1914=5, 1915=3, 1919=5}
maxPopulation: 10
yearOfMaxPopulation: 1909
Sample input & output: 2
Births: [1906, 1901, 1911, 1902, 1905, 1911, 1902, 1905, 1910, 1912, 1900, 1900, 1904, 1913, 1904]
Deaths: [1917, 1908, 1918, 1915, 1907, 1907, 1917, 1917, 1912, 1913, 1905, 1914]
Year counts Births: {1900=2, 1901=1, 1902=2, 1904=2, 1905=2, 1906=1, 1910=1, 1911=2, 1912=1, 1913=1}
Year counts Birth-Deaths combined: {1900=2, 1901=1, 1902=2, 1904=2, 1905=1, 1906=1, 1907=-2, 1908=-1, 1910=1, 1911=2, 1912=0, 1913=0}
Yearwise population: {1900=2, 1901=3, 1902=5, 1904=7, 1905=8, 1906=9, 1907=7, 1908=6, 1910=7, 1911=9, 1912=9, 1913=9}
maxPopulation: 9
yearOfMaxPopulation: 1906
Here, deaths occurred (1914 & later) after the last birth year 1913, was not counted at all, that avoids unnecessary computations.
For a total of 10 million data (births & deaths combined) and over 1000 years range, the program took about 3 sec. to finish.
If same size data with 100 years range, it took 1.3 sec.
All the inputs are randomly taken.
$births = [1984, 1981, 1984, 1991, 1996];
$deaths = [1991, 1984];
$years = array_unique(array_merge($births, $deaths));
sort($years);
$increaseByYear = array_count_values($births);
$decreaseByYear = array_count_values($deaths);
$populationByYear = array();
foreach ($years as $year) {
$increase = $increaseByYear[$year] ?? 0;
$decrease = $decreaseByYear[$year] ?? 0;
$previousPopulationTally = end($populationByYear);
$populationByYear[$year] = $previousPopulationTally + $increase - $decrease;
}
$maxPopulation = max($populationByYear);
$maxPopulationYears = array_keys($populationByYear, $maxPopulation);
$maxPopulationByYear = array_fill_keys($maxPopulationYears, $maxPopulation);
print_r($maxPopulationByYear);
This will account for the possibility of a tied year, as well as if a year of someone's death does not correspond to someone's birth.
Memory wise it is to keep currentPopulation and currentYear calculated. Starting by sorting both $births and $deaths arrays is a very good point, because bubble sorting is not that heavy task, yet allows to cut some corners:
<?php
$births = [1997, 1999, 2000];
$deaths = [2000, 2001, 2001];
function highestPopulationYear(array $births, array $deaths): Int {
// sort takes time, but is neccesary for futher optimizations
sort($births);
sort($deaths);
// first death year is a first year where population might decrase
// sorfar max population
$currentYearComputing = $deaths[0];
// year before first death has potential of having the biggest population
$maxY = $currentYearComputing-1;
// calculating population at the begining of the year of first death, start maxPopulation
$population = $maxPop = count(array_splice($births, 0, array_search($deaths[0], $births)));
// instead of every time empty checks: `while(!empty($deaths) || !empty($births))`
// we can control a target time. It reserves a memory, but this slot is decreased
// every iteration.
$iterations = count($deaths) + count($births);
while($iterations > 0) {
while(current($births) === $currentYearComputing) {
$population++;
$iterations--;
array_shift($births); // decreasing memory usage
}
while(current($deaths) === $currentYearComputing) {
$population--;
$iterations--;
array_shift($deaths); // decreasing memory usage
}
if ($population > $maxPop) {
$maxPop = $population;
$maxY = $currentYearComputing;
}
// In $iterations we have a sum of birth/death events left. Assuming all
// are births, if this number added to currentPopulation will never exceed
// current maxPoint, we can break the loop and save some time at cost of
// some memory.
if ($maxPop >= ($population+$iterations)) {
break;
}
$currentYearComputing++;
}
return $maxY;
}
echo highestPopulationYear($births, $deaths);
not really keen on diving into Big O thing, left it to you.
Also, if you rediscover currentYearComputing every loop, you can change loops into if statements and leave with just one loop.
while($iterations > 0) {
$changed = false;
if(current($births) === $currentYearComputing) {
// ...
$changed = array_shift($births); // decreasing memory usage
}
if(current($deaths) === $currentYearComputing) {
// ...
$changed = array_shift($deaths); // decreasing memory usage
}
if ($changed === false) {
$currentYearComputing++;
continue;
}
I fill very comfortable of this solution, the complexity Big O is n + m
<?php
function getHighestPopulation($births, $deaths){
$max = [];
$currentMax = 0;
$tmpArray = [];
foreach($deaths as $key => $death){
if(!isset($tmpArray[$death])){
$tmpArray[$death] = 0;
}
$tmpArray[$death]--;
}
foreach($births as $k => $birth){
if(!isset($tmpArray[$birth])){
$tmpArray[$birth] = 0;
}
$tmpArray[$birth]++;
if($tmpArray[$birth] > $currentMax){
$max = [$birth];
$currentMax = $tmpArray[$birth];
} else if ($tmpArray[$birth] == $currentMax) {
$max[] = $birth;
}
}
return [$currentMax, $max];
}
$births = [1997, 1997, 1997, 1998, 1999];
$deaths = [1998, 1999];
print_r (getHighestPopulation($births, $deaths));
?>
One of most simple and clear approach for your problem.
$births = [1909, 1919, 1904, 1911, 1908, 1908, 1903, 1901, 1914, 1911, 1900, 1919, 1900, 1908, 1906];
$deaths = [1910, 1911, 1912, 1911, 1914, 1914, 1913, 1915, 1914, 1915];
/* for generating 1 million records
for($i=1;$i<=1000000;$i++) {
$births[] = rand(1900, 2020);
$deaths[] = rand(1900, 2020);
}
*/
function highestPopulationYear(Array $births, Array $deaths): Int {
$start_time = microtime(true);
$population = array_count_values($births);
$deaths = array_count_values($deaths);
foreach ($deaths as $year => $death) {
$population[$year] = ($population[$year] ?? 0) - $death;
}
ksort($population, SORT_NUMERIC);
$cumulativeSum = $maxPopulation = $maxYear = 0;
foreach ($population as $year => &$number) {
$cumulativeSum += $number;
if($maxPopulation < $cumulativeSum) {
$maxPopulation = $cumulativeSum;
$maxYear = $year;
}
}
print " Execution time of function = ".((microtime(true) - $start_time)*1000)." milliseconds";
return $maxYear;
}
print highestPopulationYear($births, $deaths);
output:
1909
complexity:
O(m + log(n))
I am trying to calculate price for number of days from 1-21 based on date.
HomeController
$Sql = ' SELECT DISTINCT
a.property_id, a.date, a.minimum_stay,
a.maximum_stay,a.quantity,
a.arrival_allowed,a.departure_allowed,
p.duration, p.persons, p.amount,
p.extra_person_price, p.minimum_stay AS price_minimum_stay,
p.maximum_stay AS price_maximum_stay, p.weekdays,
p.period_till, p.period_from,
datediff(p.period_till, p.period_from) AS number_of_days
FROM availabilities AS a
JOIN prices AS p
ON a.property_id=p.property_id
WHERE a.minimum_stay >0
AND a.maximum_stay < 22
AND a.date >= p.period_from
AND a.date <= p.period_till
';
$Stm = $this->getEntityManager()->getConnection()->prepare($Sql);
$Stm->execute();
return $Stm->fetchAll(PDOConnection::FETCH_ASSOC);
public function CalculatePrice($persons, $extra_person_price, $date, $amount, $duration, $period_till, $period_from)
{
//loop through persons
foreach ($persons as $person) {
//calculate price for persons
if ($person > 1) {
$amount += $person * $extra_person_price;
}
//array to link parameters with database fields
$tmp = array(
"date" => $date,
"person" => $person,
"price_person" => number_format($amount / 100, 2, '.', ',')
);
//loop through $tmp an add value from 2 to 21 to day an add this to $tmp array with calculated value
//$x=$number_of_days;
//$days = (strtotime($period_till) - strtotime($period_from)) / (60 * 60 * 24);
for ($x = 1; $x <= 21; ++$x) {
if ($x >1) {
$tmp["day$x"] = $tmp["day".($x-1)] + number_format($amount / 100, 2, '.', ',');
//number_format(($amount * $x) / 100, 2, '.', ',');
} else {
$tmp["day$x"] = "0.00";
}
$price_per_person[] = $tmp;
}
return $price_per_person;
}
I am also calculating price for number of persons but that part is good. Right now I have made a for loop and stored numbers from 1 to 21 for number of days see my second for loop in function CalculatePrice. But this part not good I need to calculate this based on date. for example:
Regularly price for most of days is 123 Euro. let say on 3 September a day cost 250 euro and on 7 September it cost 300 Euro. So let say A person want to stay for 5 days and he arrives at 3 September so the calculation will be: 250 + 123 + 123 + 123 + 300 = 919 Euro.
But I need this to be based on date. I have tried with period_from and period_til but so far no luck. Can someone give an example or some useful hints how I can do this in a for loop like my second for loop.
I have solved this question based on the theory T. Abdelmalek gave me. I made an array and stored date and price of each date in and loped through. The answer has been deleted somehow I don't know how to mark as solved
You can use date_diff like that:
First date = $leave->getLeaveFrom();
Second date = $leave->getLeaveTo();
$diff = date_diff($leave->getLeaveFrom(),$leave->getLeaveTo());
Results = var_dump($diff);
(Language PHP - This question is for any language, particularly I'm using PHP)
For example you have an array of numbers like:
$q = array( 1, 2, 3, 4, 5, ... ); // ... mean you can give more numbers
$i = 0;
$currentAverage = 0;
while ($i < count( $q )) {
$currentAverage = ($currentAverage + $q[$i]) / 2; // this is my try
$i++;
}
echo "The final average is: " . $currentAverage . "<br/>";
Obviusly, you can divide by count( $q ) the sum, but that's not the idea.
I hope you can help me! thanks.
You can't calculate an "incremental" mean average without knowing the total number of items make up that average.
For example, if you have 10 items that average 5 and you want to add the next item, X, you have to give the appropriate "weight" to the newly added item.
For example, to get the next average, you would do
(currentAverage * currentNumberOfItems + X) / (currentNumberOfItems + 1)
If we say X is 7, the new average would be
(5 * 10 + 7) / (10 + 1)
= (50 + 7) / 11
= 57 / 11
= 5.181818182
It is impossible to do this calculation without knowing the current number of items that make up the average (10) beforehand
To show you this working in an incremental fashion, here is a for loop that keeps track of the average as the loop is running
$xs = [1,2,3,4,5];
$average = $xs[0];
for ($count = 1; $count < count($xs); $count++) {
echo sprintf("average: %0.3f, count: %d" . PHP_EOL, $average, $count);
$average = ($average * $count + $xs[$count]) / ($count + 1);
}
average: 1.000, count: 1
average: 1.500, count: 2
average: 2.000, count: 3
average: 2.500, count: 4
Could use this:
$q = array( 1, 2, 3, 4, 5, ... ); // ... mean you can give more numbers
$i = 0;
$currentAverage = 0;
while ($i < count( $q )) {
$sliceArr = array_slice($q, 0, $i+1);
$currentAverage = array_sum($sliceArr) / count($sliceArr);
$i++;
}
echo "The final average is: " . $currentAverage . "<br/>";
I'm making a game in PHP (don't ask lol), and the player has a location which is an integer. There's a travel page and this basically shows a 5x5 tiled map. Each tile is a different part of the player's universe. By clicking on it he can travel there.
Just to give you an idea of the integers behind the map:
11, 12, 13, 14, 15
21, 22, 23, 24, 25
31, 32, 33, 34, 35
41, 42, 43, 44, 45
51, 52, 53, 54, 55
Let's say the player starts at 33(the middle) and I wanted to charge him different rates depending on how far he traveled. So, for example, 1 tile in any direction is a 100 credits, 2 tiles is 200 and so on.
So what I came up with is this. $ol represents the player's current location and $nl is where they are travelling to...
if($ol-11==$nl || $ol-10==$nl || $ol-9==$nl || $ol+1==$nl || $ol+11==$nl || $ol+10==$nl || $ol+9==$nl || $ol-1==$nl || $ol-11==$nl ){
echo "cost 100 credits!";
}
else if($ol-22==$nl || $ol-21==$nl || $ol-20==$nl || $ol-19==$nl || $ol-18==$nl || $ol-8==$nl || $ol+2==$nl || $ol+12==$nl || $ol+22==$nl
|| $ol+21==$nl || $ol+20==$nl || $ol+19==$nl || $ol+18==$nl || $ol+8==$nl || $ol-2==$nl || $ol-12==$nl ){
echo "cost 200 credits!";
}
That's the code for 1 and 2 tile travel. As you can see it's a lengthy statement.
I basically worked out a pattern for the grid I'd set up. For example, travelling up 1 tile would always be -10 of the current tile.
Before I type out any more ridiculously long if statements, is there a neater or more efficient way to do this?
I would use a different method: As the first digit defines the row and the second digit the column, I would split the number in these two digits and use these numbers to determine how many rows and how many columns are being travelled.
So for any position:
$row = floor($tile_value / 10);
$column = $tile_value % 10;
With this it is easy to calculate distances.
Edit: A small example to measure absolute distances:
$row_org = floor($tile_org_value / 10);
$column_org = $tile_org_value % 10;
$row_new = floor($tile_new_value / 10);
$column_new = $tile_new_value % 10;
$row_diff = $row_new - $row_org;
$col_diff = $col_new - $col_org;
$distance = sqrt(pow($row_diff, 2) + pow($col_diff, 2));
As in my comment above, you cannot measure distance in units, since not all points can be reached in a straight line through points.
You need to consider these points to be points (x, y coordinates) on a graph. Then you can get the distance between any 2 points using Pythagoras.
For example, if we consider your top row as being the coordinates (1,1) (1,2) and so on, if the person travels from (1,1) to (4,3), the distance travelled is the square root of 3 (4-1) squared plus 2 (3-1) squared, i.e. sqrt(9+4) = sqrt(13)
I would probably try an array for coordinates. This will allow you to set the initial coordinates. You can then pass new coordinates to the function which will move the position and calculate the cost.
<?php
$array = array( );
//populate the array with 0's
for( $i = 1; $i <= 5; $i++ ) {
for( $j = 1; $j <= 5; $j++ ) {
$array[$i][$j] = 0;
}
}
//set beginning position
$array[3][3] = 1;
function newPosition( $array, $newX, $newY ) {
$oldX = 0;
$oldY = 0;
//locate current position
foreach($array as $key=>$subArray) {
foreach($subArray as $subKey=>$val) {
if($val === 1) {
$oldX = $key;
$oldY = $subKey;
}
}
}
//delete old position
$array[$oldX][$oldY] = 0;
//set new position
$array[$newX][$newY] = 1;
//Calculate x and y difference
$xTravel = abs($oldX - $newX);
$yTravel = abs($oldY - $newY);
//Add x and y difference
$totalTravel = $xTravel + $yTravel;
//Calculate the cost
$totalCost = $totalTravel * 100;
echo "cost $totalCost credits!\n";
return $array;
}
$array = newPosition( $array, 5, 2 );
$array = newPosition( $array, 1, 5 );
$array = newPosition( $array, 1, 5 );
$array = newPosition( $array, 3, 3 );
Output
cost 300 credits!
cost 700 credits!
cost 0 credits!
cost 400 credits!
See the demo
Your code seems legit. You could order the conditions so that the most used ones are first.