Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Day 7 - Part 1 & 2 #3

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This is my take on the [advent of code](https://adventofcode.com/) in Go.
You will find in this repository the resolution of problems day by day.
In each one them, you can find the programming puzzles problem in the README.

My goal is to maintain my Go level, practice algorithms skills and obvisouly learn new stuffs!
My goal is to maintain my Go level, practice algorithms skills and obviously learn new stuffs!
My friends [ablqk](https://github.com/ablqk) and [floppyzedolfin](https://github.com/floppyzedolfin/) are the ones
that made me dive into this challenge. Also, they will be my reviewers.

Expand Down
3 changes: 3 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
day04 "github.com/doniacld/adventofcode/puzzles/2020/04"
day05 "github.com/doniacld/adventofcode/puzzles/2020/05"
day06 "github.com/doniacld/adventofcode/puzzles/2020/06"
day07 "github.com/doniacld/adventofcode/puzzles/2020/07"
"github.com/doniacld/adventofcode/puzzles/solver"
"log"
)
Expand Down Expand Up @@ -53,6 +54,8 @@ func main() {
s = day05.New("./puzzles/2020/05/input.txt")
case 6:
s = day06.New("./puzzles/2020/06/input.txt")
case 7:
s = day07.New("./puzzles/2020/07/input.txt", "shiny gold")
}

out, err := s.Solve()
Expand Down
2 changes: 1 addition & 1 deletion puzzles/2020/05/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

You board your plane only to discover a new problem: you dropped your boarding pass! You aren't sure which seat is yours, and all of the flight attendants are busy with the flood of people that suddenly made it through passport control.

You write a quick program to use your phone's camera to scan all of the nearby boarding passes (your puzzle input); perhaps you can find your seat through process of elimination.
You write a quick program to use your phone's camera to scan all the nearby boarding passes (your puzzle input); perhaps you can find your seat through process of elimination.

Instead of zones or groups, this airline uses binary space partitioning to seat people. A seat might be specified like FBFBBFFRLR, where F means "front", B means "back", L means "left", and R means "right".

Expand Down
69 changes: 69 additions & 0 deletions puzzles/2020/07/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Day 7: Handy Haversacks

## Part One

You land at the regional airport in time for your next flight. In fact, it looks like you'll even have time to grab some food: all flights are currently delayed due to issues in luggage processing.

Due to recent aviation regulations, many Bags (your puzzle input) are being enforced about bags and their contents; bags must be color-coded and must contain specific quantities of other color-coded bags. Apparently, nobody responsible for these regulations considered how long they would take to enforce!

For example, consider the following Bags:

light red bags contain 1 bright white bag, 2 muted yellow bags.
dark orange bags contain 3 bright white bags, 4 muted yellow bags.
bright white bags contain 1 shiny gold bag.
muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.
shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.
dark olive bags contain 3 faded blue bags, 4 dotted .black bags.
vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
faded blue bags contain no other bags.
dotted black bags contain no other bags.

These Bags specify the required contents for 9 bag types. In this example, every faded blue bag is empty, every vibrant plum bag contains 11 bags (5 faded blue and 6 dotted black), and so on.

You have a shiny gold bag. If you wanted to carry it in at least one other bag, how many different bag colors would be valid for the outermost bag? (In other words: how many colors can, eventually, contain at least one shiny gold bag?)

In the above Bags, the following options would be available to you:

A bright white bag, which can hold your shiny gold bag directly.
A muted yellow bag, which can hold your shiny gold bag directly, plus some other bags.
A dark orange bag, which can hold bright white and muted yellow bags, either of which could then hold your shiny gold bag.
A light red bag, which can hold bright white and muted yellow bags, either of which could then hold your shiny gold bag.

So, in this example, the number of bag colors that can eventually contain at least one shiny gold bag is 4.

How many bag colors can eventually contain at least one shiny gold bag? (The list of Bags is quite long; make sure you get all of it.)

(The list of rules is quite long; make sure you get all of it.)

Your puzzle answer was 161.

## Part Two

It's getting pretty expensive to fly these days - not because of ticket prices, but because of the ridiculous number of bags you need to buy!

Consider again your shiny gold bag and the rules from the above example:

faded blue bags contain 0 other bags.
dotted black bags contain 0 other bags.
vibrant plum bags contain 11 other bags: 5 faded blue bags and 6 dotted black bags.
dark olive bags contain 7 other bags: 3 faded blue bags and 4 dotted black bags.

So, a single shiny gold bag must contain 1 dark olive bag (and the 7 bags within it) plus 2 vibrant plum bags (and the 11 bags within each of those): 1 + 1*7 + 2 + 2*11 = 32 bags!

Of course, the actual rules have a small chance of going several levels deeper than this example; be sure to count all of the bags, even if the nesting becomes topologically impractical!

Here's another example:

shiny gold bags contain 2 dark red bags.
dark red bags contain 2 dark orange bags.
dark orange bags contain 2 dark yellow bags.
dark yellow bags contain 2 dark green bags.
dark green bags contain 2 dark blue bags.
dark blue bags contain 2 dark violet bags.
dark violet bags contain no other bags.

In this example, a single shiny gold bag must contain 126 other bags.

How many individual bags are required inside your single shiny gold bag?

Your puzzle answer was 30899.
27 changes: 27 additions & 0 deletions puzzles/2020/07/bags/bags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package bags

// Bags defines a map with the bag color as key and its family as value
type Bags map[string]bagFamily
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a (minor) problem in having an exposed type containing non-exposed types.
In this case, for a bigger project, either

  • expose BagFamily
  • make an interface for Bag that something implements


// NewBags initializes the Bags map
func NewBags() Bags {
return make(map[string]bagFamily, 0)
}

// bagFamily defines the list of parent's bag and children ones
type bagFamily struct {
parents []bag
children []bag
}

// a bag is composed of a number and a coloured name
type bag struct {
nb int
name string
}

// newBagFamily returns a new bagFamily
func newBagFamily() bagFamily {
b := make([]bag, 0)
return bagFamily{b, b}
floppyzedolfin marked this conversation as resolved.
Show resolved Hide resolved
}
23 changes: 23 additions & 0 deletions puzzles/2020/07/bags/childrencounter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package bags

// CountBags returns the content of a bag
func (r Bags) CountBags(target string) int {
_, ok := r[target]
if !ok {
return 0
}

// browse the children of the target
// decrease from one because of the target bag that is counted
return r.countBags(target) - 1
}

// countBags counts the bags recursively
func (r Bags) countBags(target string) int {
counter := 1
v := r[target]
for _, c := range v.children {
counter += c.nb * r.countBags(c.name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also perform some memoisation and store the number of bags inside [nameX] in bag.

This would avoid having to compute twice the contents of "shiny brown bag" (and of its children)

}
return counter
}
35 changes: 35 additions & 0 deletions puzzles/2020/07/bags/childrencounter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package bags

import (
"testing"

"github.com/doniacld/adventofcode/internal/filereader"
"github.com/stretchr/testify/assert"
)

func TestBags_CountBags(t *testing.T) {
tt := []struct {
description string
file string
input string
expected int
}{
{"several branches case", "./../resources/input-test.txt", "shiny gold", 32},
{"linear case", "./../resources/input-test-part2.txt", "shiny gold", 126},
{"does not exist", "./../resources/input-test-part2.txt", "bright gold", 0},

}

for _, tc := range tt {
t.Run(tc.description, func(t *testing.T) {
r := NewBags()
_ = filereader.ReadAndExtract(tc.file, func(line string) error {
r.ParseLine(line)
return nil
})

out := r.CountBags(tc.input)
assert.Equal(t, tc.expected, out)
})
}
}
71 changes: 71 additions & 0 deletions puzzles/2020/07/bags/parentscounter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package bags

// CountPossiblePaths returns the number of possible bags paths to get the target bag
func (r Bags) CountPossiblePaths(target string) int {
var count int
// maintain a list of the seen bags to avoid to count duplicates
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// maintain a list of the seen bags to avoid to count duplicates
// maintain a list of the bags seen to avoid to count duplicates

s := make(seen, 0)

// loop until the visit of all paths ends
for true {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really need a loop here ? An infinite one, on top of that ?

v, ok := r[target]
if !ok {
return count
}
// browse the parents of the target
return r.browseParents(v.parents, s, count)
}
// should not happened ?
return count
}

// browseParents browse the parents of a target bag
// and counts the number of ways to access this bag
func (r Bags) browseParents(v []bag, s seen, counter int) int {
// browse the list of parents of the target bag
for i := 0; i < len(v); i++ {
Copy link
Collaborator

@floppyzedolfin floppyzedolfin Jul 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for _, parent := range v {

// make the parent the new target
target := v[i].name

// has no parent and is not seen
if !r.hasParent(target) && !s.isSeen(target) {
s.insertSeen(target)
counter++
continue
}

// has parents and is not seen
if !s.isSeen(target) {
s.insertSeen(target)
counter++
counter = r.browseParents(r[target].parents, s, counter)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me you can start the for loop with counter++

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rewrite these two (three) ifs thusly :

if !isSeen(target) {
  // include this bag as seen
  s.insertSeen(target)
  counter++

  // check for parents, and include them if any
  if r.hasParent(target) {
    counter = r.browseParents(r[target].parents, s, counter)
  }
}

}
// if seen, we just continue and do nothing
}
return counter
}

// hasParent returns true if a target has parents or false if it is a root
func (r Bags) hasParent(target string) bool {
if v, ok := r[target]; ok {
return len(v.parents) != 0
}
return false
}

// seen holds a map of seen bags
type seen map[string]struct{}

// insertSeen inserts a bag into the seen map
func (s seen) insertSeen(bag string) {
s[bag] = struct{}{}
}

// isSeen returns true if the target is already seen
func (s seen) isSeen(target string) bool {
if _, ok := s[target]; ok {
return true
}
return false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about return ok ?

}

32 changes: 32 additions & 0 deletions puzzles/2020/07/bags/parentscounter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package bags

import (
"testing"

"github.com/doniacld/adventofcode/internal/filereader"
"github.com/stretchr/testify/assert"
)

func TestBags_CountPossiblePaths(t *testing.T) {
tt := []struct {
description string
file string
input string
expected int
}{
{"nominal case", "./../resources/input-test.txt", "shiny gold", 4},
}

for _, tc := range tt {
t.Run(tc.description, func(t *testing.T) {
r := NewBags()
_ = filereader.ReadAndExtract(tc.file, func(line string) error {
r.ParseLine(line)
return nil
})

out := r.CountPossiblePaths(tc.input)
assert.Equal(t, tc.expected, out)
})
}
}
60 changes: 60 additions & 0 deletions puzzles/2020/07/bags/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package bags

import (
"log"
"regexp"
"strconv"
"strings"
)

var (
regSourceBag = regexp.MustCompile("^([a-z]+ [a-z]+) bags contain (.*).")
regContentBags = regexp.MustCompile("([0-9]+) ([a-z]+ [a-z]+) bags?")
Copy link
Collaborator

@floppyzedolfin floppyzedolfin Jul 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really need to split the words apart ?

worldsapart

)

// ParseLine takes a line from the input and stores all the bags with their associated
// source bags and the number of each one
func (r Bags) ParseLine(s string) {
// extract the source bag
rule := regSourceBag.FindStringSubmatch(s)

// extract the bag content
if rule[2] == "no other bags" {
return
}
content := regContentBags.FindAllStringSubmatch(rule[2], -1)

// store all the bags
for _, l := range content {
// extract the number of bags
nb, err := strconv.Atoi(l[1])
if err != nil {
log.Fatal(err)
}
// extract the bag name
leave := strings.Join(l[2:], "")
Copy link
Collaborator

@floppyzedolfin floppyzedolfin Jul 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this is the reason for the gif - you could be using l[2] instead of l[2:])

// insert the content bags as key and the source bag as value
r.insertParentBag(leave, bag{nb, rule[1]})
r.insertChildBag(rule[1], bag{nb, leave})
}
}

// insertParentBag insert a parent bag into the map
func (r Bags) insertParentBag(name string, parent bag) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use func (r *Bags) ...

v, ok := r[name]
if !ok {
r[name] = newBagFamily()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand - this value seems (to me) to be overridden 3 lines below.

}
v.parents = append(v.parents, parent)
r[name] = v
}

// insertChildBag inserts a child bag into the map
func (r Bags) insertChildBag(name string, child bag) {
v, ok := r[name]
if !ok {
r[name] = newBagFamily()
}
v.children = append(v.children, child)
r[name] = v
}
31 changes: 31 additions & 0 deletions puzzles/2020/07/bags/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package bags

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseRule(t *testing.T) {
tt := []struct {
description string
input string
expected Bags
}{
{"nominal case",
"light red bags contain 1 bright white bag, 2 muted yellow bags.",
map[string]bagFamily{
"light red": {[]bag(nil), []bag{{1, "bright white"}, {2, "muted yellow"}}},
"bright white": {[]bag{{1, "light red"}}, []bag(nil)},
"muted yellow": {[]bag{{2, "light red"}}, []bag(nil)}},
},
}

for _, tc := range tt {
t.Run(tc.description, func(t *testing.T) {
r := NewBags()
r.ParseLine(tc.input)
assert.Equal(t, tc.expected, r)
})
}
}
Loading