Skip to content

Instantly share code, notes, and snippets.

@onokonem
Created April 28, 2020 07:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save onokonem/4da201e47a6956a04ab63bfeaf80a783 to your computer and use it in GitHub Desktop.
Save onokonem/4da201e47a6956a04ab63bfeaf80a783 to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"flag"
"fmt"
"io"
"log"
"os"
"time"
)
var (
flagInputFilePath = flag.String("i", "./input.txt", "input file path")
flagOutputFilePath = flag.String("o", "./output.txt", "output file path")
)
func main() {
flag.Parse()
inFile, err := os.OpenFile(*flagInputFilePath, os.O_RDONLY, 0444)
if err != nil {
log.Fatalf("opening input file: %s", err)
}
outFile, err := os.OpenFile(
*flagOutputFilePath,
os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
0644,
)
if err != nil {
log.Fatalf("opening input file: %s", err)
}
inReader := bufio.NewReader(inFile)
outWriter := bufio.NewWriter(outFile)
start := time.Now()
if err := Transform(inReader, outWriter); err != nil {
log.Fatalf("transforming input: %s", err)
}
if err := outWriter.Flush(); err != nil {
log.Fatalf("flushing output writer: %s", err)
}
log.Printf(
"transformation complete (%s), output written to %s",
time.Since(start),
*flagOutputFilePath,
)
}
// Transform parses the table of values from the given reader
// and writes the values to the given output writer
// in ascending order.
func Transform(in io.Reader, out io.Writer) error {
vals := make([]uint, len(patterns))
scanner := NewScanner(in, nil)
// Read num of rows:
numRows, err := scanner.ScanUint()
if err != nil {
return fmt.Errorf("scanning number of rows: %w", err)
}
// if err := scanner.ScanLineBreak(); err != nil {
// return fmt.Errorf("scanning line-break after header: %w", err)
// }
for row := uint(0); row < numRows; row++ {
// Read num of columns
numCols, err := scanner.ScanUint()
if err != nil {
return fmt.Errorf(
"scanning number of columns for row %d: %w",
row, err,
)
}
for col := uint(0); col < numCols; col++ {
// Read value
val, err := scanner.ScanUint()
if err != nil {
return fmt.Errorf("scanning column %d: %w", col, err)
}
vals[val]++
}
err = scanner.SkipSpaces()
if err != nil {
_, off := scanner.Pos()
return fmt.Errorf(
"skiping spaces at offset %d after column %d on line %d: %v",
off, numCols-1, row, err,
)
}
if lastByte, off := scanner.Pos(); lastByte != '\n' {
return fmt.Errorf(
"expected line-break at offset %d after column %d on line %d, got %q",
off, numCols-1, row, lastByte,
)
}
}
nonZeroVals := 0
for _, n := range vals {
if n > 0 {
nonZeroVals++
}
}
// Write
z := 0
for i, n := range vals {
if n < 1 {
continue
}
z++
for j := uint(0); j < n; j++ {
if _, err := out.Write(patterns[i]); err != nil {
return fmt.Errorf("writing value %d-%d: %w", i, j, err)
}
if z == nonZeroVals && j+1 == n {
// Last element
continue
}
if _, err := out.Write(strSpace); err != nil {
return fmt.Errorf("writing space after %d-%d : %w", i, j, err)
}
}
}
return nil
}
// Scanner represents an io.Reader scanner
type Scanner struct {
reader io.Reader
buffer []byte
offsetNext uint
lastByte byte
}
// NewScanner creates a new scanner instance
// allocating a new buffer if none is provided
func NewScanner(reader io.Reader, buffer []byte) *Scanner {
if buffer == nil {
buffer = make([]byte, 1)
}
return &Scanner{
buffer: buffer,
reader: reader,
}
}
// next reads 1 byte, sets lastByte and increments the current offset
// uses the first index of the buffer
func (s *Scanner) next() error {
if _, err := s.reader.Read(s.buffer[:1]); err != nil {
if err == io.EOF {
return err
}
return fmt.Errorf("read error at offset %d: %w", s.offsetNext, err)
}
s.offsetNext++
s.lastByte = s.buffer[0]
return nil
}
// Pos returns the character and offset the scanner is currently at
func (s *Scanner) Pos() (byte, uint) {
offsetNext := s.offsetNext
if offsetNext > 0 {
offsetNext--
}
return s.lastByte, offsetNext
}
// ScanUint scans an unsigned integer ignoring any leading spaces and tabs
func (s *Scanner) ScanUint() (x uint, err error) {
inNum := false
for {
if err = s.next(); err != nil {
if inNum && err == io.EOF {
return x, nil
}
}
switch s.lastByte {
case ' ', '\t':
// Skip leading spaces and tabs
if !inNum {
continue
} else {
return
}
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
inNum = true
// Warning: This isn't safe from overflow!
// TODO: check for uint overflow
x = x*10 + uint(s.lastByte-'0')
case '\r', '\n':
if inNum {
return
}
fallthrough
default:
return 0, fmt.Errorf(
"unexpected character %q at offset %d",
string(s.lastByte), s.offsetNext-1,
)
}
}
}
// ScanUint scans an unsigned integer ignoring any leading spaces and tabs
func (s *Scanner) SkipSpaces() error {
for {
if err := s.next(); err != nil {
return err
}
switch s.lastByte {
case ' ', '\t':
default:
return nil
}
}
}
var (
strSpace = []byte(" ")
patterns = [][]byte{
[]byte("0"),
[]byte("1"),
[]byte("2"),
[]byte("3"),
[]byte("4"),
[]byte("5"),
[]byte("6"),
[]byte("7"),
[]byte("8"),
[]byte("9"),
[]byte("10"),
[]byte("11"),
[]byte("12"),
[]byte("13"),
[]byte("14"),
[]byte("15"),
[]byte("16"),
[]byte("17"),
[]byte("18"),
[]byte("19"),
[]byte("20"),
[]byte("21"),
[]byte("22"),
[]byte("23"),
[]byte("24"),
[]byte("25"),
[]byte("26"),
[]byte("27"),
[]byte("28"),
[]byte("29"),
[]byte("30"),
[]byte("31"),
[]byte("32"),
[]byte("33"),
[]byte("34"),
[]byte("35"),
[]byte("36"),
[]byte("37"),
[]byte("38"),
[]byte("39"),
[]byte("40"),
[]byte("41"),
[]byte("42"),
[]byte("43"),
[]byte("44"),
[]byte("45"),
[]byte("46"),
[]byte("47"),
[]byte("48"),
[]byte("49"),
[]byte("50"),
[]byte("51"),
[]byte("52"),
[]byte("53"),
[]byte("54"),
[]byte("55"),
[]byte("56"),
[]byte("57"),
[]byte("58"),
[]byte("59"),
[]byte("60"),
[]byte("61"),
[]byte("62"),
[]byte("63"),
[]byte("64"),
[]byte("65"),
[]byte("66"),
[]byte("67"),
[]byte("68"),
[]byte("69"),
[]byte("70"),
[]byte("71"),
[]byte("72"),
[]byte("73"),
[]byte("74"),
[]byte("75"),
[]byte("76"),
[]byte("77"),
[]byte("78"),
[]byte("79"),
[]byte("80"),
[]byte("81"),
[]byte("82"),
[]byte("83"),
[]byte("84"),
[]byte("85"),
[]byte("86"),
[]byte("87"),
[]byte("88"),
[]byte("89"),
[]byte("90"),
[]byte("91"),
[]byte("92"),
[]byte("93"),
[]byte("94"),
[]byte("95"),
[]byte("96"),
[]byte("97"),
[]byte("98"),
[]byte("99"),
[]byte("100"),
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment