package aoc import ( "errors" "fmt" "math" ) type CoordByteMap struct { Field map[Coordinate]byte // The Top-Left-most X/Y TLX, TLY int // The Bottom-Right-most X/Y BRX, BRY int // Options for the 'String' method StringEmptyIsSpace bool StringEmptyByte byte // If 'Repeats' is true, then any coordinate request will be // modded by BRX/BRY Repeats bool } func NewCoordByteMap() CoordByteMap { return CoordByteMap{ Field: make(map[Coordinate]byte), TLX: math.MaxInt, TLY: math.MaxInt, BRX: math.MinInt, BRY: math.MinInt, StringEmptyByte: ' ', } } func (m *CoordByteMap) Copy() *CoordByteMap { c := CoordByteMap{ Field: make(map[Coordinate]byte), TLX: m.TLX, TLY: m.TLY, BRX: m.BRX, BRY: m.BRY, StringEmptyByte: m.StringEmptyByte, } for i := range m.Field { c.Field[i] = m.Field[i] } return &c } func StringSliceToCoordByteMap(input []string) CoordByteMap { ret := CoordByteMap{ Field: make(map[Coordinate]byte), } for y := range input { for x := range input[y] { ret.Put(Coordinate{X: x, Y: y}, input[y][x]) } } return ret } func (m *CoordByteMap) RowCount() int { return m.BRY - m.TLY } func (m *CoordByteMap) ColCount() int { return m.BRX - m.TLX } func (m *CoordByteMap) GetRow(y int) []byte { if m.Repeats { y = (y + m.BRY) % m.BRY } var resp []byte for x := m.TLX; x <= m.BRX; x++ { resp = append(resp, m.Get(Coordinate{X: x, Y: y})) } return resp } func (m *CoordByteMap) GetCol(x int) []byte { if m.Repeats { x = (x + m.BRX) % m.BRX } var resp []byte for y := m.TLY; y <= m.BRY; y++ { resp = append(resp, m.Get(Coordinate{X: x, Y: y})) } return resp } func (m *CoordByteMap) AddRow(row []byte) { y := m.BRY + 1 for x := 0; x < len(row); x++ { m.Put(Coordinate{X: x + m.TLX, Y: y}, row[x]) } } func (m *CoordByteMap) AddCol(col []byte) { x := m.BRX + 1 for y := 0; y < len(col); y++ { m.Put(Coordinate{X: x, Y: y + m.TLY}, col[y]) } } func (m *CoordByteMap) GetPos(x, y int) byte { if m.Repeats { x = (x + m.BRX) % m.BRX y = (y + m.BRY) % m.BRY } return m.Get(Coordinate{X: x, Y: y}) } func (m *CoordByteMap) Get(pos Coordinate) byte { if m.Repeats { pos.X = (pos.X + m.BRX) % m.BRX pos.Y = (pos.Y + m.BRY) % m.BRY } if v, ok := m.Field[pos]; ok { return v } return 0 } func (m *CoordByteMap) Opt(pos Coordinate, def byte) byte { if m.Repeats { pos.X = (pos.X + m.BRX) % m.BRX pos.Y = (pos.Y + m.BRY) % m.BRY } if v, ok := m.Field[pos]; ok { return v } return def } func (m *CoordByteMap) Put(pos Coordinate, val byte) { if pos.X < m.TLX { m.TLX = pos.X } if pos.Y < m.TLY { m.TLY = pos.Y } if pos.X > m.BRX { m.BRX = pos.X } if pos.Y > m.BRY { m.BRY = pos.Y } m.Field[pos] = val } func (m *CoordByteMap) Delete(pos Coordinate) { if m.Repeats { pos.X = (pos.X + m.BRX) % m.BRX pos.Y = (pos.Y + m.BRY) % m.BRY } delete(m.Field, pos) } func (m *CoordByteMap) Height() int { return m.BRY - m.TLY } func (m *CoordByteMap) Width() int { return m.BRX - m.TLX } func (m *CoordByteMap) ReplaceAll(o, n byte) { for y := m.TLY; y <= m.BRY; y++ { for x := m.TLX; x <= m.BRX; x++ { c := Coordinate{X: x, Y: y} if m.Get(c) == o { m.Put(c, n) } } } } func (m *CoordByteMap) ToByteSlices() [][]byte { var ret [][]byte for y := m.TLY; y <= m.BRY; y++ { var line []byte for x := m.TLX; x <= m.BRX; x++ { line = append(line, m.Get(Coordinate{X: x, Y: y})) } ret = append(ret, line) } return ret } // ContainsCoord returns true if the passed coordinate is within the bounds // of the map func (m *CoordByteMap) ContainsCoord(c Coordinate) bool { if m.Repeats { c.X = (c.X + m.BRX) % m.BRX c.Y = (c.Y + m.BRY) % m.BRY } return c.X >= m.TLX && c.X <= m.BRX && c.Y >= m.TLY && c.Y <= m.BRY } // RowContains searches a row for a byte func (m *CoordByteMap) RowContains(row int, b byte) bool { for x := m.TLX; x <= m.BRX; x++ { if m.Get(Coordinate{X: x, Y: row}) == b { return true } } return false } // ColContains searches a column for a byte func (m *CoordByteMap) ColContains(col int, b byte) bool { for y := m.TLY; y <= m.BRY; y++ { if m.Get(Coordinate{X: col, Y: y}) == b { return true } } return false } // InsertRowAfter grows the map south, moves all rows after 'row' south // Then fills the 'row'th row with 'fill' func (m *CoordByteMap) InsertRowAfter(row int, fill byte) { m.GrowSouth(1, fill) for y := m.BRY; y > row; y-- { for x := m.TLX; x <= m.BRX; x++ { m.Put(Coordinate{X: x, Y: y}, m.Get(Coordinate{X: x, Y: y - 1})) } } for x := m.TLX; x <= m.BRX; x++ { m.Put(Coordinate{X: x, Y: row}, fill) } } // InsertColAfter grows the map East, moves all cols after 'col' east // Then fills the 'col'th column with 'fill' func (m *CoordByteMap) InsertColAfter(col int, fill byte) { m.GrowEast(1, fill) for x := m.BRX; x > col; x-- { for y := m.TLY; y <= m.BRY; y++ { m.Put(Coordinate{X: x, Y: y}, m.Get(Coordinate{X: x - 1, Y: y})) } } for y := m.TLY; y <= m.BRY; y++ { m.Put(Coordinate{X: col, Y: y}, fill) } } // FindFirst searches left to right, top to bottom for the first // instance of b. func (m *CoordByteMap) FindFirst(b byte) (Coordinate, error) { for y := m.TLY; y <= m.BRY; y++ { for x := m.TLX; x <= m.BRX; x++ { c := Coordinate{X: x, Y: y} if m.Get(c) == b { return c, nil } } } return Coordinate{}, errors.New("not found") } // FindLast searches right to left, bottom to top for the last // instance of b func (m *CoordByteMap) FindLast(b byte) (Coordinate, error) { for y := m.BRY; y >= m.TLY; y-- { for x := m.BRX; x >= m.TLX; x-- { c := Coordinate{X: x, Y: y} if m.Get(c) == b { return c, nil } } } return Coordinate{}, errors.New("not found") } func (m *CoordByteMap) FindAll(b ...byte) []Coordinate { var ret []Coordinate for y := m.TLY; y <= m.BRY; y++ { for x := m.TLX; x <= m.BRX; x++ { c := Coordinate{X: x, Y: y} for i := range b { if m.Get(c) == b[i] { ret = append(ret, c) } } } } return ret } func (m *CoordByteMap) FindAllNot(b ...byte) map[Coordinate]byte { ret := make(map[Coordinate]byte) for y := m.TLY; y <= m.BRY; y++ { for x := m.TLX; x <= m.BRX; x++ { c := Coordinate{X: x, Y: y} v := m.Get(c) var is bool for i := range b { if v == b[i] { is = true break } } if !is { ret[c] = v } } } return ret } // FindAdjacentOrth takes a coordinate and a desired byte, and returns // all adjacent coordinates that contain that byte func (m *CoordByteMap) FindAdjacent(st Coordinate, val byte) []Coordinate { return m.AnyContain([]Coordinate{st.North(), st.NE(), st.East(), st.SE(), st.South(), st.SW(), st.West(), st.NW()}, val) } // FindAdjacentOrth takes a coordinate and a desired byte, and returns // all adjacent coordinates that contain that byte, Orthogonally func (m *CoordByteMap) FindAdjacentOrth(st Coordinate, val byte) []Coordinate { return m.AnyContain([]Coordinate{st.North(), st.East(), st.South(), st.West()}, val) } func (m *CoordByteMap) AnyContain(check []Coordinate, val byte) []Coordinate { var ret []Coordinate for _, wrk := range check { if m.Get(wrk) == val { ret = append(ret, wrk) } } return ret } // FindAnyAdjacentOrth takes a coordinate and a list of desired bytes, and returns // all adjacent coordinates that contain those bytes func (m *CoordByteMap) FindAnyAdjacent(st Coordinate, val []byte) []Coordinate { return m.AnyContainAny([]Coordinate{st.North(), st.NE(), st.East(), st.SE(), st.South(), st.SW(), st.West(), st.NW()}, val) } // FindAnyAdjacentOrth takes a coordinate and a desired byte, and returns // all adjacent coordinates that contain that byte, Orthogonally func (m *CoordByteMap) FindAnyAdjacentOrth(st Coordinate, val []byte) []Coordinate { return m.AnyContainAny([]Coordinate{st.North(), st.East(), st.South(), st.West()}, val) } func (m *CoordByteMap) AnyContainAny(check []Coordinate, val []byte) []Coordinate { var ret []Coordinate for _, wrk := range check { for _, bt := range val { if m.Get(wrk) == bt { ret = append(ret, wrk) } } } return ret } func (m *CoordByteMap) Count(b byte) int { var ret int for y := m.TLY; y <= m.BRY; y++ { for x := m.TLX; x <= m.BRX; x++ { if m.Get(Coordinate{X: x, Y: y}) == b { ret++ } } } return ret } func (m *CoordByteMap) GrowNorth(size int, val byte) { tlY := m.TLY - 1 for x := m.TLX; x <= m.BRX; x++ { m.Put(Coordinate{X: x, Y: tlY}, val) } } func (m *CoordByteMap) GrowEast(size int, val byte) { brX := m.BRX + 1 for y := m.TLY; y <= m.BRY; y++ { m.Put(Coordinate{X: brX, Y: y}, val) } } func (m *CoordByteMap) GrowSouth(size int, val byte) { tlY := m.BRY + 1 for x := m.TLX; x <= m.BRX; x++ { m.Put(Coordinate{X: x, Y: tlY}, val) } } func (m *CoordByteMap) GrowWest(size int, val byte) { brX := m.TLX - 1 for y := m.TLY; y <= m.BRY; y++ { m.Put(Coordinate{X: brX, Y: y}, val) } } // This grows the map in all directions func (m *CoordByteMap) Grow(size int, val byte) { m.GrowNorth(size, val) m.GrowEast(size, val) m.GrowSouth(size, val) m.GrowWest(size, val) } // Runs the passed function on every point in the map // returning a new bytemap func (m CoordByteMap) Map(l func(c Coordinate, in byte) byte) CoordByteMap { i := m for y := m.TLY; y <= m.BRY; y++ { for x := m.TLX; x <= m.BRX; x++ { c := Coordinate{X: x, Y: y} nV := l(c, m.Get(c)) fmt.Println("MAP: Putting", string(nV), "at", c) i.Put(c, l(c, m.Get(c))) } } return i } func (m CoordByteMap) RowAsString(y int) string { var res string for x := m.TLX; x <= m.BRX; x++ { res = fmt.Sprintf("%s%s", res, string(m.Get(Coordinate{X: x, Y: y}))) } return res } func (m CoordByteMap) ColAsString(x int) string { var res string for y := m.TLY; y <= m.BRY; y++ { res = fmt.Sprintf("%s%s", res, string(m.Get(Coordinate{X: x, Y: y}))) } return res } func (m CoordByteMap) String() string { var ret string for y := m.TLY; y <= m.BRY; y++ { for x := m.TLX; x <= m.BRX; x++ { if m.StringEmptyIsSpace { ret = ret + string(m.Opt(Coordinate{X: x, Y: y}, m.StringEmptyByte)) } else { ret = ret + string(m.Field[Coordinate{X: x, Y: y}]) } } ret = ret + "\n" } return ret } // And the 3d Version type Coord3dByteMap struct { Field map[Coordinate3d]byte MinX, MaxX int MinY, MaxY int MinZ, MaxZ int } func StringSliceToCoord3dByteMap(input []string) Coord3dByteMap { ret := Coord3dByteMap{ Field: make(map[Coordinate3d]byte), } for y := range input { for x := range input[y] { ret.Put(Coordinate3d{X: x, Y: y, Z: 0}, input[y][x]) } } return ret } func (m *Coord3dByteMap) Copy() Coord3dByteMap { ret := Coord3dByteMap{ Field: make(map[Coordinate3d]byte), MinX: m.MinX, MaxX: m.MaxX, MinY: m.MinY, MaxY: m.MaxY, MinZ: m.MinZ, MaxZ: m.MaxZ, } for z := m.MinZ; z <= m.MaxZ; z++ { for y := m.MinY; y <= m.MaxY; y++ { for x := m.MinX; x <= m.MaxX; x++ { c := Coordinate3d{X: x, Y: y, Z: z} ret.Field[c] = m.Get(c) } } } return ret } func (m *Coord3dByteMap) Get(pos Coordinate3d) byte { if _, ok := m.Field[pos]; !ok { return 0 } return m.Field[pos] } func (m *Coord3dByteMap) Opt(pos Coordinate3d, def byte) byte { if v, ok := m.Field[pos]; ok { return v } return def } func (m *Coord3dByteMap) Put(pos Coordinate3d, val byte) { m.Field[pos] = val if pos.X < m.MinX { m.MinX = pos.X } if pos.X > m.MaxX { m.MaxX = pos.X } if pos.Y < m.MinY { m.MinY = pos.X } if pos.Y > m.MaxY { m.MaxY = pos.Y } if pos.Z < m.MinZ { m.MinZ = pos.Z } if pos.Z > m.MaxZ { m.MaxZ = pos.Z } } func (m *Coord3dByteMap) CountNeighbors(pos Coordinate3d, val byte) int { var res int for z := pos.Z - 1; z <= pos.Z+1; z++ { for y := pos.Y - 1; y <= pos.Y+1; y++ { for x := pos.X - 1; x <= pos.X+1; x++ { if m.Get(Coordinate3d{X: x, Y: y, Z: z}) == val { res++ } } } } return res } func (m Coord3dByteMap) Count(val byte) int { var ret int for z := m.MinZ; z <= m.MaxZ; z++ { for y := m.MinY; y <= m.MaxY; y++ { for x := m.MinX; x <= m.MaxX; x++ { if m.Get(Coordinate3d{X: x, Y: y, Z: z}) == val { ret++ } } } } return ret } func (m Coord3dByteMap) String() string { var ret string for z := m.MinZ; z <= m.MaxZ; z++ { for y := m.MinY; y <= m.MaxY; y++ { for x := m.MinX; x <= m.MaxX; x++ { ret = ret + string(m.Field[Coordinate3d{X: x, Y: y, Z: z}]) } ret = ret + "\n" } ret = ret + "\n\n" } return ret }