Created
April 28, 2020 07:45
-
-
Save onokonem/4da201e47a6956a04ab63bfeaf80a783 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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