0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Go] slices.SortFuncで文字列を辞書順にソートする

Last updated at Posted at 2025-04-20

はじめに

前回の記事では、Goで文字列を辞書順にソートする方法の一つとしてsort.Sliceを使った方法を紹介しました。
公式ドキュメントのsort.Sliceの項目を読んでいるとslices.SortFuncという関数が紹介されており、これでも同様のことができそうでした。
そこでこの記事では、slices.SortFuncの使い方とsort.Sliceslices.SortFuncの違いや処理効率の良い方はどちらなのかについて調べてみました。

slices.SortFuncって何?

これはGo 1.21からslicesパッケージに入った新機能で、任意のスライスを、指定した比較関数でソートする関数です。
似たようなことはsort.Slicesでもできますが、slices.SortFuncはそれをより型安全&多少高速にしたものです。

slices.SortFuncで文字列を辞書順にソートする

slices.SortFuncも使い方はsort.Sliceと似ています。

// 第一引数にソートしたいスライス、第二引数にどんなルールでソートするかの比較関数を渡す
slices.SortFunc(slice, lessFunc)
// 例
slices.SortFunc(names, func(a, b string) int {
		return strings.Compare(strings.ToLower(a), strings.ToLower(b))
})

strings.Compareは比較したい2つのstring型の値(a, b)を引数として渡すと、比較結果によって以下のint型の値が返ってきます。
a < b → -1
a > b → +1
a = b → 0
ここではstrings.ToLowerを使って大文字を小文字に変換してから比較を行なっています。

main.go
package main

import (
	"fmt"
	"slices"
	"strings"
)

func main() {
	names := []string{"Bob", "alice", "VERA"}
    
	slices.SortFunc(names, func(a, b string) int {
		return strings.Compare(strings.ToLower(a), strings.ToLower(b))
	})
	fmt.Println(names)
}

出力結果

[alice Bob VERA]

ログを出力して処理過程を見てみる

前回同様、ログを仕込んで処理過程も見てみます。

package main

import (
	"fmt"
	"slices"
	"strings"
)

func main() {
	names := []string{"Bob", "alice", "VERA"}

	slices.SortFunc(names, func(a, b string) int {
		fmt.Println(names)
		fmt.Printf("Comparing: a = %v vs b = %v\n", a, b)
		fmt.Println(strings.Compare(strings.ToLower(a), strings.ToLower(b)))
		return strings.Compare(strings.ToLower(a), strings.ToLower(b))
	})
	
	fmt.Printf("result: %v", names)
}

出力結果

[Bob alice VERA]
Comparing: a = alice vs b = Bob
-1
[alice Bob VERA]
Comparing: a = VERA vs b = Bob
1
result: [alice Bob VERA]

出力結果を見ると、初めはaにalice、bにBobが入っています。a < bなので-1が返ってきて要素の入れ替えが発生します。次に、aにはVERA、bにはBobが入っています。a > bなので1が返ってきて要素の入れ替えは発生しません。

sort.Slicesとslices.SortFuncの速度を比較してみる

slices.SortFuncでも文字列を辞書順にソートすることができました。
では、sort.Sliceslices.SortFuncでは処理効率が良いのはどちらでしょうか?
ベンチマークテストを行なって処理効率を比較してみようと思います。
今回の動作環境におけるGoのバージョンはgo1.24.2を使用しています。

ベンチマークテスト
Goには標準でベンチマーク用のツール(testing.B)が組み込まれており、お手軽に性能比較を行うことができます。
まずはテストファイル(sort_benchmark_test.go)を作成します。

sort_benchmark_test.go
package main

import (
	"math/rand"
	"slices"
	"sort"
	"testing"
	"time"
)

func generateTestData() []string {
	// rand.NewSourceでシードを与え、rand.Newで新しい乱数生成器を作る
	source := rand.NewSource(time.Now().UnixNano())
	rng := rand.New(source)

	names := []string{
		"James", "Mary", "John", "Patricia", "Robert", "Jennifer",
		"Michael", "Linda", "William", "Elizabeth", "David", "Barbara",
		"Richard", "Susan", "Joseph", "Jessica", "Thomas", "Sarah",
		"Charles", "Karen",
	}

	var data []string
	for i := 0; i < 1000000; i++ {
		data = append(data, names[rng.Intn(len(names))]) // rng.Intnを使って乱数を生成
	}
	return data
}

func BenchmarkSortSlice(b *testing.B) {
	for i := 0; i < b.N; i++ {
		data := generateTestData()
		sort.Slice(data, func(i, j int) bool {
			return data[i] < data[j]
		})
	}
}

func BenchmarkSlicesSortFunc(b *testing.B) {
	for i := 0; i < b.N; i++ {
		data := generateTestData()
		slices.SortFunc(data, func(a, b string) int {
			if a < b {
				return -1
			} else if a > b {
				return 1
			}
			return 0
		})
	}
}

ディレクトリ構造

slicestest
├─ main.go
├─ go.mod
└─ sort_benchmark_test.go

テストファイルが用意できたら以下のコマンドを実行してベンチマークを開始します。

$ go test -bench=. -benchmem

実行結果

$ go test -bench=. -benchmem

goos: darwin
goarch: arm64
pkg: slicestest
cpu: Apple M1
BenchmarkSortSlice-8        	      18	  65553181 ns/op	88022706 B/op	      41 allocs/op
BenchmarkSlicesSortFunc-8   	      18	  63693988 ns/op	88022645 B/op	      39 allocs/op
PASS
ok  	slicestest	5.008s

ns/op: 1回のベンチマークにかかる時間(ナノ秒)

B/op: 1回のベンチマークにおけるメモリ使用量(バイト)

allocs/op: 1回のベンチマークで発生したメモリアロケーション回数

-benchmemオプションをつけることでメモリ使用量やメモリアロケーション回数なども見ることができます。
ベンチマークの実行結果を見たところ、名前データが100万件あったとしても処理効率はほとんど変わらず、多少slices.SortFuncの処理効率が良いかなという程度でした。

sort.Slicesとslice.SortFuncの違い

項目 sort.Slice slices.SortFunc
パッケージ sort(Go 1.8〜) slices(Go 1.21〜)
比較方法 func(i, j int) bool func(a, b T) int(汎用的)
型の安全性 型安全でない(インターフェース的) 型安全(ジェネリクス使用)
内部処理 sort.Sort() + インデックスアクセス ジェネリクスベースのソートロジック
ユースケース 任意の構造・配列に柔軟対応 同じ型のスライスに最適
推奨度(Go 1.21以降) 古くても使われる より推奨される(型安全)

まとめ

本記事では、Goで文字列を辞書順にソートする方法としてslices.SortFuncを使った方法を紹介しました。
sort.Sliceslices.SortFuncでベンチマークを取ってみましたが、処理効率にさほど差はないように見えました。
しかし、slices.SortFuncはGo1.21以降でないと使えないことやその他の条件によって適切な方法を選択する必要がありそうです。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?