Skip to content
Snippets Groups Projects
Commit 4191dfdb authored by Romain Vuillemot's avatar Romain Vuillemot
Browse files

Create 05-programming-strategies-exercises.ipynb

parent f7f72a6d
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id:5437be6e tags:
NAME:
%% Cell type:markdown id:e58599e3-9ab7-4d43-bb22-aeccade424ce tags:
# UE5 Fundamentals of Algorithms
# Lab 5: Programming strategies: Brute force and greedy
%% Cell type:markdown id:691b3c38-0e83-4bb2-ac90-ef76d2dd9a7a tags:
---
%% Cell type:markdown id:3a1dbf76-c34e-4859-ab46-6ff8ba5d4f19 tags:
<details style="border: 1px">
<summary> How to use those notebooks</summary>
For each of the following questions:
- In the `# YOUR CODE HERE` cell, remove `raise NotImplementedError()` to write your code
- Write an example of use of your code or make sure the given examples and tests pass
- Add extra tests in the `#Tests` cell
</details>
%% Cell type:markdown id:8f128fee-a1d6-4106-b997-0ed27b5ed91d tags:
# Exercice 1: Knapsack problem
The goal of the knapsack problem is to select the maximum number of items (each with weights, coming from a limited set) that can be packed in a knapsack, which has a limited capacity (eg a total weight). The problem can be formulated as follows:
$\max \sum_{i=1}^n v_i x_i$
- **$x_i ∈{0,1}$** the binary variable to pick item $i$ from $n$ weights
So that:
$\sum_{i=1}^n w_i x_i \leq W \quad \text{and} \quad x_i \in \{0,1,2,\dots,n-1\}$
- **W:** The maximum weight capacity of the knapsack
- **w:** A list containing the $n$ weights of the available items
We first assume that each item can only be picked 1 time at most. Here is an example of solution:
%% Cell type:code id:3c4b8bae-2220-4fff-b3fc-189585e25fb1 tags:
``` python
weights = [4, 5, 7, 8] # limited set of items with weights
capacity = 17 # maximum capacity of the knapsack
selected_items = [1, 1, 0, 1] # x_i items: 1 item of weight 4, etc.
```
%% Cell type:markdown id:72b396ed-8504-4d5d-a16d-d1d9b21b09c7 tags:
The above selection of items is correct (and optimal) as it sums equals the total capacity weight:
$(4 \times 1) + (5 \times 1) + (7 \times 0) + (8 \times 1) = 4 + 5 + 0 + 8 = 17$
%% Cell type:markdown id:c49a5db7-16d5-4809-827a-c167af01d8fd tags:
## 1.1 Check if a solution is correct
%% Cell type:markdown id:5379ed20-2701-4475-b3a2-baaf7b0054c6 tags:
Write a function that checks if a given list of selected items fits into the knapsack (but it is not necessarily optimal):
%% Cell type:code id:e114caea-cf9d-4fe3-bd01-8a747667f6da tags:
``` python
def check_knapsack_solution(weights, capacity, selected_items):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:5ef1cc51-63e7-404f-8e05-f450ea264797 tags:
``` python
assert check_knapsack_solution(weights, capacity, selected_items) # it fits
```
%% Cell type:markdown id:e1ea24d2-df15-4b8a-923b-4006d0911a81 tags:
## 1.2 Brute force approach
%% Cell type:markdown id:d8552dac-ea9d-4ded-a57f-da86dbcfff47 tags:
A brute force solution to the Knapsack problem is trying **all possible combinations of items** and check if they fit. You may implement such solution as follows:
- Calculate the combination of items selections and their total weight
- Check if each combination is within the knapsack capacity
- Return the best (valid) combination that fits within the capacity
You may use the Cartesian product ([doc](https://docs.python.org/3/library/itertools.html#itertools.product)) to create the list of all possible selections by permutting $x_i$. This means to create an array of permutation of selected items (`0` meaning the item is not selected, `1` it is selected). So `(0, 0, 0, 0)` means we do not pick any item, and `(1, 1, 1, 1)` we pick them all. Here is the code for the product:
%% Cell type:code id:e688c41f-ef8c-413b-b86a-c116c457d8b1 tags:
``` python
from itertools import product
list(product([0, 1], repeat=len(weights)))
```
%% Cell type:markdown id:aa8969ef-8ec2-4765-979a-d0f3a363943f tags:
The brute force function will return a tuple with both the sum and the list of $x_i$ as a `Tuple`.
%% Cell type:code id:4b9a42ff-416b-45ef-b7e9-de1ed6f6a478 tags:
``` python
def brute_force_knapsack(weights, capacity):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:54d8d9d5-e996-4502-a9ac-0fcf9e9072bf tags:
``` python
# Tests
assert brute_force_knapsack(weights, capacity) == (17, (1, 1, 0, 1)) # (sum, (x_i))
```
%% Cell type:markdown id:d3142897-f48c-4d61-8322-dec75b79da50 tags:
To better interpret the solution, write a function that converts the $x_i$ into a list of weights:
%% Cell type:code id:6450ebd1-b2cb-4c13-be89-c8c1727030e0 tags:
``` python
def get_used_weights(weights, best_combination):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:24f10578-4d4e-4921-92f1-4db5073e64ec tags:
``` python
assert get_used_weights(weights, (1, 1, 0, 1)) == [4, 5, 8]
```
%% Cell type:markdown id:9786bab1-ef77-4a8c-9988-19b1dea15620 tags:
## 1.3 Greedy approach
Now use a greedy approach to solve this problem.
**Important:** we now have **unlimited** number of items to take from. You may use the following steps:
1. Start by initializing a list containing the indices of all remaining items
2. Begin with an empty knapsack, having a total weight of 0
3. In each iteration, select the item with the least weight from the remaining items that can fit into the current knapsack capacity
4. Add this item to the knapsack and remove it from the list of available items
5. After completing the process, return the total weight of the knapsack and the list of items selected.
Tip: to implement the greedy hypothesis (picking the items with lowest weight first), you may sort the indice list of ascending weights using:
```python
sorted_indices = sorted(range(n), key=lambda i: weights[i])
```
The function should return the result in a format similar to the brute force.
%% Cell type:code id:14b9d2b0-813b-4c6c-91e4-a2049274726d tags:
``` python
def greedy_knapsack(weights, W):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:92be4d4b-6ac9-4ada-801d-d9ef21a67b92 tags:
``` python
weights = [2, 3, 5]
capacity = 11
greedy_knapsack(weights, capacity)
```
%% Cell type:code id:71cf817f-34f9-4c52-ab84-0902964bfd62 tags:
``` python
assert brute_force_knapsack(weights, capacity)[0] == greedy_knapsack(weights, capacity)[0]
```
%% Cell type:code id:87bf1ffe-9e68-4f64-a197-16364a11ee47 tags:
``` python
weights = [2, 3, 6]
capacity = 11
assert brute_force_knapsack(weights, capacity)[0] > greedy_knapsack(weights, capacity)[0]
```
%% Cell type:markdown id:e87958bc-bacc-4dd6-85b5-45af8cb59e67 tags:
As you can see the brute force outperfoms the greedy for this particular scenario.
%% Cell type:markdown id:e7602801-5fb5-4904-bafa-810d9787c6f5 tags:
## 1.4 Greedy with limited availability
Now write a new version of the Greedy function but with a limit `max_quantity` variable that limits the quantity of items that can be picked. Eg with `max_quantity = 2` each item can only be picked twice.
%% Cell type:code id:755116cb-456e-48ad-8dfe-bd72617488d9 tags:
``` python
def greedy_knapsack_with_max_quantity(weights, capacity, max_quantity):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:cae704d4-4904-4b6c-89de-3029bd1b5fbd tags:
``` python
weights = [4, 5, 7, 8] # item weights
capacity = 15 # knapsack capacity
max_quantity = 2 # maximum quantity an item can be selected (for each item)
greedy_knapsack_with_max_quantity(weights, capacity, max_quantity)
```
%% Cell type:markdown id:e5b7304d-7b64-42df-9465-8f4491525a8b tags:
# Exercice 2: Organize a schedule
Write a greedy algorithm that returns a list of time slots that do not overlap. The time slots are provided using the `(start, end)` format where start/end are integers (instead of time).
%% Cell type:markdown id:2698e92a-188d-4dbf-8e76-f7b7374db2b0 tags:
Let's begin by writing a function that checks that a given schedule is OK:
- Each time slot has `start < end` and `duration > 0`
- No time slot overlaps
%% Cell type:code id:95c36e13-10c5-4922-9211-f8fc269de372 tags:
``` python
def check_schedule_solution(schedule):
# YOUR CODE HERE
raise NotImplementedError()
return True
```
%% Cell type:code id:90a4a8a7-4ee7-4bca-9bae-e8372e9bc047 tags:
``` python
assert check_schedule_solution([(0, 2), (2, 4)])
assert not check_schedule_solution([(0, 3), (2, 4)])
```
%% Cell type:markdown id:b8219a51-e70c-4885-941f-287bff3eadfc tags:
Now write the greedy algorithm that picks time slots without overlaps. In this question you may prioritize the ones that end last, so you may sort by [reverse order](https://docs.python.org/3/howto/sorting.html). Eg:
```python
intervals.sort(key=lambda x: x[1])
````
%% Cell type:code id:e0ed0841-1255-4bdb-b7f8-e689c2a953af tags:
``` python
def interval_scheduling(intervals):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:d6a50280-f016-4686-8515-1b4a136c0fd9 tags:
``` python
interval_scheduling([(0, 2), (2, 4), (1, 3)])
```
%% Cell type:code id:bd38d673-077d-44b1-b81c-ac7448f5c01c tags:
``` python
assert interval_scheduling([(0, 2), (2, 4), (1, 3)]) == [(0, 2), (2, 4)]
```
%% Cell type:markdown id:c142c3b3-2f01-48d0-8596-601bb133542b tags:
Write another version of the greedy algorithm that prioritizes the time slots that are the longest.
%% Cell type:code id:095836b8-2612-4d58-a9ce-b7968489418c tags:
``` python
def interval_scheduling_longest(intervals):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:afcf3a69-aa36-4b71-93a8-218337ed88b9 tags:
``` python
assert interval_scheduling_longest([(0, 4), (3, 5), (5, 7)]) == [(0, 4), (5, 7)]
assert interval_scheduling([(0, 4), (3, 5), (5, 7)]) == interval_scheduling_longest(([(0, 4), (3, 5), (5, 7)]))
```
%% Cell type:markdown id:a559e4db-9ef2-4d20-bb9f-cb638d8c1f24 tags:
%% Cell type:code id:46947633-3f5f-420b-b416-500a0dab4fcb tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment