464 lines
9.0 KiB
Go
464 lines
9.0 KiB
Go
package aoc
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Some handy Constants
|
|
const (
|
|
BORDER_NS = "\u2502"
|
|
BORDER_WE = "\u2500"
|
|
|
|
BORDER_NW = "\u250C"
|
|
BORDER_NE = "\u2510"
|
|
BORDER_SW = "\u2514"
|
|
BORDER_SE = "\u2518"
|
|
|
|
FILL_CHAR = "\u2588"
|
|
CLEAR_SCREEN = "\033[H\033[2J"
|
|
CLEAR_LINE = "\033[1K"
|
|
|
|
MAX_INT = int(^uint(0) >> 1)
|
|
MIN_INT = -MAX_INT - 1
|
|
|
|
SHRUG = "¯\\_(ツ)_/¯"
|
|
)
|
|
|
|
func CheckErr(err error) {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// Fact returns the factorial of the given int
|
|
func Fact(x int) int {
|
|
var ret int
|
|
for i := 0; i <= x; i++ {
|
|
ret += i
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Abs returns the absolute value of the given int
|
|
func Abs(x int) int {
|
|
if x < 0 {
|
|
return x * -1
|
|
}
|
|
return x
|
|
}
|
|
|
|
// Gcd Finds the greatest common denominator
|
|
func Gcd(x, y int) int {
|
|
for y != 0 {
|
|
x, y = y, x%y
|
|
}
|
|
return x
|
|
}
|
|
|
|
// Lcm finds the least common multiple, using gcd
|
|
func Lcm(a, b int, integers ...int) int {
|
|
result := a * b / Gcd(a, b)
|
|
for i := 0; i < len(integers); i++ {
|
|
result = Lcm(result, integers[i])
|
|
}
|
|
return result
|
|
}
|
|
|
|
// AbsInt returns the absolute value of i
|
|
func AbsInt(i int) int {
|
|
if i < 0 {
|
|
return i * -1
|
|
}
|
|
return i
|
|
}
|
|
|
|
// ArgIsSet return true if an argument with the asked for key is present
|
|
func ArgIsSet(a string) bool {
|
|
for i := range os.Args {
|
|
if os.Args[i] == a || strings.HasPrefix(os.Args[i], a+"=") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetArgValue returns the argument with the asked for key or ""
|
|
func GetArgValue(a string) string {
|
|
for i := range os.Args {
|
|
if strings.HasPrefix(os.Args[i], a+"=") {
|
|
return strings.TrimPrefix(os.Args[i], a+"=")
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetArgNumber returns the asked for argument position or ""
|
|
func GetArgNumber(i int) string {
|
|
if len(os.Args) > i {
|
|
return os.Args[i]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// OptArgNumber returns either the asked for argument position or the passed default
|
|
func OptArgNumber(i int, def string) string {
|
|
if len(os.Args) > i {
|
|
return os.Args[i]
|
|
}
|
|
return def
|
|
}
|
|
|
|
// StdinToString reads from stdin and returns a string
|
|
func StdinToString() string {
|
|
var input string
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
for scanner.Scan() {
|
|
input += scanner.Text()
|
|
}
|
|
return input
|
|
}
|
|
|
|
// StdinToIntSlice reads from stdin and returns it as an int slice
|
|
func StdinToIntSlice() []int {
|
|
var ret []int
|
|
st := StdinToStringSlice()
|
|
for _, v := range st {
|
|
ret = append(ret, Atoi(v))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// StdinToStringSlice reads from stdin and returns it as a string slice
|
|
func StdinToStringSlice() []string {
|
|
var input []string
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
for scanner.Scan() {
|
|
input = append(input, scanner.Text())
|
|
}
|
|
return input
|
|
}
|
|
|
|
// StdinToCoordMap reads stdin and returns it as a CoordByteMap
|
|
func StdinToCoordMap() CoordByteMap {
|
|
return StringSliceToCoordByteMap(StdinToStringSlice())
|
|
}
|
|
|
|
// Atof parses a float out of a string
|
|
func Atof(i string) float64 {
|
|
var ret float64
|
|
var err error
|
|
if ret, err = strconv.ParseFloat(i, 64); err != nil {
|
|
log.Fatalf("Invalid Atof: %s\n%v", i, err)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Atoi goes through i and removes and characters that aren't numeric (or -)
|
|
// Then runs it through the normal Atoi
|
|
func Atoi(i string) int {
|
|
var wrk string
|
|
for _, bt := range i {
|
|
if bt == '-' || (bt >= '0' && bt <= '9') {
|
|
wrk = fmt.Sprintf("%s%s", wrk, string(bt))
|
|
}
|
|
}
|
|
var ret int
|
|
var err error
|
|
if ret, err = strconv.Atoi(wrk); err != nil {
|
|
log.Fatalf("Invalid Atoi: %s\n%v", i, err)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Itoa is basically redundant
|
|
func Itoa(i int) string {
|
|
return strconv.Itoa(i)
|
|
}
|
|
|
|
// Atoui is the same as Atoi, but with unsigned ints
|
|
func Atoui(i string) uint64 {
|
|
var ret uint64
|
|
var err error
|
|
if ret, err = strconv.ParseUint(i, 10, 64); err != nil {
|
|
log.Fatalf("Invalid Atoi: %s\n%v", i, err)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// FileToStringSlice takes a file and returns it as a slice of strings
|
|
func FileToStringSlice(fn string) []string {
|
|
return strings.Split(string(FileToBytes(fn)), "\n")
|
|
}
|
|
|
|
// FileToString reads a file and returns it as a string
|
|
func FileToString(fn string) string {
|
|
return string(FileToBytes(fn))
|
|
}
|
|
|
|
// FileToBytes reads a file and returns it as a slice of bytes
|
|
func FileToBytes(fn string) []byte {
|
|
var c []byte
|
|
var err error
|
|
c, err = ioutil.ReadFile(fn)
|
|
if err != nil {
|
|
fmt.Println("Unable to read file: " + fn)
|
|
os.Exit(1)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// PrintProgress is for outputting a progress bar
|
|
func PrintProgress(curr, total int) {
|
|
pct := int(float64(curr) / float64(total) * 100)
|
|
fmt.Print("[")
|
|
for i := 0; i < 100; i += 10 {
|
|
if pct > i {
|
|
fmt.Print(FILL_CHAR)
|
|
} else {
|
|
fmt.Print(" ")
|
|
}
|
|
}
|
|
fmt.Print("]")
|
|
}
|
|
|
|
// StringPermutations takes a string and returns all permutations of it
|
|
func StringPermutations(str string) []string {
|
|
perms := stringPermHelper(str, 0)
|
|
var wrk []string
|
|
// Now de-dupe
|
|
for i := range perms {
|
|
var found bool
|
|
for j := range wrk {
|
|
if wrk[j] == perms[i] {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
wrk = append(wrk, perms[i])
|
|
}
|
|
}
|
|
return wrk
|
|
}
|
|
|
|
func stringPermHelper(str string, i int) []string {
|
|
ret := []string{str}
|
|
if i != len(str) {
|
|
r := []rune(str)
|
|
for j := i; j < len(r); j++ {
|
|
r[i], r[j] = r[j], r[i]
|
|
ret = append(ret, stringPermHelper(string(r), i+1)...)
|
|
r[i], r[j] = r[j], r[i]
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// IntPermutations takes a slice of ints and returns all permutations of it
|
|
func IntPermutations(inp []int) [][]int {
|
|
perms := intPermHelper(inp, 0)
|
|
var wrk [][]int
|
|
// Now de-dupe
|
|
for i := range perms {
|
|
var found bool
|
|
for j := range wrk {
|
|
if IntSlicesAreEqual(perms[i], wrk[j]) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
wrk = append(wrk, perms[i])
|
|
}
|
|
}
|
|
return wrk
|
|
}
|
|
|
|
func intPermHelper(inp []int, i int) [][]int {
|
|
ret := [][]int{inp}
|
|
if i != len(inp) {
|
|
r := make([]int, len(inp))
|
|
copy(r, inp)
|
|
for j := i; j < len(r); j++ {
|
|
r[i], r[j] = r[j], r[i]
|
|
ret = append(ret, intPermHelper(r, i+1)...)
|
|
r[i], r[j] = r[j], r[i]
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// IntSlicesAreEqual takes two int slices and returns if they are equal
|
|
func IntSlicesAreEqual(s1 []int, s2 []int) bool {
|
|
if len(s1) != len(s2) {
|
|
return false
|
|
}
|
|
for k := range s1 {
|
|
if s1[k] != s2[k] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func StringSliceIndex(h []string, n string) int {
|
|
for k, v := range h {
|
|
if v == n {
|
|
return k
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// StringSliceContains takes a string slice and a string and return true
|
|
// if the string is in the slice
|
|
func StringSliceContains(h []string, n string) bool {
|
|
for _, v := range h {
|
|
if v == n {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IntSliceContains takes an int slice and an int and return true
|
|
// if the int is in the slice
|
|
func IntSliceContains(h []int, n int) bool {
|
|
for _, v := range h {
|
|
if v == n {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// AppendString returns a slice of strings that are base+group[...]
|
|
func AppendStrings(base string, group []string) []string {
|
|
for k, v := range group {
|
|
group[k] = base + v
|
|
}
|
|
return group
|
|
}
|
|
|
|
// UnionStringSlice returns a slice that combines list1 and list2
|
|
func UnionStringSlice(list1, list2 []string) []string {
|
|
for k := range list1 {
|
|
if !StringSliceContains(list2, list1[k]) {
|
|
list2 = append(list2, list1[k])
|
|
}
|
|
}
|
|
return list2
|
|
}
|
|
|
|
// IntersectStringSlice returns a slice that contains all elements that
|
|
// both list1 and list2 contain
|
|
func IntersectStringSlice(list1, list2 []string) []string {
|
|
var ret []string
|
|
for k := range list1 {
|
|
if StringSliceContains(list2, list1[k]) {
|
|
ret = append(ret, list1[k])
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Unique removes all duplicates
|
|
func UniqueStringSlice(list []string) []string {
|
|
var ret []string
|
|
for k := 0; k < len(list); k++ {
|
|
var dupe bool
|
|
for j := k; j < len(list); j++ {
|
|
if list[k] == list[j] {
|
|
dupe = true
|
|
break
|
|
}
|
|
}
|
|
if !dupe {
|
|
ret = append(ret, list[k])
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// IsPrime takes a number and return true if that number is prime
|
|
func IsPrime(value int) bool {
|
|
for i := 2; i <= int(math.Floor(float64(value)/2)); i++ {
|
|
if value%i == 0 {
|
|
return false
|
|
}
|
|
}
|
|
return value > 1
|
|
}
|
|
|
|
func SliceMin(sl []int) int {
|
|
switch len(sl) {
|
|
case 0:
|
|
return MIN_INT
|
|
case 1:
|
|
return sl[0]
|
|
case 2:
|
|
return Min(sl[0], sl[1])
|
|
default:
|
|
return Min(sl[0], sl[1], sl[2:]...)
|
|
}
|
|
}
|
|
|
|
func Min(v1, v2 int, vrest ...int) int {
|
|
min := v2
|
|
if v1 < v2 {
|
|
min = v1
|
|
}
|
|
for i := range vrest {
|
|
if vrest[i] < min {
|
|
min = vrest[i]
|
|
}
|
|
}
|
|
return min
|
|
}
|
|
|
|
func Max(v1, v2 int, vrest ...int) int {
|
|
max := v2
|
|
if v1 > v2 {
|
|
max = v1
|
|
}
|
|
for i := range vrest {
|
|
if vrest[i] > max {
|
|
max = vrest[i]
|
|
}
|
|
}
|
|
return max
|
|
}
|
|
|
|
// Sum adds up all of the numbers from l to h
|
|
func Sum(l, h int) int {
|
|
var ret int
|
|
for i := Min(l, h); i <= Max(l, h); i++ {
|
|
ret = ret + i
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func ManhattanDistance(x1, y1, x2, y2 int) int {
|
|
return AbsInt(x1-x2) + AbsInt(y1-y2)
|
|
}
|
|
|
|
func PromptUser(text string, required bool) string {
|
|
var resp string
|
|
fmt.Print(text + ": ")
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
if scanner.Scan() {
|
|
resp = scanner.Text()
|
|
}
|
|
if resp == "" && required {
|
|
fmt.Println("Non-empty response is required")
|
|
return PromptUser(text, required)
|
|
}
|
|
return strings.TrimSpace(resp)
|
|
}
|