Commit e176c53

Richard Luby <richluby@gmail.com>
2016-11-02 12:29:41
server stores categorized questions
server now stores questions by category.
1 parent 8df93a2
question.go
@@ -9,45 +9,61 @@ import (
 	"strings"
 )
 
+// assignCategory figures out what category
+// this question is a member of
+func assignCategory(category string, record *Record) {
+	if _, inMap := recordMap[category]; inMap != true {
+		recordMap[category] = []Record{}
+		categoryKeys = append(categoryKeys, category)
+		record.Category = len(categoryKeys) - 1
+	} else {
+		for i, value := range categoryKeys {
+			if value == category {
+				record.Category = i
+			}
+		}
+	}
+}
+
 // parseLine parses a csv line and returns a single record
-func parseLine(line string) (Record, error) {
+func parseLine(line string) error {
 	var record Record
 	if strings.HasPrefix(line, "#") {
-		return record, nil
+		return nil
 	} else if strings.Compare(strings.TrimSpace(line), "") == 0 {
-		return record, nil
+		return nil
 	}
 	tokens := strings.Split(line, ",")
 	if len(tokens) != 3 {
-		return record, fmt.Errorf("Unable to parse line for %s", line)
+		return fmt.Errorf("Unable to parse line for %s", line)
 	}
 	record.Question = strings.TrimSpace(tokens[0])
 	record.Answer = strings.TrimSpace(tokens[1])
-	record.Category = strings.TrimSpace(tokens[2])
-	return record, nil
+	category := strings.TrimSpace(strings.ToLower(tokens[1]))
+	assignCategory(category, &record)
+	recordMap[category] = append(recordMap[category], record)
+	return nil
 }
 
 // loadFile reads the lines of the Answer directory and returns an array of Records
 // The questions are expected in the form of a csv in the order
 // question, Category, category
 // Lines starting with '#' are ignored
-func LoadFile(file *os.File) ([]Record, error) {
+func LoadFile(file *os.File) error {
 	log.Printf("Loading file: %+v", file.Name())
 	var records []Record
 	scanner := bufio.NewScanner(file)
 	for scanner.Scan() {
-		if record, err := parseLine(scanner.Text()); err != nil {
+		if err := parseLine(scanner.Text()); err != nil {
 			log.Printf("Error while parsing line: %+v. \nContinuing execution", err)
-		} else if record.Question != "" {
-			records = append(records, record)
 		}
 	}
 	log.Printf("Loaded %d questions from %s.", len(records), file.Name())
-	return records, nil
+	return nil
 }
 
 // loadRecords loads the records into memory according to
-// the supplied configuration
+// the supplied configuration for the given file.
 func loadRecords(path string, fileInfo os.FileInfo, err error) error {
 	file, err := os.Open(path)
 	if err != nil {
@@ -58,13 +74,11 @@ func loadRecords(path string, fileInfo os.FileInfo, err error) error {
 	if err != nil {
 		return err
 	}
-	var loadedRecords []Record
 	if fileStats.Mode().IsRegular() {
-		loadedRecords, err = LoadFile(file)
+		err = LoadFile(file)
 		if err != nil {
 			return err
 		}
-		records = append(records, loadedRecords...)
 	}
 	return nil
 }
server.go
@@ -42,11 +42,9 @@ QUESTIONS = "/path/to/questions"
 USER_TESTS = "/path/to/tests"
 `
 
+// the running configuration for the server
 var serverConfig SERVER_CONFIG
 
-// Contains the questions from which to pull
-var records []Record
-
 //loadServerConfiguration loads the configuration file
 // cfgFile : the file that contains the configuration
 func loadServerConfiguration(cfgFile string) SERVER_CONFIG {
@@ -95,7 +93,11 @@ func ExecuteServer() {
 		log.Printf("Failed to load questions due to error: %+v", err)
 		os.Exit(EXIT_CODE.FILE_IO_ERROR)
 	}
-	log.Printf("Loaded %d questions from %s.", len(records), serverConfig.QUESTIONS)
+	numQuestions := 0
+	for _, recordArray := range recordMap {
+		numQuestions += len(recordArray)
+	}
+	log.Printf("Loaded %d questions from %s with %d categories.", numQuestions, serverConfig.QUESTIONS, len(categoryKeys))
 	rand.Seed(time.Now().Unix())
 	Listen(serverConfig)
 }
serverHandlers.go
@@ -12,15 +12,25 @@ import (
 	"strconv"
 )
 
+// buildRecordArray builds an array of Records based on the parameter criteria
+func buildRecordArray(numQuestions int) []Record {
+	var keyIndex, valueIndex int
+	var recordArray = []Record{}
+	for i := 0; i < numQuestions; i++ {
+		keyIndex = rand.Intn(len(categoryKeys))
+		valueIndex = rand.Intn(len(recordMap[categoryKeys[keyIndex]]))
+		recordArray = append(recordArray, recordMap[categoryKeys[keyIndex]][valueIndex])
+	}
+	return recordArray
+}
+
 // handleRequestForTest provides a JSON formatted test for the client
 func handleRequestForTest(writer http.ResponseWriter, request *http.Request) error {
 	var giveRecords []Record
 	var err error
 	questions := request.FormValue("questions")
 	numQuestions, err := strconv.Atoi(questions)
-	for i := 0; i < numQuestions; i += 1 {
-		giveRecords = append(giveRecords, records[rand.Intn(len(records))])
-	}
+	giveRecords = buildRecordArray(numQuestions)
 	data, err := json.Marshal(giveRecords)
 	if err != nil {
 		return fmt.Errorf("Error building questions: %+v", err)
@@ -129,5 +139,4 @@ func init() {
 		API_ROOT + "/test": ServerHandler{
 			Request:        API_ROOT + "/test",
 			HandleFunction: handleTestQueries}}
-
 }
structures.go
@@ -3,6 +3,18 @@ package main
 // API_ROOT defines the root path for the web api interface
 const API_ROOT = "/api"
 
+// recordMap contains all records grouped into
+// categorical buckets. the category serves as
+// the keyname. the map is populated through the
+// loadRecords function call.
+var recordMap map[string][]Record
+
+// categoryKeys contains the list of categories
+// available from loading the questions.
+// is used for randomly selecting questions based
+// on category
+var categoryKeys []string
+
 // EXIT_CODES define exit error codes
 type EXIT_CODES struct {
 	BAD_CONFIG,
@@ -19,8 +31,8 @@ var EXIT_CODE = EXIT_CODES{
 // Record stores information related to a single record
 type Record struct {
 	Question,
-	Answer,
-	Category string
+	Answer string
+	Category,
 	ID int
 }
 
@@ -37,3 +49,7 @@ type ClientTest struct {
 	Score    float32
 	Username string
 }
+
+func init() {
+	recordMap = map[string][]Record{}
+}