package main import ( "fmt" "math/big" "strings" ) type Map struct { data []rune width int height int } type Point struct { x, y int } func (p *Point) String() string { return fmt.Sprintf("(%d, %d)", p.x, p.y) } func (p *Point) IsBetween(p1 *Point, p2 *Point) bool { return isBetween(p.x, p1.x, p2.x) && isBetween(p.y, p1.y, p2.y) } // Returns true is a is between (x, y) disregarding // x, y order ( (x,y) is considered an interval. // The definition of between includes both ends func isBetween(a int, x int, y int) bool { if x > y { return isBetween(a, y, x) } return x <= a && a <= y } func (p *Point) Dist(p2 *Point) *Vector { return NewVecFromInt(p2.x-p.x, p2.y-p.y) } func NewVecFromInt(x, y int) *Vector { return &Vector{big.NewRat(int64(x), 1), big.NewRat(int64(y), 1)} } type Vector struct { x, y *big.Rat } func (v *Vector) String() string { return fmt.Sprintf("(%s, %s)", v.x, v.y) } func (v *Vector) Time(n *big.Rat) *Vector { return &Vector{new(big.Rat).Mul(v.x, n), new(big.Rat).Mul(v.y, n)} } func (v *Vector) Opp() *Vector { return v.Time(big.NewRat(-1, 1)) } func (v *Vector) Plus(vec2 *Vector) *Vector { return &Vector{new(big.Rat).Add(v.x, vec2.x), new(big.Rat).Add(v.y, vec2.y)} } func (v *Vector) AsPoint() *Point { if !v.x.IsInt() || !v.y.IsInt() { panic("Not a point") } return &Point{int(v.x.Num().Int64()), int(v.y.Num().Int64())} } func BaryCenter(p1, p2 *Point) *Vector { return &Vector{ big.NewRat(int64(p1.x+p2.x), 2), big.NewRat(int64(p1.y+p2.y), 2), } } func (m *Map) isInbound(point *Point) bool { return point.x >= 0 && point.x < m.width && point.y >= 0 && point.y < m.width } func (m *Map) Get(point *Point) (rune, bool) { if !m.isInbound(point) { return 0, false } idx := point.x + m.width*point.y return m.data[idx], true } func (m *Map) getByDataIndex(idx int) *Point { x := idx % m.width y := idx / m.width return &Point{x, y} } func NewMap(input string) *Map { if !strings.HasSuffix(input, "\n") { input += "\n" } width := strings.Index(input, "\n") height := strings.Count(input, "\n") data := []rune(strings.ReplaceAll(input, "\n", "")) return &Map{data, width, height} } func (m *Map) getAllAntennaFrequencies() <-chan rune { ch := make(chan rune) go func() { dejaVu := make(map[rune]bool) dejaVu['.'] = true for _, r := range m.data { if _, ok := dejaVu[r]; !ok { dejaVu[r] = true ch <- r } } close(ch) }() return ch } func (m *Map) getAllAntennaOfFrequency(freq rune) []*Point { ret := make([]*Point, 0) for i, r := range m.data { if r == freq { ret = append(ret, m.getByDataIndex(i)) } } return ret } type AntennaPair struct { antenna1 *Point antenna2 *Point freq rune } func (m *Map) getAllAntennaPairsOfFrequency(freq rune) <-chan *AntennaPair { antennas := m.getAllAntennaOfFrequency(freq) ch := make(chan *AntennaPair) go func() { for i := 0; i < len(antennas); i++ { for j := i + 1; j < len(antennas); j++ { ch <- &AntennaPair{antennas[i], antennas[j], freq} } } close(ch) }() return ch } func (m *Map) getEquidistantAntinodeLocationsForFreq(freq rune) <-chan *Point { ch := make(chan *Point) go func() { for p := range m.getAllAntennaPairsOfFrequency(freq) { bc := BaryCenter(p.antenna1, p.antenna2) dist := p.antenna1.Dist(p.antenna2).Time(big.NewRat(3, 2)) antiNode1 := bc.Plus(dist).AsPoint() antiNode2 := bc.Plus(dist.Opp()).AsPoint() if m.isInbound(antiNode1) { ch <- antiNode1 } if m.isInbound(antiNode2) { ch <- antiNode2 } } close(ch) }() return ch } func (m *Map) generateInlinePoints(p *Point, stepX, stepY int, filter func(*Point) bool, ch chan<- *Point) { for { p = &Point{p.x + stepX, p.y + stepY} if !m.isInbound(p) { return } if filter(p) { ch <- p } } } func (m *Map) getStep(p *AntennaPair) (int, int) { dist := p.antenna1.Dist(p.antenna2) if dist.x.Cmp(big.NewRat(0, 1)) == 0 { return 0, 1 } if dist.y.Cmp(big.NewRat(0, 1)) == 0 { return 1, 0 } step := new(big.Rat).Quo(dist.y, dist.x) stepX := int(step.Denom().Int64()) stepY := int(step.Num().Int64()) if stepX < 0 { stepX = -stepX } return stepX, stepY } func (m *Map) getCollinearAntinodeLocationsFreq(freq rune) <-chan *Point { ch := make(chan *Point) go func() { for pair := range m.getAllAntennaPairsOfFrequency(freq) { stepX, stepY := m.getStep(pair) filter := func(p *Point) bool { return !p.IsBetween(pair.antenna1, pair.antenna2) } m.generateInlinePoints(pair.antenna1, stepX, stepY, filter, ch) m.generateInlinePoints(pair.antenna1, -stepX, -stepY, filter, ch) } close(ch) }() return ch } func dedupPoints(points <-chan *Point) <-chan *Point { ch := make(chan *Point) go func() { m := make(map[int]map[int]bool) for p := range points { if _, ok := m[p.x]; ok { if _, ok2 := m[p.x][p.y]; ok2 { continue } m[p.x][p.y] = true } else { m[p.x] = make(map[int]bool) m[p.x][p.y] = true } ch <- p } close(ch) }() return ch } func (m *Map) getAllEquidistantAntinodeLocations() <-chan *Point { ch := make(chan *Point) go func() { for freq := range m.getAllAntennaFrequencies() { for p := range m.getEquidistantAntinodeLocationsForFreq(freq) { ch <- p } } close(ch) }() return dedupPoints(ch) } func (m *Map) getAllCollinearAntinodeLocations() <-chan *Point { ch := make(chan *Point) go func() { for freq := range m.getAllAntennaFrequencies() { for p := range m.getCollinearAntinodeLocationsFreq(freq) { ch <- p } } close(ch) }() return dedupPoints(ch) } func question8A(input string) int { i := 0 m := NewMap(input) for _ = range m.getAllEquidistantAntinodeLocations() { i++ } return i } func question8B(input string) int { i := 0 m := NewMap(input) for _ = range m.getAllCollinearAntinodeLocations() { //fmt.Println(">> ", p) i++ } return i } func main() { fmt.Printf("The answer to question 8A is %d \n", question8A(realInput)) fmt.Printf("The answer to question 8B is %d \n", question8B(mockInput)) }