Skip to content

Commit b423f69

Browse files
committed
fixed ch5 exercises and slns
1 parent 556181d commit b423f69

File tree

2 files changed

+43
-125
lines changed

2 files changed

+43
-125
lines changed

Exercises/Chapter05/Exercises.cs

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,46 +9,19 @@ namespace Exercises.Chapter5
99
{
1010
public static class Exercises
1111
{
12-
// 1. Write some tests to illustrate that `IEnumerable` obeys the functor laws
13-
// (tip: you can take the tests for `Option` in LaYumba.Functional.Tests/Option/FunctorLaws
14-
// as a starting point).
15-
16-
// 2. Implement `Map` in terms of `Bind` and `Return` for `Container`, `Option`
17-
// and `IEnumerable`.
18-
19-
// 3. Write a class `Switch<T, R>` that would enable you to write the code
20-
// in the snippet below. (Hint: notice how each `.Case` is equivalent to a
21-
// `KeyValuePair` in the `TransferMappingRules` Dictionary we have used
22-
// above.)
23-
24-
//static string DailyComment(DateTime day)
25-
//{
26-
// Predicate<DateTime> always = d => true;
27-
// Predicate<DateTime> weekend = d => d.DayOfWeek == DayOfWeek.Saturday
28-
// || d.DayOfWeek == DayOfWeek.Sunday;
29-
// Predicate<DateTime> friday = d => d.DayOfWeek == DayOfWeek.Friday;
30-
// Predicate<DateTime> nearEOY = d => d.Month == 12 && d.Day > 25;
31-
32-
// var @switch = Switch<DateTime, string>()
33-
// .Case(always, d => d.DayOfWeek.ToString() + ":")
34-
// .Case(weekend, d => "chilling...")
35-
// .Case(friday, d => "thank God it's the weekend!")
36-
// .Case(nearEOY, d => "getting ready for a party!");
37-
38-
// IEnumerable<string> comments = @switch.MatchAll(day);
39-
// return string.Join(" ", comments.ToArray());
40-
//}
41-
42-
43-
// 4. Write tests to ensure it works as expected.
44-
45-
// 5. Exhance `Switch<T, R>` so that it can also be used like the `switch`
46-
// statement in the C# language, by adding an overload taking a constant
47-
// value to match against `Case(T value, Func<T, R> func)`, and a `Match`
48-
// method that returns the result of invoking the function of the _first_
49-
// matching case.
50-
51-
// Write a simple scenario with those methods, and unit test it
52-
12+
// 1. Without looking at any code or documentation (or intllisense), write the function signatures of
13+
// `OrderByDescending`, `Take` and `Average`, which we used to implement `AverageEarningsOfRichestQuartile`:
14+
decimal AverageEarningsOfRichestQuartile(List<Person> population)
15+
=> population
16+
.OrderByDescending(p => p.Earnings)
17+
.Take(population.Count/4)
18+
.Select(p => p.Earnings)
19+
.Average();
20+
21+
// 2 Check your answer with the MSDN documentation: https://docs.microsoft.com/
22+
// en-us/dotnet/api/system.linq.enumerable. How is Average different?
23+
24+
// 3 Implement a general purpose Compose function that takes two unary functions
25+
// and returns the composition of the two.
5326
}
5427
}

Exercises/Chapter05/Solutions.cs

Lines changed: 29 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -9,99 +9,44 @@ namespace Exercises.Chapter5.Solutions
99
{
1010
static class Exercises
1111
{
12-
// 1. Write some tests to illustrate that `IEnumerable` obeys the functor laws
13-
// (tip: you can take the tests for `Option` in LaYumba.Functional.Tests/Option/FunctorLaws
14-
// as a starting point).
12+
// 1. Without looking at any code or documentation (or intllisense), write the function signatures of
13+
// `OrderByDescending`, `Take` and `Average`, which we used to implement `AverageEarningsOfRichestQuartile`:
14+
decimal AverageEarningsOfRichestQuartile(List<Person> population)
15+
=> population
16+
.OrderByDescending(p => p.Earnings)
17+
.Take(population.Count/4)
18+
.Select(p => p.Earnings)
19+
.Average();
1520

16-
// Answers in LaYumba.Functional.Tests/IEnumerable/FunctorLaws
21+
// OrderByDescending : (IEnumerable<T>, (T -> decimal)) -> IEnumerable<T>
22+
// particularized for this case:
23+
// OrderByDescending : (IEnumerable<Person>, (Person -> decimal)) -> IEnumerable<Person>
1724

18-
// 2. Implement `Map` in terms of `Bind` and `Return` for `Option`
19-
// and `IEnumerable`.
20-
21-
static Option<R> Map<T, R>(this Option<T> @this
22-
, Func<T, R> func)
23-
=> @this.Bind(v => Some(func(v)));
25+
// Take : (IEnumerable<T>, int) -> IEnumerable<T>
26+
// particularized for this case:
27+
// Take : (IEnumerable<Person>, int) -> IEnumerable<Person>
2428

25-
static IEnumerable<R> Map<T, R>(this IEnumerable<T> @this
26-
, Func<T, R> func)
27-
=> @this.Bind(v => List(func(v)));
29+
// Select : (IEnumerable<T>, (T -> R)) -> IEnumerable<R>
30+
// particularized for this case:
31+
// Select : (IEnumerable<Person>, (Person -> decimal)) -> IEnumerable<decimal>
2832

29-
// 3. Write a class `Switch<T, R>` that would enable you to write the code
30-
// in the snippet below. (Hint: notice how each `.Case` is equivalent to a
31-
// `KeyValuePair` in the `TransferMappingRules` Dictionary we have used
32-
// above.)
33+
// Average : IEnumerable<T> -> T
34+
// particularized for this case:
35+
// Average : IEnumerable<decimal> -> decimal
3336

34-
static string DailyComment(DateTime day)
35-
{
36-
Predicate<DateTime> always = d => true;
37-
Predicate<DateTime> weekend = d => d.DayOfWeek == DayOfWeek.Saturday
38-
|| d.DayOfWeek == DayOfWeek.Sunday;
39-
Predicate<DateTime> friday = d => d.DayOfWeek == DayOfWeek.Friday;
40-
Predicate<DateTime> nearEOY = d => d.Month == 12 && d.Day > 25;
4137

42-
var @switch = Switch<DateTime, string>()
43-
.Case(always, d => d.DayOfWeek.ToString() + ":")
44-
.Case(weekend, d => "chilling...")
45-
.Case(friday, d => "thank God it's the weekend!")
46-
.Case(nearEOY, d => "getting ready for a party!");
38+
// 2 Check your answer with the MSDN documentation: https://docs.microsoft.com/
39+
// en-us/dotnet/api/system.linq.enumerable. How is Average different?
4740

48-
IEnumerable<string> comments = @switch.MatchAll(day);
49-
return string.Join(" ", comments.ToArray());
50-
}
41+
// Average is the only method call that does not return an IEnumerable;
42+
// this also means that Average is the only greedy method and causes all the
43+
// previous ones in the chain to be evaluated
5144

52-
// note that here I use a tuple instead of KeyValuePair, but that's a minor detail
53-
static IEnumerable<(Predicate<T>, Func<T, R>)> Switch<T, R>()
54-
=> Enumerable.Empty<(Predicate<T>, Func<T, R>)>();
55-
56-
static IEnumerable<(Predicate<T>, Func<T, R>)> Case<T, R>(
57-
this IEnumerable<(Predicate<T>, Func<T, R>)> rules
58-
, Predicate<T> pred, Func<T, R> func)
59-
=> rules.Append((pred, func));
6045

61-
static IEnumerable<R> MatchAll<T, R>(this IEnumerable<(Predicate<T>, Func<T, R>)> rules, T t)
62-
=> rules.Where(rule => rule.Item1(t))
63-
.Map(rule => rule.Item2(t));
64-
65-
// 4. Write tests to ensure it works as expected.
46+
// 3 Implement a general purpose Compose function that takes two unary functions
47+
// and returns the composition of the two.
6648

67-
[TestCase("24 Jun 2016", ExpectedResult = "Friday: thank God it's the weekend!")]
68-
[TestCase("27 Dec 2016", ExpectedResult = "Tuesday: getting ready for a party!")]
69-
public static string TestDateComment(string date)
70-
=> DailyComment(DateTime.Parse(date));
71-
72-
// 5. Exhance `Switch<T, R>` so that it can also be used like the `switch`
73-
// statement in the C# language, by adding an overload taking a constant
74-
// value to match against `Case(T value, Func<T, R> func)`, and a `Match`
75-
// method that returns the result of invoking the function of the _first_
76-
// matching case.
77-
78-
static IEnumerable<(Predicate<T>, Func<T, R>)> Case<T, R>(
79-
this IEnumerable<(Predicate<T>, Func<T, R>)> rules
80-
, T value, Func<T, R> func)
81-
=> rules.Append((new Predicate<T>(v => v.Equals(value)), func));
82-
83-
static R Match<T, R>(this IEnumerable<(Predicate<T>, Func<T, R>)> rules, T t)
84-
=> rules.Where(rule => rule.Item1(t))
85-
.First().Item2(t);
86-
87-
// Write a simple scenario with those methods, and unit test it
88-
89-
static string NumberComment(int number)
90-
{
91-
Predicate<int> isOdd = i => i % 2 == 1;
92-
93-
var @switch = Switch<int, string>()
94-
.Case(33, _ => "33 is my favourite number!") // match on specific value
95-
.Case(isOdd, i => $"{i} is a bit of an odd one...") // match on a condition
96-
.Case(_ => true, i => $"{i} is just a boring, even number"); // catch all
97-
98-
return @switch.Match(number);
99-
}
100-
101-
102-
[TestCase(33, ExpectedResult = "33 is my favourite number!")]
103-
[TestCase(27, ExpectedResult = "27 is a bit of an odd one...")]
104-
[TestCase(-2, ExpectedResult = "-2 is just a boring, even number")]
105-
public static string TestNumberComment(int number) => NumberComment(number);
49+
static Func<T1, R> Compose<T1, T2, R>(this Func<T2, R> g, Func<T1, T2> f)
50+
=> x => g(f(x));
10651
}
10752
}

0 commit comments

Comments
 (0)