I am using the coding puzzels of https://adventofcode.com for improving my F# skills.
Problem:
Day 1 Part 2:
Starting with frequency 0, a list of numbers should be added successive generating new frequencies. Searched is the first frequency which occurs twice. If the end of the list has been reached without success; the list should be processed again.
Example Input: [+1, -2, +3, +1]
- Current frequency 0, change of +1; resulting frequency 1.
- Current frequency 1, change of -2; resulting frequency -1. C* urrent frequency -1, change of +3; resulting frequency 2.
- Current frequency 2, change of +1; resulting frequency 3. (At this point, we continue from the start of the list.)
- Current frequency 3, change of +1; resulting frequency 4.
- Current frequency 4, change of -2; resulting frequency 2, which has already been seen.
In this example, the first frequency reached twice is 2.
Solution
let input = [1;-2;3;1]
type State =
| FinalState of int
| IntermediateState of int * int Set
type State with
static member Zero = IntermediateState(0, Set.empty.Add(0))
let foldIntermediateState (freqHistory: int Set, freq : int) : State =
let isFinal = freqHistory |> Set.contains freq
if isFinal then
FinalState (freq)
else
IntermediateState (freq, freqHistory.Add(freq))
let foldState state value =
match state with
| FinalState _ -> state
| IntermediateState (lastFreq, freqHistory) -> foldIntermediateState (freqHistory, lastFreq + value)
let rec processListRec state lst =
let result = lst |> Seq.fold foldState state
match result with
| FinalState result -> printfn "The result is: %i" result
| IntermediateState _ -> lst |> processListRec result
input |> Seq.map int |> processListRec State.Zero
Questions
In fact, I am proud to have found a solution without mutable states that looks (at least for me) functional! :D. So any feeback that helps improving the solution or even alternative function approches for solving that problem are welcome :).
2 Answers 2
It also looks functional to me, and I like your use of union type State
.
You can define the Zero
static member of State
as:
type State =
| FinalState of int
| IntermediateState of int * int Set
static member Zero = IntermediateState(0, Set.empty.Add(0))
You don't have to create a separate with
-definition.
In general I avoid writing the result to the console in the algorithm function it self. Instead it is more useful to return the result and let the caller handle that as needed:
let rec processListRec state lst =
let result = lst |> Seq.fold foldState state
match result with
| FinalState result -> result
| IntermediateState _ -> lst |> processListRec result
Used as:
printfn "%A" (processListRec State.Zero input)
You need some kind of stop condition or an input check if all changes are positive or negative.
For instance will [1;1;1]
run forever, because no frequency will ever occur twice.
A raw recursive solution could be:
let findReoccurantFrequency input =
if (input |> List.groupBy (fun x -> x > 0)) |> List.length < 2 then
raise (Exception "Invalid input (only positive or negative changes)")
else
let rec runner lst freq freqs =
if freqs |> List.contains freq then
freq
else
let head, tail = if lst = [] then input.Head, input.Tail else lst.Head, lst.Tail
runner tail (freq + head) (freq::freqs)
runner input 0 []
I'm not saying that it's a better solution, just another approach for the exercise and inspiration. Especially I'm not proud of the input check.
Edit
As pointed out by JanDotNet in his comment, my input validation is poor, so it's maybe better to leave it out. Below is a revised version of my implementation using a Set
instead of a List
for the frequencies:
let findReoccurantFrequency input =
let rec runner lst freq freqs =
match freqs |> Set.contains freq with
| true -> freq
| false ->
let head, tail = if lst = [] then input |> List.head, input |> List.tail else lst.Head, lst.Tail
runner tail (freq + head) (freqs.Add(freq))
runner input 0 Set.empty
-
1\$\begingroup\$ Thanks for your answer. The points are absolutly valid :). 2 small comments: a) Determining invalid input is not such easy (e.g. [3;-1] will also result in an endless loop) Therefore I would assume that the input is valid. b) I really like your recursive solution! It is much cleaner and less code :). Note that for the real problem statement (input of 1015 numbers which create a list of 145.146 freqs) it more appropriated to use a Set<int> instead of a list<int> for the freqs. \$\endgroup\$JanDotNet– JanDotNet2019年01月30日 20:01:11 +00:00Commented Jan 30, 2019 at 20:01
Caveat : I don't know F#.
I liked your solution, the use of algebraic data types, pattern matching and use of list processing via fold and higher order functions are good.
However I'm trying to apply a functional style in python (which I'm learning) and I think there is a way to do this that use simple list data structures, with (list) comprehension or the equivalent
Here is my attempt in python to present an alternative approach
import functools
import itertools
fchanges = []
with open('frequency_input') as f:
for line in f:
fchanges.append(int(line))
frequencies = [sum(fchanges[:index]) for index in range(1, len(fchanges)+1)]
skew = frequencies[-1]
print('Skew is ' + str(skew))
accumulator = itertools.accumulate(itertools.cycle(fchanges))
prefix = itertools.takewhile(lambda x: (x + skew) not in frequencies, accumulator)
# takewhile consumes the value I'm looking for to check the predicate, hence this hack
# don't need the list, memory optimization - just need the length
plen = functools.reduce(lambda x, y: x+1, prefix, 0)
accumulator = itertools.accumulate(itertools.cycle(fchanges))
print('found first repetition ' + str(next(itertools.islice(accumulator, plen, None)) + skew))
Happy to receive feedback on my attempt too for any observers.
Explore related questions
See similar questions with these tags.