110 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			110 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // domain represents a domain log record
 | |
| type domain struct {
 | |
| 	name   string
 | |
| 	visits int
 | |
| }
 | |
| 
 | |
| // parser parses a log file and provides an iterator to iterate upon the domains
 | |
| //
 | |
| // the parser struct is carefully crafted to be usable using its zero values except the map field
 | |
| type parser struct {
 | |
| 	sum     map[string]int // visits per unique domain
 | |
| 	domains []domain       // unique domain names
 | |
| 	total   int            // total visits to all domains
 | |
| 	lines   int            // number of parsed lines (for the error messages)
 | |
| 	err     error          // saves the last error occurred
 | |
| }
 | |
| 
 | |
| // newParser creates and returns a new parser.
 | |
| func newParser() *parser {
 | |
| 	return &parser{sum: make(map[string]int)}
 | |
| }
 | |
| 
 | |
| // add parses the given line and saves the result to the internal list of
 | |
| // domains. it doesn't add the record when the parsing fails.
 | |
| func add(p *parser, line string) {
 | |
| 	// if there was a previous error do not add
 | |
| 	if p.err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	dom, err := parse(p, line)
 | |
| 
 | |
| 	// store only the last error
 | |
| 	if err != nil {
 | |
| 		p.err = err
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	push(p, dom)
 | |
| }
 | |
| 
 | |
| // iterator returns two functions for iterating over domains.
 | |
| // next = returns true when there are more domains to iterate on.
 | |
| // cur  = returns the current domain
 | |
| func iterator(p *parser) (next func() bool, cur func() domain) {
 | |
| 	// remember the last received line
 | |
| 	var last int
 | |
| 
 | |
| 	next = func() bool {
 | |
| 		defer func() { last++ }()
 | |
| 		return len(p.domains) > last
 | |
| 	}
 | |
| 
 | |
| 	cur = func() domain {
 | |
| 		d := p.domains[last-1]
 | |
| 		vis := p.sum[d.name]
 | |
| 
 | |
| 		// return a copy so the caller cannot change it
 | |
| 		return domain{name: d.name, visits: vis}
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // error returns the last error occurred
 | |
| func err(p *parser) error {
 | |
| 	return p.err
 | |
| }
 | |
| 
 | |
| // parse parses the given text and returns a domain struct
 | |
| func parse(p *parser, line string) (dom domain, err error) {
 | |
| 	p.lines++ // increase the parsed line counter (only write is here)
 | |
| 
 | |
| 	fields := strings.Fields(line)
 | |
| 	if len(fields) != 2 {
 | |
| 		err = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	name, visits := fields[0], fields[1]
 | |
| 
 | |
| 	n, err := strconv.Atoi(visits)
 | |
| 	if n < 0 || err != nil {
 | |
| 		err = fmt.Errorf("wrong input: %q (line #%d)", visits, p.lines)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	return domain{name: name, visits: n}, nil
 | |
| }
 | |
| 
 | |
| // push pushes the given domain to the internal list of domains.
 | |
| // it also increases the total visits for all the domains.
 | |
| func push(p *parser, d domain) {
 | |
| 	// collect the unique domains
 | |
| 	if _, ok := p.sum[d.name]; !ok {
 | |
| 		p.domains = append(p.domains, d)
 | |
| 	}
 | |
| 
 | |
| 	p.sum[d.name] += d.visits
 | |
| 	p.total += d.visits
 | |
| }
 |