ExpireMap

Предадени решения

Краен срок:
18.11.2014 17:00
Точки:
10

Срокът за предаване на решения е отминал


В тази задача ще си говорим за кеширане. Понякога е много полезно да се запази резултат от скъпа операция и вместо след това отново да се извърши скъпата операция да се използва вече запазеният резултат. Но света се променя и след време този резултат може да е невалиден. За това е хубаво да го пазим само определено време. Това време е различно за различните видове резултати. HTML-a на web страница може да се пази секунди или дори цели минути, но повече от това е прекалено много. Но IP-то на домейн може да се пази часове и дори дни, защото рядко се променя. Точно DNS-ите са хубав пример как кеширането работи за да "ускори" многократно интернета ни.

Вашата задача ще е да направите тип ExpireMap, който да може да се използва за такова кеширане. Той трябва да е key-value хранилище, подобен на map в Go, но всеки ключ ще има максимално време на живот. След като времето на живот на ключа изтече той и стойността му трябва да изчезнат от хранилището. Ключовете в хранилището са string, а стойностите от произволен тип (иначе казано - interface{}).

Хранилището трябва да може да бъде използваемо от много go рутини едновременно.

type ExpireMap

Функциите и методите, които трябва да има вашето решение са следните

func NewExpireMap() *ExpireMap

Връща указател към нов обект от тип ExpireMap, готов за използване.

func (em *ExpireMap) Set(key string, value interface{}, expire time.Duration)

Добавя в хранилището нов ключ key със стойност value за expire време. Duration е дефиниран в пакета time. expire време след добавянето на ключа той вече трябва да спре да бъде в хранилището. Стойността може да е от всякакъв тип, докато ключа е от тип string.

func (em *ExpireMap) Get(key string) (interface{}, bool)

Връща стойността на ключ и true, когато ключа е намерен. Когато ключа липсва или е изтекъл връща nil и false.

func (em *ExpireMap) GetInt(key string) (int, bool)

Връща стойността, отговаряща на този ключ и true, когато ключа е намерен и стойността му е от тип int. Когато ключа липсва, изтекъл е или стойността му не е от този тип метода трябва да върне нула и false.

func (em *ExpireMap) GetFloat64(key string) (float64, bool)

Връща стойността на този ключ и true, когато ключа е намерен и стойността му е от тип float64. Когато ключа липсва, изтекъл е или стойността му не е от този тип метода трябва да върне нула и false.

func (em *ExpireMap) GetString(key string) (string, bool)

Връща стойността на ключ и true, когато ключа е намерен и стойността му е от тип string. Когато ключа липсва, изтекъл е или стойността не е от тип string метода трябва да върне празен стринг и false.

func (em *ExpireMap) GetBool(key string) (bool, bool)

Връща стойността на ключ и true, когато ключа е намерен и стойността му е от тип bool. Когато ключа липсва, изтекъл е или стойността не е от тип bool метода трябва да върне false и false.

func (em *ExpireMap) Expires(key string) (time.Time, bool)

За определен ключ тази функция трябва да върне времето, когато той ще изтече. Втората върната стойност е true когато ключа е намерен в хранилището. Когато не е намерен функцията трябва да върне нулевото време и false.

func (em *ExpireMap) Delete(key string)

Премахва ключа от хранилището. Когато няма такъв ключ метода не трябав да гърми.

func (em *ExpireMap) Contains(key string) bool

Казва дали ключ е в хранилището или не. Отново, ключове са в хранилището само ако техния expire не е вече изтекъл.

func (em *ExpireMap) Size() int

Връща големината на хранилището. Големина е бройката неизтекли ключове, които са в него.

func (em *ExpireMap) Increment(key string) error

За ключ увеличава стойността му числово с 1 когато тази стойност е от тип int или стринг, представящ цяло число. Когато стойността е от тип, различен от тези неща функцията трябва да върне грешка, различна от nil. float32 и float64 не са целочислен типове. Времето на изтичане на ключа не трябва да се променя.

func (em *ExpireMap) Decrement(key string) error

Същото като Increment, но намалява стойността с 1 вместо да я увеличава.

func (em *ExpireMap) ToUpper(key string) error

Ако стойността за ключа key е от тип string, то всички букви в този string трябва да станат главни. При успех метода трябва да върне nil, а при невъзможност да изпълни задачата - грешка. Пример за употреба:

em := NewExpireMap()
em.Set("example", "creeper on the roof", 10 * time.Minute)
if err := em.ToUpper("example"); err != nil {
    // error converting the string. maybe it has expired?
}
upped, _ := em.GetString("example") // upped is "CREEPER ON THE ROOF"
func (em *ExpireMap) ToLower(key string) error

Същото като ToUpper, но всички букви трябва станат малки.

func (em *ExpireMap) ExpiredChan() <- chan string

Тази функция трябва да върне канал, по който да идват ключовете, които са изтекли. Те трябва да се пускат по канала в момента на изтичането си. Ключове, които са изтрити с Delete или Cleanup не трябва да се връщат по този канал. Единствено тези, които са достигнали до времето си на изтичане. Не е задължително през цялото време някой да чете от върнатия канал. Ключове добавени в инстанцията след извикването на ExpireChan също трябва да се пратят по канала, когато тяхното време дойде.

em := NewExpireMap()
defer em.Destroy()

added := time.Now()

em.Set("key1", "val1", 50 * time.Millisecond)
em.Set("key2", "val2", 100 * time.Millisecond)

expires := em.ExpiredChan()

for i := 0; i < 2; i++ {
    key := <- expires
    fmt.Printf("%s expired after %s\n", key, time.Since(added))
}
func (em *ExpireMap) Cleanup()

Изчиства хранилището. След извикване на функцията в ExpireMap-a не трябва да се съдържа нито един ключ.

func (em *ExpireMap) Destroy()

Трябва да освободи всички ресурси, които е използвал този ExpireMap. Това означава изчистване на всички структури, затваряне на всички канали и спиране на всички горутини, свързани с нормалната му работа. След извикване на Destroy тази инстанция на ExpireMap няма да бъде повече използвана. Този метод би трябвало да се ползва по подобен начин:

em := NewExpireMap()
defer em.Destroy()

Пример

cache := NewExpireMap()
defer cache.Destroy()

cache.Set("foo", "bar", 15*time.Second)
cache.Set("spam", "4", 25*time.Minute)
cache.Set("eggs", 9000.01, 3*time.Minute)

err := cache.Increment("spam")

if err != nil {
    fmt.Println("Sadly, incrementing the spam did not succeed")
}

if val, ok := cache.Get("spam"); ok {
    fmt.Printf("The value of spam should be the string 5: %s", val)
} else {
    fmt.Println("No spam. Have some eggs instead?")
}

if eggs, ok := cache.GetFloat64("eggs"); !ok || eggs <= 9000 {
    fmt.Println("We did not have as many eggs as expected. Have you considered our spam offers?")
}

Тестовете ни ще позволяват на вашите имплементации да се забавят до 10ms с изчистването на изтекли ключове. Много ще харесаме решения, които не хабят памет за да пазят вече изтекли ключове и стойности.