I think you’ll find the data was deliberately designed that way
Livebook. solution using maps
My solution is very similar to @seeplusplus
Didn’t realize I could use the rules to sort the pages, until I got a bit stuck in part 2 and decided to rethink the whole thing again
I used a sort function to Enum.sort based on the rules. This created a list of update entries each with {original_order, sorted_order). From there it was a simple sort for; part1 == and part 2 !=, then summing the middle value in each list.
Here’s the code for part 1 and part 2:
defmodule Aoc.Day05 do
def run() do
IO.puts("Part 1 : result: #{part1()}")
IO.puts("Part 2 : result: #{part2()}")
end
def part1() do
get_ordered_lists()
|> Enum.filter(fn {original, ordered} -> original == ordered end)
|> Enum.map(fn {l, _o} -> Enum.at(l, trunc(Enum.count(l) / 2)) end)
|> Enum.sum()
end
def part2() do
get_ordered_lists()
|> Enum.filter(fn {original, ordered} -> original != ordered end)
|> Enum.map(fn {_l, o} -> Enum.at(o, trunc(Enum.count(o) / 2)) end)
|> Enum.sum()
end
def get_ordered_lists() do
{rules, updates} = get_input()
updates
|> Enum.map(fn page_list -> {page_list, check_page_order(page_list, rules)} end)
end
def check_page_order(page_list, rules) do
page_list
|> Enum.sort(fn n1, n2 ->
rule = Map.get(rules, n1, [n1])
not Enum.member?(rule, n2)
end)
|> Enum.reverse()
end
# parsed_rules is map of page => [pages list must be after]
# parsed_update is list of updates, each update is a list of original order.
def get_input() do
[rules, updates] =
String.split(Atom.to_string(__MODULE__), ".")
|> List.last()
|> String.downcase()
|> then(fn day -> "lib/#{day}/input.txt" end)
|> File.read!()
|> String.split("\n\n")
parsed_rules =
rules
|> String.split("\n")
|> Enum.map(fn rule -> String.split(rule, "|") end)
|> Enum.map(fn [f, l] -> {String.to_integer(f), String.to_integer(l)} end)
|> Enum.group_by(fn {b, _a} -> b end)
|> Enum.map(fn {p, list} -> {p, list |> Enum.map(fn {_p, i} -> i end)} end)
|> Enum.into(%{})
parsed_updates =
updates
|> String.split("\n")
|> Enum.map(fn nums -> String.split(nums, ",") end)
|> Enum.map(fn list -> list |> Enum.map(fn strint -> String.to_integer(strint) end) end)
{parsed_rules, parsed_updates}
end
end
Many problems in AoC are NOT the general case. Given input is built in a specific way to ensure there is a solution. Those “meta” bias can sometimes be used to find shortcuts.
Interesting, I did not know that. It’s my first time trying AoC.
Another possible condition that would cause confusion is if the rule-set is not transitive.
1|2
2|3
3|11,2,3
No way to sort that to fit all the rules.
Another one would be if the rules form islands.
1|2
3|4
4,3,2,1
There are many ways to sort that like 1,2,3,4
and 1,3,2,4
Speaking of which, if the data has an even number of elements then there is not middle element.
I spent a lot of time fretting over these possibilities. I guess the best way to do this would be to check for them as assumptions before trying to handle them somehow.
Hey!
First, I have grouped the rules by their second number. For each number of a page, I get the relevant rules from this map. I then check whether the rest of my list has any numbers in common with the list of first numbers of the relevant rules. If so, it means that the page violates a rule.
Source Code:
$ ./day05.exs
Part1. Sum: 4790
Part 1 + I/O done in 5729 µs
Part2. Sum: 6319
Part 2 done in 1605 µs
File day05.exs
:
#!/usr/bin/env elixir
# AoC 2024. day 5.
defmodule Part1 do
@spec order_ok?([integer()], MapSet.t()) :: boolean()
def order_ok?(numbers, rules)
def order_ok?([], _set), do: true
def order_ok?([number | numbers], rules) do
# If for all the following `numbers` x after `number`, we cannot find a rule x|number
# Then `number` is at the right position
Enum.all?(numbers, fn x -> !MapSet.member?(rules, {x, number}) end)
&& order_ok?(numbers, rules)
end
end
# part 1. accumulates the data and at the same time solve part 1.
# it could be decoupled
start = System.monotonic_time(:microsecond)
{rules, bad_updates, sum} = File.stream!("../day05.txt")
|> Enum.reduce({MapSet.new(), [], 0}, fn line, {rules, updates, sum} ->
case Regex.run(~r/^(?:(\d+)\|(\d+))|\d+(?:,\d+)+/, line) do
[_, fst, snd] -> # first, accumulate the rules in the MapSet
{MapSet.put(rules, {String.to_integer(fst), String.to_integer(snd)}), updates, sum}
nil -> {rules, updates, sum}
[update] -> # then, check all the updates
update = update |> String.split(",") |> Enum.map(&String.to_integer/1)
if Part1.order_ok?(update, rules) do
# take the middle number and add it to sum
{rules, updates, sum + :lists.nth(div(length(update), 2) + 1, update)}
else
{rules, [update | updates], sum}
end
end
end)
IO.puts("Part1. Sum: #{sum}")
elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Part 1 + I/O done in #{elapsed} µs"
# part 2
start = System.monotonic_time(:microsecond)
Enum.reduce(bad_updates, 0, fn update, sum ->
update = Enum.sort(update, fn n1, n2 -> MapSet.member?(rules, {n1, n2}) end)
sum + :lists.nth(div(length(update), 2) + 1, update)
end)
|> IO.inspect(label: "Part2. Sum")
elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Part 2 done in #{elapsed} µs"
I used local topological sort over the local graph formed by pair of nodes in each updated runs under 30ms so seems fair enough
Why not
not is_in_rules
@king-11 Figured I’d rather bring some discussion here than Reddit.
Looks like it took about 36ms for the swap method in part 2. (Using Livebook, so bear with my formatting).
Edit: Got it down to around 29ms now.
Part 1
[rules, updates] = Kino.FS.file_path("day5_input.txt")
|> File.read!()
|> String.trim()
|> String.split("\n\n")
|> Enum.map(fn section ->
String.trim(section)
|> String.split("\n")
end)
rules = Enum.map(rules, fn pair ->
String.split(pair, "|")
|> Enum.map(&String.to_integer/1)
|> List.to_tuple()
end)
updates = Enum.map(updates, fn group ->
String.split(group, ",")
|> Enum.map(&String.to_integer/1)
end)
defmodule UpdateValidator do
@doc """
Check if all {x before y} rules pass for the given update.
"""
def is_valid_update?(update, rules) do
not Enum.any?(rules, fn {x, y} -> not is_before?(x, y, update) end)
end
@doc """
Check if x occurs before y in the given list
"""
def is_before?(x, y, list = [first | rest]) do
if x in list and y in list do
if y == first, do: false, else: is_before?(x, y, rest)
else
true
end
end
end
sum_middle_numbers = fn updates -> updates
|> Enum.map(& {length(&1) |> Integer.floor_div(2), &1}) # Get index of middle num.
|> Enum.map(fn {middle_index, update} ->
Enum.at(update, middle_index)
end) # Extract value at index
|> Enum.sum()
end
valid_updates = Enum.filter(updates, fn update ->
UpdateValidator.is_valid_update?(update, rules)
end)
result = sum_middle_numbers.(valid_updates)
----
Part 2
defmodule UpdateFixer do
import UpdateValidator
@doc """
Recursively applies fixes until no more
modifications are made (some fixes break other rules).
"""
def fix_update(update, rules) do
recursively_fix_update({true, update}, rules)
end
# Simply facilitates recursive calling.
defp recursively_fix_update({true, update}, rules) do
result = do_fix_update(update, rules)
recursively_fix_update(result, rules)
end
defp recursively_fix_update({false, update}, _), do: update
# Applies fixes for all rules by swapping values.
defp do_fix_update(update, rules, modified? \\ false)
defp do_fix_update(update, [], modified?), do: {modified?, update}
defp do_fix_update(update, [{x, y} | rules], modified?) do
{update_, modified?} = if not is_before?(x, y, update) do
# Swap values if this rule failed validation.
update_ = update
|> Enum.map(& {&1}) # Pack into tuple to make compat. with keyreplace
|> List.keyreplace(x, 0, {y})
|> List.keyreplace(y, 0, {x})
|> Enum.map(fn {val} -> val end)
{update_, true}
else
# Otherwise, pass update as-is.
{update, modified?}
end
# Check next rule.
do_fix_update(update_, rules, modified?)
end
end
start = System.monotonic_time(:microsecond)
result = updates -- valid_updates # Invalid updates
|> Enum.map(fn update -> UpdateFixer.fix_update(update, rules) end)
|> sum_middle_numbers.()
IO.puts("Part 2 result: #{result}")
elapsed = System.monotonic_time(:microsecond) - start
IO.puts "Part 2 finished in #{elapsed / 1000}ms"
Part 2 result: ----
Part 2 finished in 29.442ms
The first task of today was much easier than yesterdays first task. Took a while, but i made it:
for part 2 im not really sure how to debug why it doesnt pass on the real input. It passes on the test input. Not sure what i can do to debug. Any help? Or tips to fix my code maybe it has bugs …
i mean good thing santa doesnt get angry even if i complete the parts next day right
I don’t have much advice to offer on debugging, but I can offer a hint: after you run fix_invalid_update()
on an update, check if it’s valid again. I think you’ll find that some still aren’t
Fun problem!
defmodule AOC.Y2024.Day5 do
@moduledoc false
use AOC.Solution
@impl true
def load_data() do
Data.load_day(2024, 5, "\n\n")
|> then(fn [rules, updates] ->
{
rules
|> String.split("\n")
|> Enum.map(&(&1 |> String.split("|") |> List.to_tuple()))
|> MapSet.new(),
updates
|> String.split("\n")
|> Enum.map(&String.split(&1, ","))
}
end)
end
@impl true
def part_one({rules, updates}) do
updates
|> Enum.filter(fn update -> right_order?(rules, update) end)
|> General.map_sum(fn update ->
update |> Enum.at(div(length(update), 2)) |> String.to_integer()
end)
end
@impl true
def part_two({rules, updates}) do
updates
|> Enum.reject(fn update -> right_order?(rules, update) end)
|> Enum.map(fn update ->
Enum.sort(update, fn a, b -> not MapSet.member?(rules, {b, a}) end)
end)
|> General.map_sum(fn update ->
update |> Enum.at(div(length(update), 2)) |> String.to_integer()
end)
end
defp right_order?(rules, update) do
update
|> Enum.with_index(1)
|> Enum.map(fn {v, i} -> update |> Enum.slice(i..-1//1) |> Enum.map(fn k -> {v, k} end) end)
|> Enum.all?(fn order ->
Enum.all?(order, fn {a, b} -> !MapSet.member?(rules, {b, a}) end)
end)
end
end
You’re right … Thank you.
I think I missed this as I was refactoring the code after part2 was complete. Here’s the revised code:
def check_page_order(page_list, rules) do
page_list
Enum.sort(fn n1, n2 →
rule = Map.get(rules, n1, [n1])
not Enum.member?(rule, n2)
end)
Enum.reverse()
end
I’ve used digraph and digraph_utils.
Doing so may be overkill but digraph_utils really helped quickly solving part 2.
defmodule D5 do
def p1(file) do
{digraph, updates} = parse(file)
updates
|> Enum.filter(fn update -> right_order?(update, digraph) end)
|> Enum.map(fn update -> Enum.at(update, div(Enum.count(update), 2)) end)
|> Enum.sum()
end
defp parse(file) do
[section1, section2] =
file
|> File.read!()
|> String.split("\n\n")
digraph = :digraph.new()
section1
|> String.split("\n")
|> Enum.map(fn rule -> rule |> String.split("|") |> Enum.map(&String.to_integer/1) end)
|> Enum.each(fn [n1, n2] ->
:digraph.add_vertex(digraph, n1)
:digraph.add_vertex(digraph, n2)
:digraph.add_edge(digraph, n1, n2)
end)
updates =
section2
|> String.split(["\n"])
|> Enum.map(fn u -> String.split(u, ",") |> Enum.map(&String.to_integer/1) end)
{digraph, updates}
end
defp right_order?([n1, n2 | rest], digraph) do
case :digraph.get_short_path(digraph, n1, n2) do
[^n1, ^n2] ->
right_order?([n2 | rest], digraph)
_ ->
false
end
end
defp right_order?(_, _) do
true
end
def p2(file) do
{digraph, updates} = parse(file)
updates
|> Enum.filter(fn update -> not right_order?(update, digraph) end)
|> Enum.map(fn update ->
digraph
|> :digraph_utils.subgraph(update)
|> :digraph_utils.topsort()
end)
|> Enum.map(fn update -> Enum.at(update, div(Enum.count(update), 2)) end)
|> Enum.sum()
end
end
Here’s how I did this:
defmodule AdventOfCode.Y2024.Day05 do
alias AdventOfCode.Helpers.{InputReader, Transformers}
def input, do: InputReader.read_from_file(2024, 5)
def run(input \\ input()) do
input = parse(input)
{run_1(input), run_2(input)}
end
defp run_1(input) do
input
|> Enum.filter(fn {a, b} -> a == b end)
|> Enum.map(fn {a, _} -> a |> Enum.at(div(length(a), 2)) end)
|> Enum.sum()
end
defp run_2(input) do
input
|> Enum.filter(fn {a, b} -> a != b end)
|> Enum.map(fn {_, b} -> b |> Enum.at(div(length(b), 2)) end)
|> Enum.sum()
end
def parse(data \\ input()) do
[deps, updates] = Transformers.sections(data)
given_sorted_pair({parse_deps(deps), parse_updates(updates)})
end
defp parse_deps(deps) do
for line <- Transformers.lines(deps),
into: MapSet.new(),
do: String.split(line, "|") |> Enum.map(&String.to_integer/1) |> List.to_tuple()
end
defp parse_updates(updates) do
for line <- Transformers.lines(updates) do
for update <- String.split(line, ","), do: String.to_integer(update)
end
end
defp given_sorted_pair({deps, updates}) do
for update <- updates do
{update, Enum.sort(update, &({&1, &2} in deps))}
end
end
end
Nice. I was thinking of giving digraph a try, but confused myself into thinking it would sort on the values, not the edges. Nice to see it in action.
I need to not code at 12am on workdays. This took me longer than I wanted, but I was out today. I’m just glad to have it done before the next one.
Part 1:
#!/usr/bin/env elixir
defmodule Day5.Part1 do
defp parse(str) do
[sorting_rules, [""], updates] =
str
|> String.split("\n")
|> Enum.chunk_by(fn x -> x == "" end)
sorting_rules =
sorting_rules
|> Enum.reduce(
%{},
fn str, state ->
[l, r] =
str
|> String.split("|")
Map.update(state, :"#{l}", [r], fn tail -> [r|tail] end)
end
)
updates =
updates
|> Enum.map(fn str -> String.split(str, ",") end)
{sorting_rules, updates}
end
def sorted?([_], _sorting_rules), do: true
def sorted?([a | tail], must_suffix) do
tail
|> Enum.all?(fn t ->
must_suffix_t = must_suffix[:"#{t}"]
is_nil(must_suffix_t) or a not in must_suffix_t
end) and sorted?(tail, must_suffix)
end
def solve() do
{sorting_rules, updates} =
File.read!("05/input.txt")
|> parse()
updates
|> Enum.filter(
fn update ->
sorted?(update, sorting_rules)
end
)
|> Enum.reduce(
0,
fn update, acc ->
middle_num =
update
|> Enum.at(
((update
|> Enum.count()) / 2)
|> Float.floor()
|> trunc()
)
|> String.to_integer()
acc + middle_num
end
)
|> IO.puts()
end
end
Day5.Part1.solve()
Part 2:
#!/usr/bin/env elixir
defmodule Day5.Part2 do
defp parse(str) do
[sorting_rules, [""], updates] =
str
|> String.split("\n")
|> Enum.chunk_by(fn x -> x == "" end)
sorting_rules =
sorting_rules
|> Enum.reduce(
%{},
fn str, state ->
[l, r] =
str
|> String.split("|")
Map.update(state, :"#{l}", [r], fn tail -> [r|tail] end)
end
)
updates =
updates
|> Enum.map(fn str -> String.split(str, ",") end)
{sorting_rules, updates}
end
def sorted?([_], _sorting_rules), do: true
def sorted?([a | tail], must_suffix) do
tail
|> Enum.all?(fn t ->
must_suffix_t = must_suffix[:"#{t}"]
is_nil(must_suffix_t) or a not in must_suffix_t
end) and sorted?(tail, must_suffix)
end
defp place(x, acc, rules) do
index = acc
|> Enum.map(
fn item ->
if not is_nil(rules[:"#{item}"]) do
x in rules[:"#{item}"]
else
false
end
end
)
|> Enum.find_index(fn b -> b == false end) || acc |> Enum.count()
List.insert_at(acc, index, x)
end
defp sort(list, rules) do
list
|> Enum.reduce(
[],
fn x, acc ->
place(x, acc, rules)
end
)
end
def solve() do
{sorting_rules, updates} =
File.read!("05/input.txt")
|> parse()
updates
|> Enum.filter(
fn update ->
not sorted?(update, sorting_rules)
end
)
|> Enum.map(
fn update ->
update
|> sort(sorting_rules)
end
)
|> Enum.reduce(
0,
fn update, acc ->
middle_num =
update
|> Enum.at(
((update
|> Enum.count()) / 2)
|> Float.floor()
|> trunc()
)
|> String.to_integer()
acc + middle_num
end
)
|> IO.puts()
end
end
Day5.Part2.solve()
is this correct algorithm for part2?
invalid: [[47, 89, 77, 74, 61, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]]
current: [47, 89, 77, 74, 61, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 89 at index 1 with 77 at index 2"
current: [47, 77, 89, 74, 61, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 47 at index 0 with 74 at index 3"
current: [74, 77, 89, 47, 61, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 89 at index 2 with 61 at index 4"
current: [74, 77, 61, 47, 89, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 77 at index 1 with 61 at index 2"
current: [74, 61, 77, 47, 89, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 89 at index 4 with 53 at index 5"
current: [74, 61, 77, 47, 53, 89, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 47 at index 3 with 53 at index 4"
current: [74, 61, 77, 53, 47, 89, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 77 at index 2 with 53 at index 3"
current: [74, 61, 53, 77, 47, 89, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 61 at index 1 with 53 at index 2"
current: [74, 53, 61, 77, 47, 89, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 74 at index 0 with 53 at index 1"
current: [53, 74, 61, 77, 47, 89, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 89 at index 5 with 82 at index 6"
current: [53, 74, 61, 77, 47, 82, 89, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 61 at index 2 with 82 at index 5"
current: [53, 74, 82, 77, 47, 61, 89, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 74 at index 1 with 82 at index 2"
current: [53, 82, 74, 77, 47, 61, 89, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 61 at index 5 with 38 at index 7"
current: [53, 82, 74, 77, 47, 38, 89, 61, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 47 at index 4 with 38 at index 5"
current: [53, 82, 74, 77, 38, 47, 89, 61, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 77 at index 3 with 38 at index 4"
current: [53, 82, 74, 38, 77, 47, 89, 61, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 82 at index 1 with 38 at index 3"
current: [53, 38, 74, 82, 77, 47, 89, 61, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 74 at index 2 with 73 at index 9"
current: [53, 38, 73, 82, 77, 47, 89, 61, 45, 74, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 45 at index 8 with 15 at index 10"
current: [53, 38, 73, 82, 77, 47, 89, 61, 15, 74, 45, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 89 at index 6 with 15 at index 8"
current: [53, 38, 73, 82, 77, 47, 15, 61, 89, 74, 45, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 77 at index 4 with 15 at index 6"
current: [53, 38, 73, 82, 15, 47, 77, 61, 89, 74, 45, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 45 at index 10 with 76 at index 11"
current: [53, 38, 73, 82, 15, 47, 77, 61, 89, 74, 76, 45, 41, 24, 56, 55, 67, 68, 95]
"replace 82 at index 3 with 76 at index 10"
current: [53, 38, 73, 76, 15, 47, 77, 61, 89, 74, 82, 45, 41, 24, 56, 55, 67, 68, 95]
"replace 38 at index 1 with 76 at index 3"
current: [53, 76, 73, 38, 15, 47, 77, 61, 89, 74, 82, 45, 41, 24, 56, 55, 67, 68, 95]
"replace 47 at index 5 with 41 at index 12"
current: [53, 76, 73, 38, 15, 41, 77, 61, 89, 74, 82, 45, 47, 24, 56, 55, 67, 68, 95]
"replace 15 at index 4 with 41 at index 5"
current: [53, 76, 73, 38, 41, 15, 77, 61, 89, 74, 82, 45, 47, 24, 56, 55, 67, 68, 95]
"replace 15 at index 5 with 24 at index 13"
current: [53, 76, 73, 38, 41, 24, 77, 61, 89, 74, 82, 45, 47, 15, 56, 55, 67, 68, 95]
"replace 15 at index 13 with 55 at index 15"
current: [53, 76, 73, 38, 41, 24, 77, 61, 89, 74, 82, 45, 47, 55, 56, 15, 67, 68, 95]
"replace 89 at index 8 with 55 at index 13"
current: [53, 76, 73, 38, 41, 24, 77, 61, 55, 74, 82, 45, 47, 89, 56, 15, 67, 68, 95]
"replace 77 at index 6 with 55 at index 8"
current: [53, 76, 73, 38, 41, 24, 55, 61, 77, 74, 82, 45, 47, 89, 56, 15, 67, 68, 95]
"replace 24 at index 5 with 55 at index 6"
current: [53, 76, 73, 38, 41, 55, 24, 61, 77, 74, 82, 45, 47, 89, 56, 15, 67, 68, 95]
"replace 73 at index 2 with 67 at index 16"
current: [53, 76, 67, 38, 41, 55, 24, 61, 77, 74, 82, 45, 47, 89, 56, 15, 73, 68, 95]
"replace 45 at index 11 with 68 at index 17"
current: [53, 76, 67, 38, 41, 55, 24, 61, 77, 74, 82, 68, 47, 89, 56, 15, 73, 45, 95]
"replace 24 at index 6 with 68 at index 11"
current: [53, 76, 67, 38, 41, 55, 68, 61, 77, 74, 82, 24, 47, 89, 56, 15, 73, 45, 95]
"replace 55 at index 5 with 68 at index 6"
current: [53, 76, 67, 38, 41, 68, 55, 61, 77, 74, 82, 24, 47, 89, 56, 15, 73, 45, 95]
"replace 15 at index 15 with 95 at index 18"
current: [53, 76, 67, 38, 41, 68, 55, 61, 77, 74, 82, 24, 47, 89, 56, 95, 73, 45, 15]
"replace 56 at index 14 with 95 at index 15"
current: [53, 76, 67, 38, 41, 68, 55, 61, 77, 74, 82, 24, 47, 89, 95, 56, 73, 45, 15]
"replace 77 at index 8 with 95 at index 14"
fixed: [[53, 76, 67, 38, 41, 68, 55, 61, 95, 74, 82, 24, 47, 89, 77, 56, 73, 45, 15]]
If i a find a rule for a number in an update, where the update number should be before the the second number in the rule, i swap them with two simple replace_at
IO.inspect(acc_, label: "current", charlists: :as_lists, limit: :infinity)
IO.inspect("replace #{b} at index #{b_index} with #{next} at index #{new_next_id}")
acc_ |> List.replace_at(new_next_id, b) |> List.replace_at(b_index, next)
this part isnt actually rocket science … but i wonder if its correct … well hard to read so its more like is the assumptions correct
i have looked at some of the rules manually for this example, and it seems to checkout
for ex the first one is a good example for all the others, and it represents what all the others do
invalid: [[47, 89, 77, 74, 61, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]]
current: [47, 89, 77, 74, 61, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
"replace 89 at index 1 with 77 at index 2"
current: [47, 77, 89, 74, 61, 53, 82, 38, 45, 73, 15, 76, 41, 24, 56, 55, 67, 68, 95]
here 89 swaps place with 77.
Is it correct assumption that the rules is always left to right oriented maybe?
The full “documented” algorithm in its full glory:
defp fix_invalid_update(update, rules) do
update
# reduce with update itself as accumulator
|> Enum.reduce(update, fn next, acc ->
rules
# get alll the rules for the number "next",
# assuming i should only get rules where number "next" is the first number in the rule
|> Enum.filter(&(&1 |> Tuple.to_list() |> hd() == next))
# reduce the rules with the current acumulator as accumulator (the update)
|> Enum.reduce(acc, fn {_, b}, acc_ ->
b_index = acc_ |> Enum.find_index(&(&1 == b))
# find new position for next, since it can have changed since last iteration
new_next_id = acc_ |> Enum.find_index(&(&1 == next))
if b_index == nil do
acc_
else
if b_index < new_next_id do
IO.inspect(acc_, label: "current", charlists: :as_lists, limit: :infinity)
IO.inspect("replace #{b} at index #{b_index} with #{next} at index #{new_next_id}")
acc_ |> List.replace_at(new_next_id, b) |> List.replace_at(b_index, next)
else
acc_
end
end
end)
end)
end
havent read the others solutions yet. If i cant make it work myself its not worth the cheat