From 827168d4c03695b024673200d4e6e20066fc982a Mon Sep 17 00:00:00 2001 From: Bill <25640332+Bilbottom@users.noreply.github.com> Date: Sun, 15 Sep 2024 19:28:20 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20risk=20invasions=20qu?= =?UTF-8?q?estion=20(#36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../challenging-sql-problems.md | 7 +- .../gold/risk-invasions--sample-output.sql | 10 + .../problems/gold/risk-invasions.md | 252 ++++++++++++++++++ .../gold/loan-repayment-schedule.sql | 84 +++--- .../solutions/gold/risk-invasions--duckdb.sql | 85 ++++++ .../solutions/gold/risk-invasions.md | 42 +++ .../solutions/gold/risk-invasions.sql | 19 ++ mkdocs.yml | 2 + 8 files changed, 456 insertions(+), 45 deletions(-) create mode 100644 docs/challenging-sql-problems/problems/gold/risk-invasions--sample-output.sql create mode 100644 docs/challenging-sql-problems/problems/gold/risk-invasions.md create mode 100644 docs/challenging-sql-problems/solutions/gold/risk-invasions--duckdb.sql create mode 100644 docs/challenging-sql-problems/solutions/gold/risk-invasions.md create mode 100644 docs/challenging-sql-problems/solutions/gold/risk-invasions.sql diff --git a/docs/challenging-sql-problems/challenging-sql-problems.md b/docs/challenging-sql-problems/challenging-sql-problems.md index 3a7672b..32bc504 100644 --- a/docs/challenging-sql-problems/challenging-sql-problems.md +++ b/docs/challenging-sql-problems/challenging-sql-problems.md @@ -44,9 +44,10 @@ These require a bit more thinking. Expect to spend a bit of time on these. 1. [Loan repayment schedules](problems/gold/loan-repayment-schedule.md) -2. [Supply chain network](problems/gold/supply-chain-network.md) -3. [Encoding datelist ints](problems/gold/encoding-datelist-ints.md) -4. [Travel plans](problems/gold/travel-plans.md) +2. [Risk invasions](problems/gold/risk-invasions.md) +3. [Supply chain network](problems/gold/supply-chain-network.md) +4. [Encoding datelist ints](problems/gold/encoding-datelist-ints.md) +5. [Travel plans](problems/gold/travel-plans.md) --- diff --git a/docs/challenging-sql-problems/problems/gold/risk-invasions--sample-output.sql b/docs/challenging-sql-problems/problems/gold/risk-invasions--sample-output.sql new file mode 100644 index 0000000..f372066 --- /dev/null +++ b/docs/challenging-sql-problems/problems/gold/risk-invasions--sample-output.sql @@ -0,0 +1,10 @@ +```sql +solution(attackers_remaining, defenders_remaining, likelihood, attackers_win_likelihood, defenders_win_likelihood) as ( + values + (0, 1, 0.1629735937, 0.7334902409, 0.2665097591), + (0, 2, 0.1035361654, 0.7334902409, 0.2665097591), + (1, 0, 0.1164097098, 0.7334902409, 0.2665097591), + (2, 0, 0.2821825544, 0.7334902409, 0.2665097591), + (3, 0, 0.3348979767, 0.7334902409, 0.2665097591) +) +``` diff --git a/docs/challenging-sql-problems/problems/gold/risk-invasions.md b/docs/challenging-sql-problems/problems/gold/risk-invasions.md new file mode 100644 index 0000000..2a8ea21 --- /dev/null +++ b/docs/challenging-sql-problems/problems/gold/risk-invasions.md @@ -0,0 +1,252 @@ +--- +hide: + - tags +tags: + - window function + - recursive CTE + - custom axis +--- + +# Risk invasions 🛡️ + +> [!SUCCESS] Scenario +> +> You work for a company that has built a digital version of [Risk, the board game](). To improve the player experience, the product managers want to add a feature which shows the success likelihood of an invasion. + +> [!NOTE] +> +> If you don't know the invasion rules of Risk, check out the following video (06:34 to 08:45): +> +> - [https://youtu.be/Xo8RSozX6Ac?t=394](https://youtu.be/Xo8RSozX6Ac?t=394) + +> [!QUESTION] +> +> As a proof of concept, write a query to calculate the outcome likelihoods of an invasion where the attacker is committing 8 troops and the defender is committing 6 troops. +> +> Assume that both the attacker and defender commit the maximum available troops that they can with each battle (attackers commit up to 3 troops, defenders commit up to 2 troops), and that the invasion continues until either the attacker or defender has no troops left. +> +> The output should have a row per possible outcome, which is the different combinations of attacker and defender troops remaining after the invasion has finished. +> +> The output should have the columns: +> +> - `attackers_remaining` as the number of troops the attacker has left +> - `defenders_remaining` as the number of troops the defender has left +> - `likelihood` as the probability of that outcome occurring +> - `attackers_win_likelihood` as the probability of the attacker winning the invasion +> - `defenders_win_likelihood` as the probability of the defender winning the invasion +> +> The likelihoods should be calculated to 10 decimal places. +> +> Order the output by `attackers_remaining` and `defenders_remaining`. + +The solution can be found at: + +- [risk-invasions.md](../../solutions/gold/risk-invasions.md) + +A worked example is provided below to help illustrate the invasion steps. + +--- + + +>? INFO: **Sample input** +> +> To not give away any spoilers, we'll tweak the rules for this example. +> +> Assume that attackers can commit up to 2 troops and defenders can commit up to 1 troop. +> +> Calculate the outcome likelihoods of an invasion where the attacker is committing 3 troops and the defender is committing 2 troops. + + +>? INFO: **Sample output** +> +> | attackers_remaining | defenders_remaining | likelihood | attackers_win_likelihood | defenders_win_likelihood | +> |--------------------:|--------------------:|-------------:|-------------------------:|-------------------------:| +> | 0 | 1 | 0.1629735937 | 0.7334902409 | 0.2665097591 | +> | 0 | 2 | 0.1035361654 | 0.7334902409 | 0.2665097591 | +> | 1 | 0 | 0.1164097098 | 0.7334902409 | 0.2665097591 | +> | 2 | 0 | 0.2821825544 | 0.7334902409 | 0.2665097591 | +> | 3 | 0 | 0.3348979767 | 0.7334902409 | 0.2665097591 | +> +--8<-- "docs/challenging-sql-problems/problems/gold/risk-invasions--sample-output.sql" + + +>? TIP: **Hint 1** +> +> Create (recursive) CTEs for: +> +> - All possible outcomes of each die roll +> - All possible outcomes of each battle +> +> ...and calculate the likelihood of each row in each CTE. + + +>? TIP: **Hint 2** +> +> Use a recursive CTE to walk through all the possible outcomes of an invasion, calculating the likelihood of each outcome. + + +>? TIP: **Hint 3** +> +> Don't track battle outcomes as "win/lose". Instead, track the number of attacks won: an attacker with 3 dice against a defender with 2 dice can win 0, 1, or 2 attacks. + +--- + +### Worked example + +To help illustrate the invasion steps, consider the scenario in the **Sample input**. + +#### Battle scenarios + +Given that attackers can commit up to 2 troops and defenders can commit up to 1 troop, each battle has two scenarios: + +1. The attacker commits 2 troops and the defender commits 1 troop. +2. The attacker commits 1 troop and the defender commits 1 troop. + +Note that we should only see the second scenario when the attacker has 1 troop remaining as we assume that the attacker commits the maximum available troops with each battle. + +First, we'll calculate the likelihood of the attacker winning in each scenario. + +##### Attacker commits 2 troops and defender commits 1 troop + +Consider all the outcomes of each dice roll. + +There are 216 (6 \* 6 \* 6) possible outcomes: 6 for the attacker's first die, 6 for the attacker's second die, and 6 for the defender's die. + +The attacker wins if the attacker's highest die is greater than the defender's die. + +- When the defender rolls a 6, the attacker always loses: this is 36 of the outcomes. +- When the defender rolls a 5, the attacker wins if the attacker rolls a 6: this is 11 of the outcomes, and the defender wins in the other 25 for this defender roll. +- When the defender rolls a 4, the attacker wins if the attacker rolls a 5 or 6: this is 20 of the outcomes, and the defender wins in the other 16 for this defender roll. +- When the defender rolls a 3, the attacker wins if the attacker rolls a 4, 5, or 6: this is 27 of the outcomes, and the defender wins in the other 9 for this defender roll. +- When the defender rolls a 2, the attacker wins if the attacker rolls a 3, 4, 5, or 6: this is 32 of the outcomes, and the defender wins in the other 4 for this defender roll. +- When the defender rolls a 1, the attacker wins if one of their rolls is not a 1: this is 35 of the outcomes, and the defender wins in the other 1 for this defender roll. + +The total number of outcomes where the attacker wins is 0 + 11 + 20 + 27 + 32 + 35 = 125, so the likelihood of the attacker winning is 125 / 216 = 0.578703704. + +##### Attacker commits 1 troop and defender commits 1 troop + +Consider all the outcomes of each dice roll. + +There are 36 (6 \* 6) possible outcomes: 6 for the attacker's die, and 6 for the defender's die. + +The attacker wins if the attacker's highest die is greater than the defender's die. + +- When the defender rolls a 6, the attacker always loses: this is 6 of the outcomes. +- When the defender rolls a 5, the attacker wins if the attacker rolls a 6: this is 1 of the outcomes, and the defender wins in the other 5 for this defender roll. +- When the defender rolls a 4, the attacker wins if the attacker rolls a 5 or 6: this is 2 of the outcomes, and the defender wins in the other 4 for this defender roll. +- When the defender rolls a 3, the attacker wins if the attacker rolls a 4, 5, or 6: this is 3 of the outcomes, and the defender wins in the other 3 for this defender roll. +- When the defender rolls a 2, the attacker wins if the attacker rolls a 3, 4, 5, or 6: this is 4 of the outcomes, and the defender wins in the other 2 for this defender roll. +- When the defender rolls a 1, the attacker wins if one of their rolls is not a 1: this is 5 of the outcomes, and the defender wins in the other 1 for this defender roll. + +The total number of outcomes where the attacker wins is 0 + 1 + 2 + 3 + 4 + 5 = 15, so the likelihood of the attacker winning is 15 / 36 = 0.416666667. + +To summarise, we have the following likelihoods for each scenario: + +| attackers | defenders | attacks_won | attacks_lost | likelihood | +| --------: | --------: | ----------: | -----------: | -----------: | +| 1 | 1 | 1 | 0 | 0.4166666667 | +| 1 | 1 | 0 | 1 | 0.5833333333 | +| 2 | 1 | 1 | 0 | 0.5787037037 | +| 2 | 1 | 0 | 1 | 0.4212962963 | + +#### Invasion outcomes + +An invasion is a series of battles, so we can calculate the likelihood of each outcome by multiplying the likelihoods of each battle. + +Starting with the attacker committing 3 troops and the defender committing 2 troops, we have the following possible paths for the invasion: + +```mermaid +flowchart LR + A3D2["3 Attackers\n2 Defenders"] + A2D2["2 Attackers\n2 Defenders"] + A1D2["1 Attacker\n2 Defenders"] + A0D2["0 Attackers\n2 Defenders"] + A3D1["3 Attackers\n1 Defender"] + A2D1["2 Attackers\n1 Defender"] + A1D1["1 Attacker\n1 Defender"] + A0D1["0 Attackers\n1 Defender"] + A3D0["3 Attackers\n0 Defenders"] + A2D0["2 Attackers\n0 Defenders"] + A1D0["1 Attacker\n0 Defenders"] + + A3D2 -->|"attacker wins\n(0.5787...)"| A3D1 + A3D2 -->|"defender wins\n(0.4213...)"| A2D2 + A2D2 -->|"attacker wins\n(0.5787...)"| A2D1 + A2D2 -->|"defender wins\n(0.4213...)"| A1D2 + A3D1 ---->|"attacker wins\n(0.5787...)"| A3D0 + A3D1 -->|"defender wins\n(0.4213...)"| A2D1 + A2D1 --->|"attacker wins\n(0.5787...)"| A2D0 + A2D1 -->|"defender wins\n(0.4213...)"| A1D1 + + A1D2 -->|"attacker wins\n(0.4167...)"| A1D1 + A1D2 --->|"defender wins\n(0.5833...)"| A0D2 + A1D1 -->|"attacker wins\n(0.4167...)"| A1D0 + A1D1 -->|"defender wins\n(0.5833...)"| A0D1 +``` + +The likelihood of each branch was calculated in the previous section. We can calculate the likelihood of each outcome by: + +1. Multiplying the likelihoods of each branch until we reach the outcome +2. Summing the likelihoods of the paths that lead to the same outcome + +We'll walk through two examples. + +##### Attacker wins with 3 troops remaining + +There is only one path to this outcome: the attacker wins the first battle and the second battle. These each have a likelihood of 0.5787037037, so the likelihood of the attacker winning with 3 troops remaining is: + +- 0.5787037037 \* 0.5787037037 = 0.3348979767. + +##### Defender wins with 1 troop remaining + +There are three paths to this outcome: + +1. Attacker wins, defender wins, defender wins, defender wins +2. Defender wins, attacker wins, defender wins, defender wins +3. Defender wins, defender wins, attacker wins, defender wins + +Each path has the following likelihood, respectively: + +- 0.5787037037 \* 0.4212962963 \* 0.4212962963 \* 0.5833333333 = 0.0599167624 +- 0.4212962963 \* 0.5787037037 \* 0.4212962963 \* 0.5833333333 = 0.0599167624 +- 0.4212962963 \* 0.4212962963 \* 0.4166666667 \* 0.5833333333 = 0.0431400689 + +Therefore, the likelihood of the defender winning with 1 troop remaining is: + +- 0.0599167624 + 0.0599167624 + 0.0431400689 = 0.1629735937. + +Considering all the possible paths, we have the following likelihoods for the invasion: + +| attackers_remaining | defenders_remaining | likelihood | +| ------------------: | ------------------: | -----------: | +| 0 | 1 | 0.1629735937 | +| 0 | 2 | 0.1035361654 | +| 1 | 0 | 0.1164097098 | +| 2 | 0 | 0.2821825544 | +| 3 | 0 | 0.3348979767 | + +#### Invasion winner + +Now that we have the likelihoods of each outcome, we can calculate the likelihood of the attacker winning and the defender winning. + +The likelihood of the attacker winning is the sum of the likelihoods where the attacker has troops remaining: + +- 0.3348979767 + 0.2821825544 + 0.1164097098 = 0.7334902409 + +Similarly, the likelihood of the defender winning is the sum of the likelihoods where the defender has troops remaining: + +- 0.1629735937 + 0.1035361654 = 0.2665097591 + +As a sense check, we confirm that the likelihoods sum to 1 (accounting for rounding error): + +- 0.7334902409 + 0.2665097591 ~ 1 + +Therefore, the final output is: + +| attackers_remaining | defenders_remaining | likelihood | attackers_win_likelihood | defenders_win_likelihood | +| ------------------: | ------------------: | -----------: | -----------------------: | -----------------------: | +| 0 | 1 | 0.1629735937 | 0.7334902409 | 0.2665097591 | +| 0 | 2 | 0.1035361654 | 0.7334902409 | 0.2665097591 | +| 1 | 0 | 0.1164097098 | 0.7334902409 | 0.2665097591 | +| 2 | 0 | 0.2821825544 | 0.7334902409 | 0.2665097591 | +| 3 | 0 | 0.3348979767 | 0.7334902409 | 0.2665097591 | diff --git a/docs/challenging-sql-problems/solutions/gold/loan-repayment-schedule.sql b/docs/challenging-sql-problems/solutions/gold/loan-repayment-schedule.sql index 9110ae8..1789ec3 100644 --- a/docs/challenging-sql-problems/solutions/gold/loan-repayment-schedule.sql +++ b/docs/challenging-sql-problems/solutions/gold/loan-repayment-schedule.sql @@ -1,47 +1,47 @@ ```sql solution(loan_id, repayment_number, repayment_date, interest, principal, total, balance) as ( values - (1, 1, '2024-02-01'::date, 1600.00, 12682.06, 14282.06, 67317.94), - (1, 2, '2024-03-01'::date, 1346.36, 12935.70, 14282.06, 54382.24), - (1, 3, '2024-04-01'::date, 1087.64, 13194.42, 14282.06, 41187.82), - (1, 4, '2024-05-01'::date, 823.76, 13458.30, 14282.06, 27729.52), - (1, 5, '2024-06-01'::date, 554.59, 13727.47, 14282.06, 14002.05), - (1, 6, '2024-07-01'::date, 280.04, 14002.05, 14282.09, 0.00), - (2, 1, '2024-02-02'::date, 1125.00, 5751.00, 6876.00, 69249.00), - (2, 2, '2024-03-02'::date, 1038.74, 5837.26, 6876.00, 63411.74), - (2, 3, '2024-04-02'::date, 951.18, 5924.82, 6876.00, 57486.92), - (2, 4, '2024-05-02'::date, 862.30, 6013.70, 6876.00, 51473.22), - (2, 5, '2024-06-02'::date, 772.10, 6103.90, 6876.00, 45369.32), - (2, 6, '2024-07-02'::date, 680.54, 6195.46, 6876.00, 39173.86), - (2, 7, '2024-08-02'::date, 587.61, 6288.39, 6876.00, 32885.47), - (2, 8, '2024-09-02'::date, 493.28, 6382.72, 6876.00, 26502.75), - (2, 9, '2024-10-02'::date, 397.54, 6478.46, 6876.00, 20024.29), - (2, 10, '2024-11-02'::date, 300.36, 6575.64, 6876.00, 13448.65), - (2, 11, '2024-12-02'::date, 201.73, 6674.27, 6876.00, 6774.38), - (2, 12, '2025-01-02'::date, 101.62, 6774.38, 6876.00, 0.00), - (3, 1, '2024-02-03'::date, 1000.00, 3707.35, 4707.35, 96292.65), - (3, 2, '2024-03-03'::date, 962.93, 3744.42, 4707.35, 92548.23), - (3, 3, '2024-04-03'::date, 925.48, 3781.87, 4707.35, 88766.36), - (3, 4, '2024-05-03'::date, 887.66, 3819.69, 4707.35, 84946.67), - (3, 5, '2024-06-03'::date, 849.47, 3857.88, 4707.35, 81088.79), - (3, 6, '2024-07-03'::date, 810.89, 3896.46, 4707.35, 77192.33), - (3, 7, '2024-08-03'::date, 771.92, 3935.43, 4707.35, 73256.90), - (3, 8, '2024-09-03'::date, 732.57, 3974.78, 4707.35, 69282.12), - (3, 9, '2024-10-03'::date, 692.82, 4014.53, 4707.35, 65267.59), - (3, 10, '2024-11-03'::date, 652.68, 4054.67, 4707.35, 61212.92), - (3, 11, '2024-12-03'::date, 612.13, 4095.22, 4707.35, 57117.70), - (3, 12, '2025-01-03'::date, 571.18, 4136.17, 4707.35, 52981.53), - (3, 13, '2025-02-03'::date, 529.82, 4177.53, 4707.35, 48804.00), - (3, 14, '2025-03-03'::date, 488.04, 4219.31, 4707.35, 44584.69), - (3, 15, '2025-04-03'::date, 445.85, 4261.50, 4707.35, 40323.19), - (3, 16, '2025-05-03'::date, 403.23, 4304.12, 4707.35, 36019.07), - (3, 17, '2025-06-03'::date, 360.19, 4347.16, 4707.35, 31671.91), - (3, 18, '2025-07-03'::date, 316.72, 4390.63, 4707.35, 27281.28), - (3, 19, '2025-08-03'::date, 272.81, 4434.54, 4707.35, 22846.74), - (3, 20, '2025-09-03'::date, 228.47, 4478.88, 4707.35, 18367.86), - (3, 21, '2025-10-03'::date, 183.68, 4523.67, 4707.35, 13844.19), - (3, 22, '2025-11-03'::date, 138.44, 4568.91, 4707.35, 9275.28), - (3, 23, '2025-12-03'::date, 92.75, 4614.60, 4707.35, 4660.68), - (3, 24, '2026-01-03'::date, 46.61, 4660.68, 4707.29, 0.00) + (1, 1, '2024-02-01'::date, 1600.00, 12682.06, 14282.06, 67317.94), + (1, 2, '2024-03-01'::date, 1346.36, 12935.70, 14282.06, 54382.24), + (1, 3, '2024-04-01'::date, 1087.64, 13194.42, 14282.06, 41187.82), + (1, 4, '2024-05-01'::date, 823.76, 13458.30, 14282.06, 27729.52), + (1, 5, '2024-06-01'::date, 554.59, 13727.47, 14282.06, 14002.05), + (1, 6, '2024-07-01'::date, 280.04, 14002.05, 14282.09, 0.00), + (2, 1, '2024-02-02'::date, 1125.00, 5751.00, 6876.00, 69249.00), + (2, 2, '2024-03-02'::date, 1038.74, 5837.26, 6876.00, 63411.74), + (2, 3, '2024-04-02'::date, 951.18, 5924.82, 6876.00, 57486.92), + (2, 4, '2024-05-02'::date, 862.30, 6013.70, 6876.00, 51473.22), + (2, 5, '2024-06-02'::date, 772.10, 6103.90, 6876.00, 45369.32), + (2, 6, '2024-07-02'::date, 680.54, 6195.46, 6876.00, 39173.86), + (2, 7, '2024-08-02'::date, 587.61, 6288.39, 6876.00, 32885.47), + (2, 8, '2024-09-02'::date, 493.28, 6382.72, 6876.00, 26502.75), + (2, 9, '2024-10-02'::date, 397.54, 6478.46, 6876.00, 20024.29), + (2, 10, '2024-11-02'::date, 300.36, 6575.64, 6876.00, 13448.65), + (2, 11, '2024-12-02'::date, 201.73, 6674.27, 6876.00, 6774.38), + (2, 12, '2025-01-02'::date, 101.62, 6774.38, 6876.00, 0.00), + (3, 1, '2024-02-03'::date, 1000.00, 3707.35, 4707.35, 96292.65), + (3, 2, '2024-03-03'::date, 962.93, 3744.42, 4707.35, 92548.23), + (3, 3, '2024-04-03'::date, 925.48, 3781.87, 4707.35, 88766.36), + (3, 4, '2024-05-03'::date, 887.66, 3819.69, 4707.35, 84946.67), + (3, 5, '2024-06-03'::date, 849.47, 3857.88, 4707.35, 81088.79), + (3, 6, '2024-07-03'::date, 810.89, 3896.46, 4707.35, 77192.33), + (3, 7, '2024-08-03'::date, 771.92, 3935.43, 4707.35, 73256.90), + (3, 8, '2024-09-03'::date, 732.57, 3974.78, 4707.35, 69282.12), + (3, 9, '2024-10-03'::date, 692.82, 4014.53, 4707.35, 65267.59), + (3, 10, '2024-11-03'::date, 652.68, 4054.67, 4707.35, 61212.92), + (3, 11, '2024-12-03'::date, 612.13, 4095.22, 4707.35, 57117.70), + (3, 12, '2025-01-03'::date, 571.18, 4136.17, 4707.35, 52981.53), + (3, 13, '2025-02-03'::date, 529.82, 4177.53, 4707.35, 48804.00), + (3, 14, '2025-03-03'::date, 488.04, 4219.31, 4707.35, 44584.69), + (3, 15, '2025-04-03'::date, 445.85, 4261.50, 4707.35, 40323.19), + (3, 16, '2025-05-03'::date, 403.23, 4304.12, 4707.35, 36019.07), + (3, 17, '2025-06-03'::date, 360.19, 4347.16, 4707.35, 31671.91), + (3, 18, '2025-07-03'::date, 316.72, 4390.63, 4707.35, 27281.28), + (3, 19, '2025-08-03'::date, 272.81, 4434.54, 4707.35, 22846.74), + (3, 20, '2025-09-03'::date, 228.47, 4478.88, 4707.35, 18367.86), + (3, 21, '2025-10-03'::date, 183.68, 4523.67, 4707.35, 13844.19), + (3, 22, '2025-11-03'::date, 138.44, 4568.91, 4707.35, 9275.28), + (3, 23, '2025-12-03'::date, 92.75, 4614.60, 4707.35, 4660.68), + (3, 24, '2026-01-03'::date, 46.61, 4660.68, 4707.29, 0.00) ) ``` diff --git a/docs/challenging-sql-problems/solutions/gold/risk-invasions--duckdb.sql b/docs/challenging-sql-problems/solutions/gold/risk-invasions--duckdb.sql new file mode 100644 index 0000000..df319c3 --- /dev/null +++ b/docs/challenging-sql-problems/solutions/gold/risk-invasions--duckdb.sql @@ -0,0 +1,85 @@ +```sql +with recursive + +die(face) as (from generate_series(1, 6)), + +rolls(n, outcome) as ( + select 1, [face] + from die + union all + select + rolls.n + 1, + list_reverse_sort(list_append(rolls.outcome, die.face)), + from rolls + cross join die + where rolls.n < 3 +), + +battle_scenarios(attackers, defenders) as ( + select * + from + generate_series(1, 3) as attackers, + generate_series(1, 2) as defenders +), + +battle_outcomes as ( + select + battle_scenarios.attackers, + battle_scenarios.defenders, + + coalesce(attacker_rolls.outcome[1], 0) as attacker_roll_1, + coalesce(attacker_rolls.outcome[2], 0) as attacker_roll_2, + coalesce(defender_rolls.outcome[1], 0) as defender_roll_1, + coalesce(defender_rolls.outcome[2], 0) as defender_roll_2, + + (0 + + (attacker_roll_1 > defender_roll_1)::int + + (attacker_roll_2 > defender_roll_2 and battle_scenarios.defenders = 2)::int + ) as attacks_won + from battle_scenarios + left join rolls as attacker_rolls + on battle_scenarios.attackers = attacker_rolls.n + left join rolls as defender_rolls + on battle_scenarios.defenders = defender_rolls.n +), + +battle_likelihoods as ( + select + attackers, + defenders, + attacks_won, + max(attacks_won) over scenario - attacks_won as attacks_lost, + count(*) / sum(count(*)) over scenario as likelihood + from battle_outcomes + group by attackers, defenders, attacks_won + window scenario as (partition by attackers, defenders) +), + +invasions as ( + select + 8 as attackers_remaining, + 6 as defenders_remaining, + 1::numeric(12, 10) as likelihood, + union all + select + invasions.attackers_remaining - battle_likelihoods.attacks_lost, + invasions.defenders_remaining - battle_likelihoods.attacks_won, + invasions.likelihood * coalesce(battle_likelihoods.likelihood, 0), + from invasions + inner join battle_likelihoods + on least(invasions.attackers_remaining, 3) = battle_likelihoods.attackers + and least(invasions.defenders_remaining, 2) = battle_likelihoods.defenders + and battle_likelihoods.attacks_won <= least(invasions.attackers_remaining, invasions.defenders_remaining) +) + +select + attackers_remaining, + defenders_remaining, + sum(likelihood) as likelihood, + sum(sum(likelihood) filter (where attackers_remaining != 0)) over () as attackers_win_likelihood, + sum(sum(likelihood) filter (where defenders_remaining != 0)) over () as defenders_win_likelihood, +from invasions +where attackers_remaining = 0 or defenders_remaining = 0 +group by attackers_remaining, defenders_remaining +order by attackers_remaining, defenders_remaining +``` diff --git a/docs/challenging-sql-problems/solutions/gold/risk-invasions.md b/docs/challenging-sql-problems/solutions/gold/risk-invasions.md new file mode 100644 index 0000000..c8d8523 --- /dev/null +++ b/docs/challenging-sql-problems/solutions/gold/risk-invasions.md @@ -0,0 +1,42 @@ +# Risk invasions 🛡️ + +> [!TIP] +> +> Solution to the following problem: +> +> - [risk-invasions.md](../../problems/gold/risk-invasions.md) + +## Result Set + +Regardless of the database, the result set should look like: + +| attackers_remaining | defenders_remaining | likelihood | attackers_win_likelihood | defenders_win_likelihood | +| ------------------: | ------------------: | -----------: | -----------------------: | -----------------------: | +| 0 | 1 | 0.0444861546 | 0.7295558279 | 0.2704441690 | +| 0 | 2 | 0.0811874068 | 0.7295558279 | 0.2704441690 | +| 0 | 3 | 0.0613334164 | 0.7295558279 | 0.2704441690 | +| 0 | 4 | 0.0473588523 | 0.7295558279 | 0.2704441690 | +| 0 | 5 | 0.0248517821 | 0.7295558279 | 0.2704441690 | +| 0 | 6 | 0.0112265568 | 0.7295558279 | 0.2704441690 | +| 1 | 0 | 0.0317758219 | 0.7295558279 | 0.2704441690 | +| 2 | 0 | 0.0656099405 | 0.7295558279 | 0.2704441690 | +| 3 | 0 | 0.1082686491 | 0.7295558279 | 0.2704441690 | +| 4 | 0 | 0.1294309856 | 0.7295558279 | 0.2704441690 | +| 5 | 0 | 0.1283258874 | 0.7295558279 | 0.2704441690 | +| 6 | 0 | 0.1230138225 | 0.7295558279 | 0.2704441690 | +| 7 | 0 | 0.0917943963 | 0.7295558279 | 0.2704441690 | +| 8 | 0 | 0.0513363246 | 0.7295558279 | 0.2704441690 | + +
+Expand for the DDL +--8<-- "docs/challenging-sql-problems/solutions/gold/risk-invasions.sql" +
+ +## Solution + +Some SQL solutions per database are provided below. + + +> SUCCESS: **DuckDB** +> +--8<-- "docs/challenging-sql-problems/solutions/gold/risk-invasions--duckdb.sql" diff --git a/docs/challenging-sql-problems/solutions/gold/risk-invasions.sql b/docs/challenging-sql-problems/solutions/gold/risk-invasions.sql new file mode 100644 index 0000000..af34851 --- /dev/null +++ b/docs/challenging-sql-problems/solutions/gold/risk-invasions.sql @@ -0,0 +1,19 @@ +```sql +solution(attackers_remaining, defenders_remaining, likelihood, attackers_win_likelihood, defenders_win_likelihood) as ( + values + (0, 1, 0.0444861546, 0.7295558279, 0.2704441690), + (0, 2, 0.0811874068, 0.7295558279, 0.2704441690), + (0, 3, 0.0613334164, 0.7295558279, 0.2704441690), + (0, 4, 0.0473588523, 0.7295558279, 0.2704441690), + (0, 5, 0.0248517821, 0.7295558279, 0.2704441690), + (0, 6, 0.0112265568, 0.7295558279, 0.2704441690), + (1, 0, 0.0317758219, 0.7295558279, 0.2704441690), + (2, 0, 0.0656099405, 0.7295558279, 0.2704441690), + (3, 0, 0.1082686491, 0.7295558279, 0.2704441690), + (4, 0, 0.1294309856, 0.7295558279, 0.2704441690), + (5, 0, 0.1283258874, 0.7295558279, 0.2704441690), + (6, 0, 0.1230138225, 0.7295558279, 0.2704441690), + (7, 0, 0.0917943963, 0.7295558279, 0.2704441690), + (8, 0, 0.0513363246, 0.7295558279, 0.2704441690) +) +``` diff --git a/mkdocs.yml b/mkdocs.yml index d692c1a..ee479c7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -96,6 +96,7 @@ nav: - challenging-sql-problems/problems/silver/customer-sales-running-totals.md # window functions - 🟡 Gold Tier: - challenging-sql-problems/problems/gold/loan-repayment-schedule.md # recursive CTE + - challenging-sql-problems/problems/gold/risk-invasions.md # window functions, recursive CTE, custom axis - challenging-sql-problems/problems/gold/supply-chain-network.md - challenging-sql-problems/problems/gold/encoding-datelist-ints.md # window functions (gaps and islands), correlated subquery, custom axis - challenging-sql-problems/problems/gold/travel-plans.md # recursive CTEs @@ -121,6 +122,7 @@ nav: - challenging-sql-problems/solutions/silver/customer-sales-running-totals.md - 🟡 Gold Tier: - challenging-sql-problems/solutions/gold/loan-repayment-schedule.md + - challenging-sql-problems/solutions/gold/risk-invasions.md - challenging-sql-problems/solutions/gold/supply-chain-network.md - challenging-sql-problems/solutions/gold/encoding-datelist-ints.md - challenging-sql-problems/solutions/gold/travel-plans.md