11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Redigoを使う(2) 様々なデータ型を扱う

Last updated at Posted at 2018-09-01

はじめに

RedisのGo言語向けクライアントライブラリRedigoの使い方を見ます。
この記事では様々なデータ型の扱い方を見ます。

Redisには下記の5つのデータ型があります。

  • 文字列型
  • リスト型
  • セット型
  • ソート済みセット型
  • ハッシュ型

文字列型については(1)の記事で見ました。本記事では他の4つについて、Redigoでの基本的な扱い方を見ていきます。

環境

リスト型

リスト型は、文字列型の双方向リストです。コマンド一覧はリファレンスを参照して下さい。まずは単純な要素の追加と取得を行います。

main.go
package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// 要素の追加
	_, err = conn.Do("RPUSH", "seasons", "spring")
	_, err = conn.Do("RPUSH", "seasons", "summer")
	_, err = conn.Do("RPUSH", "seasons", "autumn")
	_, err = conn.Do("RPUSH", "seasons", "winter")

	// 要素の取得
	a, err := redis.String(conn.Do("LINDEX", "seasons", 2))
	if err != nil {
		panic(err)
	}
	fmt.Println(a) // autumn

	// 全体の取得
	s, err := redis.Strings(conn.Do("LRANGE", "seasons", 0, -1))
	if err != nil {
		panic(err)
	}
	fmt.Println(s) // [spring summer autumn winter]
}

RPUSHはリストの末尾に要素を追加するコマンドです(ここでは戻り値チェックを省略しています)。seasonsというキーのリストに4つの要素を追加しています。
LINDEXは、リストの要素のうち、インデクスで指定された要素を返します。
LRANGEは、リストの要素のうち、2つのインデクスで指定された範囲の要素を返します。「0」は先頭、「-1」は末尾を意味します(すなわち全ての要素を返す)。さらに結果をredis.Stringsに渡しています。これはconn.Doの結果を[]stringに変換するためのユーティリティ関数です。

要素の削除は次のように行います。

main.go
package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// 要素の追加
	_, err = conn.Do("RPUSH", "words", "xxxxxx")
	_, err = conn.Do("RPUSH", "words", "yyy")
	_, err = conn.Do("RPUSH", "words", "z")
	_, err = conn.Do("RPUSH", "words", "xxxxxx")

	// 要素の削除(先頭に近いxxxxxxを1個だけ消す)
	n, err := redis.Int(conn.Do("LREM", "words", 1, "xxxxxx"))
	if err != nil {
		panic(err)
	}
	fmt.Println(n) // 1

	// 要素の削除(先頭の要素を消す)
	a, err := redis.String(conn.Do("LPOP", "words"))
	if err != nil {
		panic(err)
	}
	fmt.Println(a) // yyy

	// 全体の取得
	s, err := redis.Strings(conn.Do("LRANGE", "words", 0, -1))
	if err != nil {
		panic(err)
	}
	fmt.Println(s) // [z xxxxxx]
}

wordsというキーのリストに4つの文字列を追加したあと、LREMとLPOPで要素の削除を行っています。

LREMは、リストを先頭から見て、指定された値の要素を指定された個数だけ削除します。この場合は、一番最初に追加したxxxxxxが削除されます。戻り値は削除された要素数です。結果をredis.IntでGoのint型に変換しています。

LPOPは、リストの先頭の要素を削除します。このときの状態は[yyy, z, xxxxxx]ですので、先頭のyyyが削除されます。戻り値は削除された要素です。redis.Stringに渡してGoのstring型に変換しています。

今回は紹介しませんでしたが、リスト型のコマンドについては、ほかに、要素を先頭と末尾から切り詰めるLTRIM、特性の要素の値を書き換えるLSET、2つのリスト間で要素の移動をするRPOPLPUSHなどがあります。

セット型

セット型は、文字列型の集合です。リスト型と異なり、要素の重複を許しません。コマンド一覧はリファレンスを参照して下さい。要素の追加・削除・取得の一連の流れを示します。

main.go
package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// 要素の追加
	_, err = conn.Do("SADD", "numbers", "one")
	_, err = conn.Do("SADD", "numbers", "two")
	_, err = conn.Do("SADD", "numbers", "three")
	_, err = conn.Do("SADD", "numbers", "four")

	// 要素の追加(既に追加済み)
	n, err := redis.Int(conn.Do("SADD", "numbers", "one"))
	if err != nil {
		panic(err)
	}
	fmt.Println(n) // 0

	// 要素の削除
	m, err := redis.Int(conn.Do("SREM", "numbers", "three"))
	if err != nil {
		panic(err)
	}
	fmt.Println(m) // 1

	// 要素の存在確認
	k, err := redis.Int(conn.Do("SISMEMBER", "numbers", "five"))
	if err != nil {
		panic(err)
	}
	fmt.Println(k) // 0

	// 全体の取得
	s, err := redis.Strings(conn.Do("SMEMBERS", "numbers"))
	if err != nil {
		panic(err)
	}
	fmt.Println(s) // [one two four]
}

SADDはセットに要素を追加します。既に存在する要素を再度追加しようとすると、戻り値は0になります(成功時は1が返る)。
SREMは、セットから指定された要素を返します。
SISMEMBERは、指定された要素がセットに存在するか否かを返します。存在すれば1、しなければ0が返ります。
SMEMBRESはセットのすべての要素を取得します。

セット型のコマンドについては、他に、ランダムに要素を削除するSPOP(いつ使うんだろ?)、要素の個数を得るSCARD、2つのセット間で要素の移動をするSMOVEなどがあります。ほか、複数のセットの積集合(SINTER)や和集合(SUNION)、差集合(SDIFF)など、集合関係のコマンドが充実しています。

ソート済みセット型

ソート済みセット型は、スコアという数値にもとづいてソートされた文字列型の集合です。C++のstd::multimap<float,string>型のようなものだと思います。コマンド一覧はリファレンスを参照して下さい。5人の生徒のテスト点数をモチーフに、よくある操作の例を示します。

main.go
package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// 要素の追加
	_, err = conn.Do("ZADD", "math_test", 60, "alice")
	_, err = conn.Do("ZADD", "math_test", 50, "bob")
	_, err = conn.Do("ZADD", "math_test", 100, "charlie")
	_, err = conn.Do("ZADD", "math_test", 10, "dave")
	_, err = conn.Do("ZADD", "math_test", 20, "eve")

	// 最低スコアの要素(とそのスコア)を得る
	s, err := redis.Strings(conn.Do("ZRANGE", "math_test", 0, 0, "WITHSCORES"))
	if err != nil {
		panic(err)
	}
	fmt.Println(s) // [dave 10]

	// スコア40以上70以下の要素(とそのスコア)を得る
	t, err := redis.Strings(conn.Do("ZRANGEBYSCORE", "math_test", 40, 70, "WITHSCORES"))
	if err != nil {
		panic(err)
	}
	fmt.Println(t) // [bob 50 alice 60]

	// スコアが小さい順に要素を2個削除する
	n, err := redis.Int(conn.Do("ZREMRANGEBYRANK", "math_test", 0, 1))
	if err != nil {
		panic(err)
	}
	fmt.Println(n) // 2

	// 全体の取得
	u, err := redis.Strings(conn.Do("ZREVRANGE", "math_test", 0, -1))
	if err != nil {
		panic(err)
	}
	fmt.Println(u) // [charlie alice bob]
}

点数の低い順でソートされた状態で要素が保持されます。

ランク 0 1 2 3 4
スコア 10 20 50 60 100
dave eve bob alice charlie

特定の順位の生徒を得たいときは、ZRANGEを使います。例のように、ZRANGE, 0, 0と指定したときは、ランクが0以上0以下の生徒、すなわち最低点の生徒が得られます(0-index)。さらにWITHSCORESというオプションを指定するとスコアも合わせて得られます。反対に、最高点の生徒を得たいときはZREVRANGEを使います。
点数の範囲で絞り込むときは、ZRANGEBYSCOREを使います。例のように、ZRANGEBYSCORE, 40, 70と指定したときは、40以上70以下のスコアの要素が得られます。
要素の削除にはZREMRANGEBYRANKを用います。ZREMRANGEBYRANK, 0, 1は、ランクが0以上1以下の生徒、つまりdaveとeveの削除を意味します。

ハッシュ型

ハッシュ型は、文字列型の対(フィールドと値)のマップです。Pythonの辞書型のようなものだと思います(値は文字列限定ですが)。コマンド一覧はリファレンスを参照して下さい。以下は、profileというキーに対し、フィールドと値の対を追加・更新する例を示します。

main.go
package main

import (
	"fmt"

	"github.com/gomodule/redigo/redis"
)

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	// 要素の追加
	_, err = conn.Do("HSET", "profile", "name", "yamada")
	_, err = conn.Do("HSET", "profile", "age", "30")
	_, err = conn.Do("HSET", "profile", "country", "america")

	// 要素の追加と更新
	t, err := redis.String(conn.Do("HMSET", "profile", "country", "japan", "city", "tokyo"))
	if err != nil {
		panic(err)
	}
	fmt.Println(t) // OK

	// 要素の取得
	s, err := redis.String(conn.Do("HGET", "profile", "country"))
	if err != nil {
		panic(err)
	}
	fmt.Println(s) // japan

	// 値をインクリメントする
	n, err := redis.Int(conn.Do("HINCRBY", "profile", "age", "2"))
	if err != nil {
		panic(err)
	}
	fmt.Println(n) // 32

	// 全体の取得
	u, err := redis.Strings(conn.Do("HGETALL", "profile"))
	if err != nil {
		panic(err)
	}
	fmt.Println(u) // [name yamada age 32 country japan city tokyo]
}

フィールドと値を追加するにはHSETを使います。ここでは戻り値を捨てていますが、フィールドが既に存在するなら0が戻り(値は上書きされます)、しないなら1が戻ります。
HMSETで複数の対を追加することもできます。フィールド1, 値1, フィールド2, 値2, ・・のように、フィールドと値を交互に並べます。
HINCRBYで値の増減を行うことも可能です。値が64bit符号付き整数の範囲内である必要があります。
またHGETALLで全ての対を取得できます。フィールドと値が交互に現れます。

おわりに

Redisの様々なデータ型をRedigoでの扱い方を見ていきました。

参考

11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?