package main import ( "bufio" "fmt" "io" "net" "net/http" "net/url" "regexp" "os" "os/signal" "sort" "strings" "sync" ) type WordAndCount struct { word string count uint } func newWordsAndCount(w *map[string]uint) []WordAndCount { ret := make([]WordAndCount, len(*w)) i := 0 for word, count := range *w { ret[i] = WordAndCount{word,count} i++ } return ret } func (wac WordAndCount) lessThan(other WordAndCount) bool { return wac.count < other.count } type Model struct { lock sync.RWMutex words map[string]uint } func NewModel() Model { return Model{words: make(map[string]uint)} } func (m *Model) AddWords(words []string) { m.lock.Lock() defer m.lock.Unlock() for _, word := range words { v, _ := m.words[word] m.words[word] = v + 1 } } func (m *Model) GetMostCommonPairs(how_many uint) []WordAndCount { m.lock.RLock() defer m.lock.RUnlock() wac := newWordsAndCount(&m.words) sort.SliceStable(wac, func(i,j int) bool { return ! wac[i].lessThan(wac[j]) }) if how_many >= uint(len(wac)) { how_many = uint(len(wac)) } return wac[:how_many] } type View struct{} func NewView() View { return View{} } func (v View) WordsFromInput(input string) []string { return strings.Fields(input) } func (v View) TableFromRankedPairs(ranked_pairs []WordAndCount) string { // we're not actually getting pairs, just strings var b strings.Builder for _, p := range ranked_pairs { fmt.Fprintf(&b, "%-15s %3d\n", p.word,p.count) } return b.String() } func NewHttpController(m Model, v View) *http.ServeMux { controller := http.NewServeMux() controller.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { if req.Method == "GET" { uri, _ := url.Parse(req.RequestURI) q := uri.Query() n, _ := q["n"] var how_many uint if len(n) > 0 { fmt.Sscanf(n[0], "%d", &how_many) } if how_many == 0 { how_many = 10 } most_common_pairs := m.GetMostCommonPairs(how_many) table := v.TableFromRankedPairs(most_common_pairs) strings.NewReader(table + "\n").WriteTo(w) } else if req.Method == "POST" { var body strings.Builder io.Copy(&body, req.Body) words := v.WordsFromInput(body.String()) m.AddWords(words) } }) return controller } type LineController struct {m Model; v View} func NewLineController(m Model, v View) LineController { return LineController{m,v} } func (l *LineController) OnCommand(w io.Writer, command string, args string) { if command == "get" { l.getWords(w,args) } else if command == "put" { l.putWords(w,args) } else { l.reply(w,"bad command, only 'get' and 'put'") } } func (l *LineController) reply(w io.Writer, text string) { strings.NewReader(text + "\n").WriteTo(w) } func (l *LineController) getWords(w io.Writer, args string) { var how_many uint fmt.Sscanf(args, "%d", &how_many) if how_many == 0 { how_many = 10 } most_common_pairs := l.m.GetMostCommonPairs(how_many) table := l.v.TableFromRankedPairs(most_common_pairs) l.reply(w,table) } func (l *LineController) putWords(w io.Writer, args string) { words := l.v.WordsFromInput(args) l.m.AddWords(words) l.reply(w,"ok") } type TextServer struct { c LineController l net.Listener } func TextListenAndServe(host string, controller LineController) error { l, err := net.Listen("tcp",host) if err != nil { return err } ts := TextServer{controller,l} ts.Serve() return nil } func (ts *TextServer) Serve() { for conn,err := ts.l.Accept(); err == nil; conn,err = ts.l.Accept() { go ts.serveOne(conn) } } func (ts *TextServer) serveOne(conn net.Conn) { split := regexp.MustCompile(`\s+`) s := bufio.NewScanner(conn) for s.Scan() { parts := split.Split(s.Text(),2) if len(parts)>1 { ts.c.OnCommand(conn,parts[0],parts[1]) } else if len(parts)==1 { ts.c.OnCommand(conn,parts[0],"") } } conn.Close() return } func main() { model := NewModel() view := NewView() httpcontroller := NewHttpController(model, view) go http.ListenAndServe("localhost:8080", httpcontroller) linecontroller := NewLineController(model,view) go TextListenAndServe("localhost:2020",linecontroller) sigs := make(chan os.Signal, 1) signal.Notify(sigs, os.Interrupt) <-sigs }