3

I have products based on different duration. For example, 1-hour booking is $200, 1 hour and 30 minutes is 280 and additional 30 minutes have $90. I have to find the best possible price according to the duration of the booking. [Minimum booking duration is 1 hour]

Product Duration Price
1 hr 60 min $200
1 hr 30 m 90 min $280
30 m 30 min $90

*$90 for every additional 30 min which is not the perfect sum of 1 and 1hr 30mins hours

My Solution

  1. Best scenario: If there is a product available for the booking duration, return that product.Eg:- 1 Hour booking

  2. Normal Scenario: My solution was to find all unique combinations of products with a duration that has the perfect sum of the total duration. If there are unique combinations, sort the array in ascending order of total sum and the first combination will be the best price. Eg:- 2 Hour booking- We have two possible combinations 1 Hour * 2 = $400 or 1hr 30 min + 30min = $370. Second one best price

  3. Worst Scenario: If there are no unique combinations find all possible combinations with repetition and sort the array to find the lowest price. An example case of the booking will be:- If customer books for 4 Hours there are a lot of possible combination

JS Code from This answer

function f(A, N, r=[], s=N){
  if (s == 0)
    return [r];

  result = [];

  for (let a of A)
    if (a <= s)
      result = result.concat(
        f(A, N, r.slice().concat(a), s-a));

  return result;
}

PHP Code from above JS Code:- [Edited - code not working as expected as js code]

protected function findCombination($products, $duration, $currentDuration, $resultTracker=[]){
    if ($currentDuration == 0)
      return $resultTracker;
    $result = [];      
    foreach ($products as $product){
        if ($product->duration <= $currentDuration){
            array_push($resultTracker,$product);
            array_push($result, $this->findCombination($products, $duration, ($currentDuration - $product->duration),$resultTracker) );
        }
    }
    return $result;
}

What is the best way to approach this problem? What is the best way to find combinations in PHP? Thanks in advance

Other StackOverflow questions

Shafeeque OM
  • 65
  • 1
  • 8
  • What have you tried so far? Where are you stuck? As you haven't shared any code, it's pretty difficult to provide help – Nico Haase Oct 07 '21 at 08:24
  • 1
    Your worst scenario is not a good option - there may be huge numbers of permutations. Better would a method that gets the price/minute of each product, then adds the best value (ie. lowest price / minute) product that will not take you, in aggregated, to over the total time. TBH, that would probably also do away with your normal scenario too. – Giles Bennett Oct 07 '21 at 08:55
  • @GilesBennett That's a great suggestion, in case of large duration there will be a repetition of products (Eg:- 4 Hour best price will be 2 x 90mins + 1 x 60 min). We can't always say that adding a min duration product will give the best price. – Shafeeque OM Oct 07 '21 at 10:38
  • I think the question miss some requirements. For example, for duration=1h why shiuld you choose product "1h" which costs $200 over 2*"30m" which would cost just $180? – Daniels118 Oct 07 '21 at 21:10
  • Yeah, I have edited tḫe question, every additional 30min cost $90. – Shafeeque OM Oct 09 '21 at 05:16
  • Can you purchase 30 minutes? If so why does buying 2 30 minute blocks cost you less than buying 1 hour? – gview Oct 09 '21 at 05:49
  • There will be a minimum booking time. In these case, it is 1 hour. I can be changed to any value. – Shafeeque OM Oct 09 '21 at 06:03

1 Answers1

0

Do it this way:

  1. Sort bookings by highest duration/price ratio (descending order), so that the most convenient bookings are the first in the array;
  2. Select the first booking whose duration is higher than minimum allowed duration and less than the target duration;
  3. until you reach the target duration, push the first booking whose duration summed to the previous ones doesn't exceed the target duration, discarding higher absolute prices.

``

class Booking {
    public $name;
    public $duration;
    public $price;
    public $ratio;
    
    public function __construct($name, $duration, $price) {
        $this->name = $name;
        $this->duration = $duration;
        $this->price = $price;
        $this->ratio = $duration / $price;
    }
    
    public function __toString() {
        return $this->name.' ($'.$this->price.')';
    }
}

function findBest($bookings, $minBuyTime, $targetTime) {
    //Sort the bookings based on the duration/price ratio, in descending order (most convenient first)
    usort($bookings, function($a, $b) {
        if ($a->ratio < $b->ratio) return  1;
        if ($a->ratio > $b->ratio) return -1;
        return 0;
    });
    //echo implode(', ', $bookings),'<br/>';
    $result = array();
    $totalTime = 0;
    //Choose the first booking excluding the ones shorter than minBuyTime
    foreach ($bookings as $booking) {
        if ($booking->duration >= $minBuyTime) {
            $better = $booking;
            break;
        }
    }
    //If there is a cheaper booking then choose it (but still exclude the ones shorter than minBuyTime)
    $i = 0;
    while ($totalTime + $better->duration > $targetTime && ++$i < count($bookings)) {
        $booking = $bookings[$i];
        if ($booking->duration >= $minBuyTime && $booking->price < $better->price) $better = $booking;
    }
    $result[] = $better;
    $totalTime += $better->duration;
    //Add additional booking until the total time reaches the target time
    while ($totalTime < $targetTime) {
        $i = 0;
        $better = $bookings[0];
        while ($totalTime + $better->duration > $targetTime && ++$i < count($bookings)) {
            $booking = $bookings[$i];
            if ($booking->price < $better->price) $better = $booking;
        }
        $result[] = $better;
        $totalTime += $better->duration;
    }
    return $result;
}

Testing 3 different target durations with 3 different booking costs:

function test($bookings, $minBuyTime, $targetTime) {
    $best = findBest($bookings, $minBuyTime, $targetTime);
    $totalTime = 0;
    $totalPrice = 0;
    foreach ($best as $booking) {
        $totalTime += $booking->duration;
        $totalPrice += $booking->price;
    }
    echo $targetTime,' min: [',implode(', ', $best), '], ',$totalTime,' min - $',$totalPrice,'<br/>';
}

$minBuyTime = 60;
$bookings = array(
    new Booking('1 hr', 60, 200),
    new Booking('1 hr 30m', 90, 280),
    new Booking('30 m', 30, 90)
);
echo implode(', ', $bookings),'<br/>';
test($bookings, $minBuyTime, 60);
test($bookings, $minBuyTime, 120);
test($bookings, $minBuyTime, 240);
echo '<br/>';

$minBuyTime = 60;
$bookings = array(
    new Booking('1 hr', 60, 200),
    new Booking('1 hr 30m', 90, 250),
    new Booking('30 m', 30, 90)
);
echo implode(', ', $bookings),'<br/>';
test($bookings, $minBuyTime, 60);
test($bookings, $minBuyTime, 120);
test($bookings, $minBuyTime, 240);
echo '<br/>';

$minBuyTime = 60;
$bookings = array(
    new Booking('1 hr', 60, 200),
    new Booking('1 hr 30m', 90, 250),
    new Booking('30 m', 30, 110)
);
echo implode(', ', $bookings),'<br/>';
test($bookings, $minBuyTime, 60);
test($bookings, $minBuyTime, 120);
test($bookings, $minBuyTime, 240);
echo '<br/>';

The above will output:

1 hr ($200), 1 hr 30m ($280), 30 m ($90)
60 min: [1 hr ($200)], 60 min - $200
120 min: [1 hr 30m ($280), 30 m ($90)], 120 min - $370
240 min: [1 hr 30m ($280), 30 m ($90), 30 m ($90), 30 m ($90), 30 m ($90), 30 m ($90)], 240 min - $730

1 hr ($200), 1 hr 30m ($250), 30 m ($90)
60 min: [1 hr ($200)], 60 min - $200
120 min: [1 hr 30m ($250), 30 m ($90)], 120 min - $340
240 min: [1 hr 30m ($250), 1 hr 30m ($250), 30 m ($90), 30 m ($90)], 240 min - $680

1 hr ($200), 1 hr 30m ($250), 30 m ($110)
60 min: [1 hr ($200)], 60 min - $200
120 min: [1 hr 30m ($250), 30 m ($110)], 120 min - $360
240 min: [1 hr 30m ($250), 1 hr 30m ($250), 1 hr ($200)], 240 min - $700
Daniels118
  • 1,149
  • 1
  • 8
  • 17