Last active
February 22, 2023 08:27
-
-
Save savaged/d229dcefda0659d60f54ea4468f6cfea to your computer and use it in GitHub Desktop.
FP styled C# example inspired by Isaac Abraham's book Programming With F#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Unit = System.ValueTuple; | |
_ = App.Run(Console.WriteLine); | |
static class App | |
{ | |
public static Unit Run(Action<string> outputter) | |
{ | |
outputter(GetAwayWins().ToSummary()); | |
return Unit.Create(); | |
} | |
static IList<FootballTeamWinSummary> GetAwayWins() => | |
SampleDataset.Results.GetAwayWins(); | |
} | |
static class Extensions | |
{ | |
public static IList<FootballTeamWinSummary> GetAwayWins( | |
this IEnumerable<FootballGameResult> self) => | |
self.Where(r => r.IsAwayWin()) | |
.GroupBy( | |
r => r.AwayTeamResult.TeamName, | |
r => r, | |
(t, g) => new FootballTeamWinSummary(t, g.Count())) | |
.OrderBy(s => s.TeamName) | |
.ToList(); | |
public static bool IsAwayWin(this FootballGameResult self) => | |
self.AwayTeamResult.Goals > self.HomeTeamResult.Goals; | |
public static string ToSummary(this FootballTeamResult self) => | |
$"{self.TeamName}: {self.Goals}"; | |
public static string ToSummary(this IList<FootballTeamWinSummary> self) => | |
SummariseWins(self); | |
private static string SummariseWins( | |
IList<FootballTeamWinSummary> w, int i = 0, string s = "") | |
{ | |
if (w.TryGetNonEnumeratedCount(out int count) && count <= i) | |
return s; | |
s += $"{w[i].TeamName}: {w[i].Wins}{Environment.NewLine}"; | |
return SummariseWins(w, ++i, s); | |
} | |
} | |
record struct FootballTeamWinSummary(string TeamName, int Wins); | |
record struct FootballTeamResult(string TeamName, int Goals); | |
record struct FootballGameResult( | |
FootballTeamResult HomeTeamResult, FootballTeamResult AwayTeamResult); | |
static class SampleDataset | |
{ | |
public static IEnumerable<FootballGameResult> Results => | |
new List<FootballGameResult> | |
{ | |
new FootballGameResult( | |
new FootballTeamResult("Middlesbrough", 1), | |
new FootballTeamResult("Hull City", 2)), | |
new FootballGameResult( | |
new FootballTeamResult("Middlesbrough", 1), | |
new FootballTeamResult("Huddersfield Town", 3)), | |
new FootballGameResult( | |
new FootballTeamResult("Huddersfield Town", 3), | |
new FootballTeamResult("Hull City", 1)), | |
new FootballGameResult( | |
new FootballTeamResult("Huddersfield Town", 2), | |
new FootballTeamResult("Middlesbrough", 1)), | |
new FootballGameResult( | |
new FootballTeamResult("Hull City", 4), | |
new FootballTeamResult("Middlesbrough", 2)), | |
new FootballGameResult( | |
new FootballTeamResult("Hull City", 1), | |
new FootballTeamResult("Huddersfield Town", 2)), | |
}; | |
} |
F# version (please remember I'm an FP noob)
type FootballTeamResult =
{
TeamName : string
Goals : int
}
type FootballGameResult =
{
HomeTeamResult : FootballTeamResult
AwayTeamResult : FootballTeamResult
}
let createFTR (n, g) =
{
TeamName = n
Goals = g
}
let createFGR (hr, ar) =
{
HomeTeamResult = hr
AwayTeamResult = ar
}
let sampleDataSet =
[
createFGR (createFTR ("Middlesbrough", 1), createFTR ("Hull City", 2))
createFGR (createFTR ("Middlesbrough", 1), createFTR ("Huddersfield Town", 3))
createFGR (createFTR ("Huddersfield Town", 3), createFTR ("Hull City", 1))
createFGR (createFTR ("Huddersfield Town", 2), createFTR ("Middlesbrough", 1))
createFGR (createFTR ("Hull City", 4), createFTR ("Middlesbrough", 2))
createFGR (createFTR ("Hull City", 1), createFTR ("Huddersfield Town", 2))
]
let isAwayWin result =
result.AwayTeamResult.Goals > result.HomeTeamResult.Goals
let toSummary (team, wins) =
String.concat ": " [ team; string wins ]
let outputter summaries =
summaries
|> List.iter (fun s -> printfn "%s" s)
()
let awayWins =
sampleDataSet
|> List.filter isAwayWin
|> List.countBy(fun result -> result.AwayTeamResult.TeamName)
|> List.sortByDescending(fun (_, aw) -> aw)
|> List.map (fun aw -> aw |> toSummary)
let run =
outputter awayWins
()
run
SQL (MySQL 8.0) version gives all the clues to how simple a declarative style can be
DROP TEMPORARY TABLE IF EXISTS tmp;
CREATE TEMPORARY TABLE tmp
(
hometeam VARCHAR(50) NOT NULL,
homegoals INT NOT NULL,
awayteam VARCHAR(50) NOT NULL,
awaygoals INT NOT NULL
);
INSERT INTO tmp
(hometeam, homegoals, awayteam, awaygoals)
VALUES
('Middlesbrough', 1, 'Hull City', 2),
('Middlesbrough', 1, 'Huddersfield Town', 3),
('Huddersfield Town', 3, 'Hull City', 1),
('Huddersfield Town', 2, 'Middlesbrough', 1),
('Hull City', 4, 'Middlesbrough', 2),
('Hull City', 1, 'Huddersfield Town', 2);
SELECT
awayteam,
COUNT(*) AS wins
FROM tmp
WHERE awaygoals > homegoals
GROUP BY awayteam
ORDER BY wins;
DROP TEMPORARY TABLE tmp;
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Challenge
Other than the initial input below, use no variable declarations, only function parameters, to get the final output.
Given this input
Home result: "Middlesbrough", Goals: 1
Away result: "Hull City", Goals: 2
Home result: "Middlesbrough", Goals: 1
Away result: "Huddersfield Town", Goals: 3
Home result: "Huddersfield Town", Goals: 3
Away result: "Hull City", Goals: 1
Home result: "Huddersfield Town", Goals: 2
Away result: "Middlesbrough", Goals: 1
Home result: "Hull City", Goals: 4
Away result: "Middlesbrough", Goals: 2
Home result: "Hull City", Goals: 1
Away result: "Huddersfield Town", Goals: 2
When filtered for away wins
Then the output is
Huddersfield Town: 1
Hull City: 2