Решение на ExpireMap от Йордан Пулов

Обратно към всички решения

Към профила на Йордан Пулов

Резултати

  • 8 точки от тестове
  • 0 бонус точки
  • 8 точки общо
  • 21 успешни тест(а)
  • 4 неуспешни тест(а)

Код

package main
import (
"errors"
"reflect"
"strconv"
"strings"
"sync"
"time"
)
type DeleteSignal struct{}
type HashedValue struct {
value interface{}
expire time.Duration
timeExpires time.Time
ch chan DeleteSignal
}
// Create new HashedValue
func NewHashedValue(value interface{}, expire time.Duration) *HashedValue {
hv := new(HashedValue)
hv.value = value
hv.expire = expire
hv.ch = make(chan DeleteSignal)
hv.timeExpires = time.Now().Add(expire)
return hv
}
type ExpireMap struct {
hash map[string]*HashedValue
expiredKeys []string
ch chan string
chEKIn chan string
chEKOut chan string
mutex *sync.Mutex
}
// Create new ExpireMap
func NewExpireMap() *ExpireMap {
em := new(ExpireMap)
em.hash = make(map[string]*HashedValue)
em.ch = make(chan string)
em.chEKIn = make(chan string)
em.chEKOut = make(chan string)
go func() {
for {
if len(em.expiredKeys) > 0 {
select {
case em.chEKOut <- em.expiredKeys[0]:
em.expiredKeys = em.expiredKeys[1:]
case key := <-em.chEKIn:
em.expiredKeys = append(em.expiredKeys, key)
}
} else {
key := <-em.chEKIn
em.expiredKeys = append(em.expiredKeys, key)
}
}
}()
em.mutex = new(sync.Mutex)
return em
}
// Set a record in Expire map
func (em *ExpireMap) Set(key string, value interface{}, expire time.Duration) {
hv := NewHashedValue(value, expire)
em.hash[key] = hv
// run a goroute to delete the record after expiring
go func(key string, duration time.Duration, ch chan DeleteSignal) {
defer em.mutex.Unlock()
select {
case <-ch:
em.mutex.Lock()
delete(em.hash, key)
case <-time.After(duration):
em.mutex.Lock()
go func(expiredKey string) {
em.chEKIn <- expiredKey
}(key)
delete(em.hash, key)
}
}(key, expire, hv.ch)
}
// get a HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) Get(key string) (interface{}, bool) {
if hashed, ok := em.hash[key]; ok {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value, true
}
return nil, false
}
// get an integer HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) GetInt(key string) (int, bool) {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "int" {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value.(int), true
}
return 0, false
}
// get a float64 HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) GetFloat64(key string) (float64, bool) {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "float64" {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value.(float64), true
}
return 0.0, false
}
// get a string HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) GetString(key string) (string, bool) {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value.(string), true
}
return "", false
}
// get a string HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) GetBool(key string) (bool, bool) {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "bool" {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value.(bool), true
}
return false, false
}
// get the time when the HashedValue.value will expire
func (em *ExpireMap) Expires(key string) (time.Time, bool) {
if hashed, ok := em.hash[key]; ok {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.timeExpires, true
}
return time.Date(1, time.January, 01, 00, 0, 0, 0, time.UTC), false
}
// deletes the value in the ExpireMap with the given key
func (em *ExpireMap) Delete(key string) {
if hashed, ok := em.hash[key]; ok {
hashed.ch <- DeleteSignal{}
}
}
// checks in the key is in the hash
func (em *ExpireMap) Contains(key string) bool {
em.mutex.Lock()
defer em.mutex.Unlock()
_, ok := em.hash[key]
return ok
}
// size of the hash
func (em *ExpireMap) Size() int {
return len(em.hash)
}
// makes the string on position key in ExpireMap with CAPS
func (em *ExpireMap) ToUpper(key string) error {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
em.mutex.Lock()
defer func() {
em.mutex.Unlock()
}()
em.hash[key].value = strings.ToUpper(hashed.value.(string))
return nil
}
return errors.New("Not a string or key expired")
}
// makes the string on position key in ExpireMap without CAPS
func (em *ExpireMap) ToLower(key string) error {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
em.mutex.Lock()
defer func() {
em.mutex.Unlock()
}()
em.hash[key].value = strings.ToLower(hashed.value.(string))
return nil
}
return errors.New("Not a string or key expired")
}
// returns expired keys
func (em *ExpireMap) ExpiredChan() <-chan string {
return em.chEKOut
}
// cleans all records in the hash
func (em *ExpireMap) Cleanup() {
for key, value := range em.hash {
value.ch <- DeleteSignal{}
delete(em.hash, key) // don't ask :D => quick fix ,sometimes the key is not deleted (on time) by the DeleteSignal
}
}
// destructor of the ExpireMap
func (em *ExpireMap) Destroy() {
for _, value := range em.hash {
value.ch <- DeleteSignal{}
}
close(em.ch)
}
// increments values like 42 or "42" by 1
func (em *ExpireMap) Increment(key string) error {
if hashed, ok := em.hash[key]; ok {
em.mutex.Lock()
defer func() {
em.mutex.Unlock()
}()
switch hashed.value.(type) {
case int:
em.hash[key].value = hashed.value.(int) + 1
return nil
break
case string:
number, err := strconv.Atoi(hashed.value.(string))
if err != nil {
return err
}
number++
em.hash[key].value = strconv.Itoa(number)
return nil
break
default:
return errors.New("Cannot increment. Not a string or int")
}
}
return errors.New("Key not found")
}
// decrements values like 42 or "42" by 1
func (em *ExpireMap) Decrement(key string) error {
if hashed, ok := em.hash[key]; ok {
em.mutex.Lock()
defer func() {
em.mutex.Unlock()
}()
switch hashed.value.(type) {
case int:
em.hash[key].value = hashed.value.(int) - 1
return nil
break
case string:
number, err := strconv.Atoi(hashed.value.(string))
if err != nil {
return err
}
number--
em.hash[key].value = strconv.Itoa(number)
return nil
break
default:
errors.New("Cannot decrement. Not a string or int")
}
}
return errors.New("Key not found")
}

Лог от изпълнението

PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
panic: test timed out

goroutine 16 [running]:
testing.alarm()
	/usr/local/lib/go/src/pkg/testing/testing.go:533 +0x44
created by time.goFunc
	/usr/local/lib/go/src/pkg/time/sleep.go:122 +0x45

goroutine 1 [chan receive]:
testing.RunTests(0x81480b0, 0x81c2ba0, 0x19, 0x19, 0x1, ...)
	/usr/local/lib/go/src/pkg/testing/testing.go:434 +0x69f
testing.Main(0x81480b0, 0x81c2ba0, 0x19, 0x19, 0x81c5600, ...)
	/usr/local/lib/go/src/pkg/testing/testing.go:365 +0x69
main.main()
	_/tmp/d20141204-6466-1tcegjg/_test/_testmain.go:91 +0x81

goroutine 4 [sleep]:
time.Sleep(0x3c336080, 0x0)
	/usr/local/lib/go/src/pkg/runtime/ztime_linux_386.c:19 +0x3a
_/tmp/d20141204-6466-1tcegjg.TestSizes(0x1837a1e0)
	/tmp/d20141204-6466-1tcegjg/solution_test.go:111 +0x335
testing.tRunner(0x1837a1e0, 0x81c2bb8)
	/usr/local/lib/go/src/pkg/testing/testing.go:353 +0x87
created by testing.RunTests
	/usr/local/lib/go/src/pkg/testing/testing.go:433 +0x684

goroutine 5 [chan receive]:
_/tmp/d20141204-6466-1tcegjg.func·001()
	/tmp/d20141204-6466-1tcegjg/solution.go:58 +0x1c7
created by _/tmp/d20141204-6466-1tcegjg.NewExpireMap
	/tmp/d20141204-6466-1tcegjg/solution.go:62 +0x113

goroutine 6 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x18300320, 0x5, 0x3b9aca00, 0x0, 0x183486c0, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 7 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x18300348, 0x5, 0x3b9aca00, 0x0, 0x18348720, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 8 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x18300370, 0x5, 0x3b9aca00, 0x0, 0x18348780, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 9 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x18300398, 0x5, 0x3b9aca00, 0x0, 0x183487e0, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 10 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x183003c0, 0x5, 0x3b9aca00, 0x0, 0x18348840, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 11 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x183003e8, 0x5, 0x3b9aca00, 0x0, 0x183488a0, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 12 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x18300410, 0x5, 0x3b9aca00, 0x0, 0x18348900, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 13 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x18300438, 0x5, 0x3b9aca00, 0x0, 0x18348960, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 14 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x18300460, 0x5, 0x3b9aca00, 0x0, 0x183489c0, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0

goroutine 15 [select]:
_/tmp/d20141204-6466-1tcegjg.func·003(0x18300488, 0x5, 0x3b9aca00, 0x0, 0x18348a20, ...)
	/tmp/d20141204-6466-1tcegjg/solution.go:77 +0x174
created by _/tmp/d20141204-6466-1tcegjg.(*ExpireMap).Set
	/tmp/d20141204-6466-1tcegjg/solution.go:92 +0xe0
exit status 2
FAIL	_/tmp/d20141204-6466-1tcegjg	1.014s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.122s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.172s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.044s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.112s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.062s
--- FAIL: TestExpiredChanWhenNoOneIsReading-2 (0.06 seconds)
	solution_test.go:554: Wrong key expired
FAIL
exit status 1
FAIL	_/tmp/d20141204-6466-1tcegjg	0.073s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.112s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.132s
--- FAIL: TestConcurrentOperations-2 (0.12 seconds)
	solution_test.go:716: Expected something bigger than 1.5e6 but found 20001
FAIL
exit status 1
FAIL	_/tmp/d20141204-6466-1tcegjg	0.133s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
--- FAIL: TestDestroyMethodClosesExpireChannel-2 (0.05 seconds)
	solution_test.go:818: Expire channel was not closed in time
FAIL
exit status 1
FAIL	_/tmp/d20141204-6466-1tcegjg	0.062s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.272s
PASS
ok  	_/tmp/d20141204-6466-1tcegjg	0.012s

История (2 версии и 1 коментар)

Йордан обнови решението на 16.11.2014 18:35 (преди над 3 години)

+package main
+
+import (
+ "errors"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+type DeleteSignal struct{}
+
+type HashedValue struct {
+ value interface{}
+ expire time.Duration
+ timeExpires time.Time
+ ch chan DeleteSignal
+}
+
+// Create new HashedValue
+func NewHashedValue(value interface{}, expire time.Duration) *HashedValue {
+ hv := new(HashedValue)
+ hv.value = value
+ hv.expire = expire
+ hv.ch = make(chan DeleteSignal)
+ hv.timeExpires = time.Now().Add(expire)
+ return hv
+}
+
+type ExpireMap struct {
+ hash map[string]*HashedValue
+ expiredKeys []string
+ ch chan string
+ chEKIn chan string
+ chEKOut chan string
+ mutex *sync.Mutex
+}
+
+// Create new ExpireMap
+func NewExpireMap() *ExpireMap {
+ em := new(ExpireMap)
+ em.hash = make(map[string]*HashedValue)
+ em.ch = make(chan string)
+ em.chEKIn = make(chan string)
+ em.chEKOut = make(chan string)
+
+ go func() {
+ for {
+ if len(em.expiredKeys) > 0 {
+ select {
+ case em.chEKOut <- em.expiredKeys[0]:
+ em.expiredKeys = em.expiredKeys[1:]
+ case key := <-em.chEKIn:
+ em.expiredKeys = append(em.expiredKeys, key)
+ }
+ } else {
+ key := <-em.chEKIn
+ em.expiredKeys = append(em.expiredKeys, key)
+ }
+ }
+ }()
+ em.mutex = new(sync.Mutex)
+ return em
+}
+
+// Set a record in Expire map
+func (em *ExpireMap) Set(key string, value interface{}, expire time.Duration) {
+ hv := NewHashedValue(value, expire)
+ em.hash[key] = hv
+
+ // run a goroute to delete the record after expiring
+ go func(key string, duration time.Duration, ch chan DeleteSignal) {
+
+ defer em.mutex.Unlock()
+
+ select {
+ case <-ch:
+ em.mutex.Lock()
+ delete(em.hash, key)
+
+ case <-time.After(duration):
+ em.mutex.Lock()
+ go func(expiredKey string) {
+ em.chEKIn <- expiredKey
+ }(key)
+
+ delete(em.hash, key)
+
+ }
+
+ }(key, expire, hv.ch)
+}
+
+// get a HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) Get(key string) (interface{}, bool) {
+ if hashed, ok := em.hash[key]; ok {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.value, true
+ }
+ return nil, false
+}
+
+// get an integer HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) GetInt(key string) (int, bool) {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "int" {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.value.(int), true
+ }
+ return 0, false
+}
+
+// get a float64 HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) GetFloat64(key string) (float64, bool) {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "float64" {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.value.(float64), true
+ }
+ return 0.0, false
+}
+
+// get a string HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) GetString(key string) (string, bool) {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
+ em.mutex.Lock()
+ defer em.mutex.Unlock()
+
+ return hashed.value.(string), true
+ }
+ return "", false
+}
+
+// get a string HashedValue.value by a key from the ExpireMap
+func (em *ExpireMap) GetBool(key string) (bool, bool) {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "bool" {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.value.(bool), true
+ }
+ return false, false
+}
+
+// get the time when the HashedValue.value will expire
+func (em *ExpireMap) Expires(key string) (time.Time, bool) {
+ if hashed, ok := em.hash[key]; ok {
+
+ em.mutex.Lock()
+
+ defer em.mutex.Unlock()
+
+ return hashed.timeExpires, true
+ }
+ return time.Date(1, time.January, 01, 00, 0, 0, 0, time.UTC), false
+}
+
+// deletes the value in the ExpireMap with the given key
+func (em *ExpireMap) Delete(key string) {
+ if hashed, ok := em.hash[key]; ok {
+ hashed.ch <- DeleteSignal{}
+ }
+}
+
+// checks in the key is in the hash
+func (em *ExpireMap) Contains(key string) bool {
+ em.mutex.Lock()
+ defer em.mutex.Unlock()
+ _, ok := em.hash[key]
+
+ return ok
+}
+
+// size of the hash
+func (em *ExpireMap) Size() int {
+ return len(em.hash)
+}
+
+// makes the string on position key in ExpireMap with CAPS
+func (em *ExpireMap) ToUpper(key string) error {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
+
+ em.mutex.Lock()
+
+ defer func() {
+ em.mutex.Unlock()
+ }()
+
+ em.hash[key].value = strings.ToUpper(hashed.value.(string))
+ return nil
+ }
+ return errors.New("Not a string or key expired")
+}
+
+// makes the string on position key in ExpireMap without CAPS
+func (em *ExpireMap) ToLower(key string) error {
+ if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
+
+ em.mutex.Lock()
+
+ defer func() {
+ em.mutex.Unlock()
+ }()
+
+ em.hash[key].value = strings.ToLower(hashed.value.(string))
+ return nil
+ }
+ return errors.New("Not a string or key expired")
+}
+
+// returns expired keys
+func (em *ExpireMap) ExpiredChan() <-chan string {
+ return em.chEKOut
+}
+
+// cleans all records in the hash
+func (em *ExpireMap) Cleanup() {
+ for key, _ := range em.hash {
+ delete(em.hash, key)
+ }
+}
+
+// destructor of the ExpireMap
+func (em *ExpireMap) Destroy() {
+
+ for _, value := range em.hash {
+
+ value.ch <- DeleteSignal{}
+
+ }
+ close(em.ch)
+
+}
+
+// increments values like 42 or "42" by 1
+func (em *ExpireMap) Increment(key string) error {
+ if hashed, ok := em.hash[key]; ok {
+
+ em.mutex.Lock()
+
+ defer func() {
+ em.mutex.Unlock()
+ }()
+
+ switch reflect.ValueOf(hashed.value).Type().Name() {
+ case "int":
+ em.hash[key].value = hashed.value.(int) + 1
+ return nil
+ break
+ case "string":
+ number, err := strconv.Atoi(hashed.value.(string))
+ if err != nil {
+ return err
+ }
+ number++
+ em.hash[key].value = strconv.Itoa(number)
+ return nil
+ break
+ default:
+ return errors.New("Cannot increment. Not a string or int")
+ }
+ }
+ return errors.New("Key not found")
+}
+
+// decrements values like 42 or "42" by 1
+func (em *ExpireMap) Decrement(key string) error {
+ if hashed, ok := em.hash[key]; ok {
+
+ em.mutex.Lock()
+
+ defer func() {
+ em.mutex.Unlock()
+ }()
+
+ switch reflect.ValueOf(hashed.value).Type().Name() {
+ case "int":
+ em.hash[key].value = hashed.value.(int) - 1
+ return nil
+ break
+ case "string":
+ number, err := strconv.Atoi(hashed.value.(string))
+ if err != nil {
+ return err
+ }
+ number--
+ em.hash[key].value = strconv.Itoa(number)
+ return nil
+ break
+ default:
+ errors.New("Cannot decrement. Not a string or int")
+ }
+ }
+ return errors.New("Key not found")
+}

Така първо с добро - харесва ми.

Второ има такова нещо като type switch - reflect не ти трябва :)

И после :

Какво става ако кажа

em.Set("key", 2, time.Millisecond)
em.Set("key", 2, 1 * time.Second)

Cleanup-а ти има лекия недостатък че не премахва горутини и съответно (приемерно) пращаш сигнали че е expired-нал ключ който всъщност е бил Cleanup-нат.

Destroy-а ти има още неща за затваряне.

Харесва ми буферирането ти на изтекли ключове, но ние НЕ го искаме и имаме тестове които фейлват заради това - не знам дали ще ги задържим и дали ще ти отнемаме точки, но го махни докато оправяш другите неща за всеки случай (аз поне не смятам че трябва да го правиш - макар че е готино :)).

Йордан обнови решението на 17.11.2014 23:56 (преди над 3 години)

package main
import (
"errors"
"reflect"
"strconv"
"strings"
"sync"
"time"
)
type DeleteSignal struct{}
type HashedValue struct {
value interface{}
expire time.Duration
timeExpires time.Time
ch chan DeleteSignal
}
// Create new HashedValue
func NewHashedValue(value interface{}, expire time.Duration) *HashedValue {
hv := new(HashedValue)
hv.value = value
hv.expire = expire
hv.ch = make(chan DeleteSignal)
hv.timeExpires = time.Now().Add(expire)
return hv
}
type ExpireMap struct {
hash map[string]*HashedValue
expiredKeys []string
ch chan string
chEKIn chan string
chEKOut chan string
mutex *sync.Mutex
}
// Create new ExpireMap
func NewExpireMap() *ExpireMap {
em := new(ExpireMap)
em.hash = make(map[string]*HashedValue)
em.ch = make(chan string)
em.chEKIn = make(chan string)
em.chEKOut = make(chan string)
go func() {
for {
if len(em.expiredKeys) > 0 {
select {
case em.chEKOut <- em.expiredKeys[0]:
em.expiredKeys = em.expiredKeys[1:]
case key := <-em.chEKIn:
em.expiredKeys = append(em.expiredKeys, key)
}
} else {
key := <-em.chEKIn
em.expiredKeys = append(em.expiredKeys, key)
}
}
}()
em.mutex = new(sync.Mutex)
return em
}
// Set a record in Expire map
func (em *ExpireMap) Set(key string, value interface{}, expire time.Duration) {
hv := NewHashedValue(value, expire)
em.hash[key] = hv
// run a goroute to delete the record after expiring
go func(key string, duration time.Duration, ch chan DeleteSignal) {
defer em.mutex.Unlock()
select {
case <-ch:
em.mutex.Lock()
delete(em.hash, key)
case <-time.After(duration):
em.mutex.Lock()
go func(expiredKey string) {
em.chEKIn <- expiredKey
}(key)
delete(em.hash, key)
}
}(key, expire, hv.ch)
}
// get a HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) Get(key string) (interface{}, bool) {
if hashed, ok := em.hash[key]; ok {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value, true
}
return nil, false
}
// get an integer HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) GetInt(key string) (int, bool) {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "int" {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value.(int), true
}
return 0, false
}
// get a float64 HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) GetFloat64(key string) (float64, bool) {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "float64" {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value.(float64), true
}
return 0.0, false
}
// get a string HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) GetString(key string) (string, bool) {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value.(string), true
}
return "", false
}
// get a string HashedValue.value by a key from the ExpireMap
func (em *ExpireMap) GetBool(key string) (bool, bool) {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "bool" {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.value.(bool), true
}
return false, false
}
// get the time when the HashedValue.value will expire
func (em *ExpireMap) Expires(key string) (time.Time, bool) {
if hashed, ok := em.hash[key]; ok {
em.mutex.Lock()
defer em.mutex.Unlock()
return hashed.timeExpires, true
}
return time.Date(1, time.January, 01, 00, 0, 0, 0, time.UTC), false
}
// deletes the value in the ExpireMap with the given key
func (em *ExpireMap) Delete(key string) {
if hashed, ok := em.hash[key]; ok {
hashed.ch <- DeleteSignal{}
}
}
// checks in the key is in the hash
func (em *ExpireMap) Contains(key string) bool {
em.mutex.Lock()
defer em.mutex.Unlock()
_, ok := em.hash[key]
return ok
}
// size of the hash
func (em *ExpireMap) Size() int {
return len(em.hash)
}
// makes the string on position key in ExpireMap with CAPS
func (em *ExpireMap) ToUpper(key string) error {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
em.mutex.Lock()
defer func() {
em.mutex.Unlock()
}()
em.hash[key].value = strings.ToUpper(hashed.value.(string))
return nil
}
return errors.New("Not a string or key expired")
}
// makes the string on position key in ExpireMap without CAPS
func (em *ExpireMap) ToLower(key string) error {
if hashed, ok := em.hash[key]; ok && reflect.ValueOf(hashed.value).Type().Name() == "string" {
em.mutex.Lock()
defer func() {
em.mutex.Unlock()
}()
em.hash[key].value = strings.ToLower(hashed.value.(string))
return nil
}
return errors.New("Not a string or key expired")
}
// returns expired keys
func (em *ExpireMap) ExpiredChan() <-chan string {
return em.chEKOut
}
// cleans all records in the hash
func (em *ExpireMap) Cleanup() {
- for key, _ := range em.hash {
- delete(em.hash, key)
+ for key, value := range em.hash {
+ value.ch <- DeleteSignal{}
+ delete(em.hash, key) // don't ask :D => quick fix ,sometimes the key is not deleted (on time) by the DeleteSignal
+
}
}
// destructor of the ExpireMap
func (em *ExpireMap) Destroy() {
for _, value := range em.hash {
value.ch <- DeleteSignal{}
}
close(em.ch)
}
// increments values like 42 or "42" by 1
func (em *ExpireMap) Increment(key string) error {
if hashed, ok := em.hash[key]; ok {
em.mutex.Lock()
defer func() {
em.mutex.Unlock()
}()
- switch reflect.ValueOf(hashed.value).Type().Name() {
- case "int":
+ switch hashed.value.(type) {
+ case int:
em.hash[key].value = hashed.value.(int) + 1
return nil
break
- case "string":
+ case string:
number, err := strconv.Atoi(hashed.value.(string))
if err != nil {
return err
}
number++
em.hash[key].value = strconv.Itoa(number)
return nil
break
default:
return errors.New("Cannot increment. Not a string or int")
}
}
return errors.New("Key not found")
}
// decrements values like 42 or "42" by 1
func (em *ExpireMap) Decrement(key string) error {
if hashed, ok := em.hash[key]; ok {
em.mutex.Lock()
defer func() {
em.mutex.Unlock()
}()
- switch reflect.ValueOf(hashed.value).Type().Name() {
- case "int":
+ switch hashed.value.(type) {
+ case int:
em.hash[key].value = hashed.value.(int) - 1
return nil
break
- case "string":
+ case string:
number, err := strconv.Atoi(hashed.value.(string))
if err != nil {
return err
}
number--
em.hash[key].value = strconv.Itoa(number)
return nil
break
default:
errors.New("Cannot decrement. Not a string or int")
}
}
return errors.New("Key not found")
}