300 lines
6.0 KiB
Go
300 lines
6.0 KiB
Go
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))
|
|
|
|
}
|