Index: src/0dev.org/commands/short/main.go ================================================================== --- src/0dev.org/commands/short/main.go +++ src/0dev.org/commands/short/main.go @@ -2,10 +2,11 @@ import ( iou "0dev.org/ioutil" "fmt" "io" + // "io/ioutil" "os" "sort" ) func main() { @@ -14,68 +15,113 @@ os.Stderr.WriteString("Unable to open input file. " + err.Error()) os.Exit(1) } pairs, symbols := analyze(f) + _, err = f.Seek(0, 0) + if err != nil { + os.Stderr.WriteString("Unable to reset input file position. " + err.Error()) + os.Exit(1) + } rec := recommend(pairs, symbols) - fmt.Println(*rec) + + reader, err := apply(rec, f) + if err != nil { + os.Stderr.WriteString("Error while constructing application reader. " + err.Error()) + os.Exit(1) + } + + // count, err := io.Copy(ioutil.Discard, reader) + // if err != nil { + // os.Stderr.WriteString("Error while applying recommendations. " + err.Error()) + // os.Exit(1) + // } + // fmt.Println(count) + + _, err = io.Copy(os.Stdout, reader) + if err != nil { + os.Stderr.WriteString("Error while applying recommendations. " + err.Error()) + os.Exit(1) + } } type recommendation struct { - p2s map[uint16]byte - s2p map[byte]uint16 + p2s map[uint16]byte + s2p map[byte]uint16 + unmapped byte } -func apply(rec *recommendation, reader io.Reader) { +// Returns an io.Reader that reads from the underlying one while applying the given recommendations +func apply(rec *recommendation, reader io.Reader) (io.Reader, error) { + // The symbol reader replaces symbols with pairs according to the s2p mapping symbolReader := iou.SizedReader(iou.ReaderFunc(func(output []byte) (int, error) { - var i int = 0 - for ; i < len(output)-1; i++ { + var ( + i int = 0 + err error + ) + for ; i < len(output)-1 && err == nil; i++ { // Read a byte from the underlying reader count, err := reader.Read(output[i : i+1]) // If we can't read anything else - return immediatelly if count == 0 { return i, err } // Convert the byte to a pair if there is a mapping for it - if pair, ok := rec.s2p[output[i]]; ok { - output[i] = byte(pair >> 8) // extract the high byte from the pair - i++ - output[i] = byte(pair) // leave only the low byte from the pair - } - - // Return on error - if err != nil { - return i + 1, err - } - } - return i + 1, nil - }), 2) - - pairReader := iou.ReaderFunc(func(output []byte) (int, error) { - for i := 0; i < len(output); i++ { - - } - }) + if p, ok := rec.s2p[output[i]]; ok { + output[i] = byte(p >> 8) // extract the high byte from the pair + i++ + output[i] = byte(p) // leave only the low byte from the pair + } + } + return i, nil + }), 2) + + currentByte, err := iou.ReadByte(symbolReader) + if err != nil { + return nil, err + } + + // The pair reader replaces pairs with symbols according to the p2s mapping + pairReader := iou.ReaderFunc(func(output []byte) (int, error) { + var ( + total int + err error + ) + start: + if len(output) <= 1 || err != nil { + return total, err + } + + nextByte, err := iou.ReadByte(symbolReader) + if s, ok := rec.p2s[(uint16(currentByte)<<8 | uint16(nextByte))]; ok { + output[0] = s + currentByte, err = iou.ReadByte(symbolReader) + } else { + output[0] = currentByte + currentByte = nextByte + } + + output, total = output[1:], total+1 + goto start + }) + + return pairReader, nil } func recommend(pairs pairSlice, symbols symbolSlice) *recommendation { - var ( - rec recommendation - pairsLength = len(pairs) - ) - + var rec recommendation rec.p2s = make(map[uint16]byte) // Store pair to symbol mappings rec.s2p = make(map[byte]uint16) // Store symbol to pair mappings - for i := 0; i < pairsLength; i++ { + for i, pairsLength := 0, len(pairs); i < pairsLength; i++ { currentPair := pairs[i] // Termination condition for when we are out of symbols - if len(symbols) == 0 { + if len(symbols) == 1 { break } gain := currentPair.count - 4 // 4 bytes for the default header currentSymbol := symbols[0] @@ -84,17 +130,15 @@ // Termination condition for possitive compression effect if gain <= 0 { break } - // Mark the recommendation and proceed with the next pair + // Mark the recommendation rec.p2s[currentPair.value] = currentSymbol.value - continue } else { // if the current symbol is present in the data // Decrease the gain by a symbol -> p`, p -> symbol replacement - gain -= 2 // Additional 2 bytes for the more complex header - gain -= currentSymbol.count // Account for swaping the symbol to a pair in order to free it + gain = gain - 2 - currentSymbol.count // 2 more bytes for header + replacement cost // Termination condition for possitive compression effect if gain <= 0 { break } @@ -104,12 +148,13 @@ pairsLength-- // Mark the current pair for replacement by the current symbol rec.p2s[currentPair.value] = currentSymbol.value } + symbols = symbols[1:] } - + rec.unmapped = symbols[len(symbols)-1].value return &rec } // Reads the provided input and returns information about the available byte pair and used symbols func analyze(reader io.Reader) (pairSlice, symbolSlice) {