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 | ||
|  | } |