Commit 6692814

Richard Luby <richluby@gmail.com>
2016-11-18 07:50:00
server does not serve duplicate questions
server iterates through all questions before starting to serve them again. only one copy of a record stored in memory at a time. access and addition/ deletion occurs in O(1)
1 parent 1da98f6
question.go
@@ -13,7 +13,7 @@ import (
 // this question is a member of
 func assignCategory(category string, record *Record) {
 	if _, inMap := recordMap[category]; inMap != true {
-		recordMap[category] = []Record{}
+		recordMap[category] = map[int]Record{}
 		categoryKeys = append(categoryKeys, category)
 		record.Category = len(categoryKeys) - 1
 	} else {
@@ -41,7 +41,8 @@ func parseLine(line string) error {
 	record.Answer = strings.TrimSpace(tokens[1])
 	category := strings.TrimSpace(strings.ToLower(tokens[2]))
 	assignCategory(category, &record)
-	recordMap[category] = append(recordMap[category], record)
+	key := len(recordMap[category]) + 1
+	recordMap[category][key] = record
 	return nil
 }
 
serverHandlers.go
@@ -19,18 +19,40 @@ import (
 var userTests map[string][]ClientTest
 var userTestsLock = sync.RWMutex{}
 
+// getRecordForCategory returns a record for the specified category
+// it moves the now used record to the usedRecordMap, and updates
+// recordMap to reflect the change
+func getRecordForCategory(category string) Record {
+	mapLock.Lock()
+	defer mapLock.Unlock()
+	if len(recordMap[category]) == 0 {
+		recordMap[category] = usedRecordMap[category]
+		usedRecordMap[category] = map[int]Record{}
+	}
+	recordIndex := rand.Intn(len(recordMap[category]))
+	record := recordMap[category][recordIndex]
+	delete(recordMap[category], recordIndex)
+	if usedRecordMap[category] == nil {
+		usedRecordMap[category] = map[int]Record{}
+	}
+	usedRecordMap[category][len(usedRecordMap[category])] = record
+	return record
+}
+
 // buildRecordArray builds an array of Records based on the parameter criteria
 func buildRecordArray(numQuestions int, categories []int) []Record {
-	var keyIndex, valueIndex int
+	var keyIndex int
 	var recordArray = []Record{}
 	for i := 0; i < numQuestions; i++ {
 		if categories == nil {
 			keyIndex = rand.Intn(len(categoryKeys))
 		} else {
 			keyIndex = categories[rand.Intn(len(categories))]
+			if keyIndex >= len(categoryKeys) {
+				return nil
+			}
 		}
-		valueIndex = rand.Intn(len(recordMap[categoryKeys[keyIndex]]))
-		recordArray = append(recordArray, recordMap[categoryKeys[keyIndex]][valueIndex])
+		recordArray = append(recordArray, getRecordForCategory(categoryKeys[keyIndex]))
 	}
 	return recordArray
 }
structures.go
@@ -1,5 +1,7 @@
 package main
 
+import "sync"
+
 // API_ROOT defines the root path for the web api interface
 const API_ROOT = "/api"
 
@@ -7,7 +9,15 @@ const API_ROOT = "/api"
 // categorical buckets. the category serves as
 // the keyname. the map is populated through the
 // loadRecords function call.
-var recordMap map[string][]Record
+var recordMap map[string]map[int]Record
+
+// usedRecordMap contains the questions that have
+// already been used. when recordMap is empty,
+// usedRecordMap replaces it.
+var usedRecordMap map[string]map[int]Record
+
+// mapLock locks the maps to prevent concurrency issues
+var mapLock *sync.Mutex
 
 // categoryKeys contains the list of categories
 // available from loading the questions.
@@ -106,5 +116,7 @@ func (cSem *CountingSemaphore) SetCapacity(cap int) {
 }
 
 func init() {
-	recordMap = map[string][]Record{}
+	recordMap = map[string]map[int]Record{}
+	usedRecordMap = map[string]map[int]Record{}
+	mapLock = &sync.Mutex{}
 }