Gih's Blog

只言片语
Posts tagged as go

Another way to make memoization function in Go

2014-09-03 by gihnius, tagged as go

Cache any function calls in Go without using reflect, very simple idea, use a normal memory cache.

Here is what I do:

package memoize

import (
    "sync"
    "time"
)

type memo struct {
    Timeout time.Time
    Result interface{}
}

type MemoPool struct {
    Pool map[string]*memo
    mutex *sync.RWMutex
}

var mp *MemoPool

func init() {
    var m = map[string]*memo{}
    mp = &MemoPool{Pool: m, mutex: new(sync.RWMutex)}
}

// memorize result return from caller() block, timeout in N seconds
func Memoize(key string, caller func() interface{}, timeout uint) interface{} {
    if timeout == 0 {
        // do not memoize
        return caller()
    }
    mp.mutex.RLock()
    memoized := mp.Pool[key]
    mp.mutex.RUnlock()
    // reached timeout or not memoized
    if memoized == nil || memoized.Timeout.Before(time.Now()) {
        result := caller()
        if result != nil {
            duration := time.Duration(timeout) * time.Second
            mp.mutex.Lock()
            mp.Pool[key] = &memo{
                Timeout: time.Now().Add(duration),
                Result: result,
            }
            mp.mutex.Unlock()
        }
        return result
    }
    return memoized.Result
}

func UnMemoize(key string) {
    delete(mp.Pool, key)
}

func UnMemoizeAll() {
    for key, _ := range mp.Pool {
        delete(mp.Pool, key)
    }
}

Check out the code from github: https://github.com/gihnius/gomemoize

usage

import "memoize"
// cache a function call for TimeOut (>= 0) seconds
memoize.Memoize("Cache_Key", func() interface{} {
  cache_return_value, err := your_function(args)
  if err != nil {
    // handle err
    // won't cache if return nil
    return nil
  }
  return cache_return_value
}, TimeOut)

// save result
result := memoize.Memoize("Cache_Key", func() interface{} {
  // call your function(s)
  cache_return_value, err := your_function(args)
  if err != nil {
    // handle err
    // won't cache if return nil
    return nil
  }
  return cache_return_value
}, TimeOut).(your_function_return_type)

// cache a long time calc subroutine
func calc_sub(i int, s string) (string, err) {
  // ...
}
result := Memoize("calc_sub", func() interface{} {
  res, err := calc_sub(1000, "string to process")
  if err != nil {
    return nil // not memoized
  }
  return res
}, 60) // cache for 1 minutes

// re-fetch the memoized(cached) result with the same call.

// remove a cached call
memoize.UnMemoize(key_name)
// remove all cached calls
memoize.UnMemoizeAll()

test

git clone https://github.com/gihnius/gomemoize
cd gomemoize
./test.sh
# or
VERBOSE="-v" ./test.sh

It works well but not so functional.

More about  Memoization from Wikipedia.


Strip chars from a string in Go.

2014-07-22 by gihnius, tagged as go
import (
    "fmt"
    "strings"
)

func stripchars(str, chr string) string {
    return strings.Map(func(r rune) rune {
        if strings.IndexRune(chr, r) < 0 {
            return r
        }
        return -1
    }, str)
}

Counting online users with Redis and Go

2014-07-20 by gihnius, tagged as go

an alternative way if you do not need to use websocket to do realtime counting.

only counting last 5 minutes, here assuming a user will stay online for five minutes after login/landing

var last_n_minutes = 5

// easily to change to yours
func redis_key_prefix() string {
    return App.Name + ":" + App.Env + ":"
}

func redis_key(suffix string) string {
    return redis_key_prefix() + suffix
}

// online key for online users every minute
// online_users_minute_5
// online_users_minute_6 // 5th and 6th minutes online users
func online_key(minute_suffix string) string {
    key := "online_users_minute_" + minute_suffix
    return redis_key(key)
}

// the key for THIS minute
func current_key() string {
    key := strconv.Itoa(time.Now().Minute())
    return online_key(key)
}

// return keys of last n minutes online users
func keys_in_last_n_minutes(n int) []string {
    now := time.Now()
    var res []string
    for i := 0; i < n ; i++ {
        ago := now.Add(-time.Duration(i) * time.Minute).Minute()
        res = append(res, online_key(strconv.Itoa(ago)))
    }
    return res
}

// add a online user to the set.
// call this operation from a ajax long pull is recommended, so
// do not need to write to redis every time user click/open a page.
func add_online_username(name string) {
    new_key := false
    key := current_key()
    if ok, _ := Redis.Exists(key); ok == false {
        new_key = true
    }
    Redis.Sadd(key, []byte(name))
    if new_key {
        // assuming a user will be offline after last_n_minutes
        expiredin := int64((last_n_minutes+1)*60)
        Redis.Expire(key, expiredin)
    }
}

// the online usernames
func online_usernames() []string {
    keys := keys_in_last_n_minutes(last_n_minutes)
    users, err := Redis.Sunion(keys...)
    if err != nil {
        return nil
    }
    var res []string
    for _, u := range users {
        res = append(res, string(u))
    }
    return res
}

// counting how many online users
// just do it from redis
func online_users_count() int {
    current_online_key := redis_key("online_users_current")
    keys := keys_in_last_n_minutes(last_n_minutes)
    Redis.Sunionstore(current_online_key, keys...)
    n, err := Redis.Scard(current_online_key)
    if err != nil {
        return -1
    }
    // go set_online_max(n)
    set_online_max(n)
    return n
}

// the max value of online users
func set_online_max(curr int) {
    max_online_key := redis_key("online_users_max")
    orig, _ := Redis.Get(max_online_key)
    n, _ := strconv.Atoi(string(orig))
    if curr > n {
        Redis.Set(max_online_key, []byte(strconv.Itoa(curr)))
    }
}

func get_online_max() int {
    max_online_key := redis_key("online_users_max")
    orig, _ := Redis.Get(max_online_key)
    n, err := strconv.Atoi(string(orig))
    if err != nil {
        return -1
    }
    return n
}

The redis package using here is: github.com/hoisie/redis, to install it to GOPATH:

go get -u github.com/hoisie/redis

Get the code and a full example, please check out: https://github.com/gihnius/redis_online_counter