Go言語のsortアレコレ!ソートの基本からスライスを自在に並べ替える方法を解説
Go言語のソートはかなり便利です。この記事では、Golangのソートの基本から応用までを解説します。sort
パッケージを活用すれば、整数、文字列はもちろん、構造体のスライスまで簡単に順番に並べ替えることが可能です。この記事では、具体的なコード例もたくさん用意しています。
はじめに
Go言語のsort
パッケージを使うと、複雑なソートもかなり簡単に書くことが可能です。この記事では、整数型・文字列型のスライスを昇順にソートするといった簡単な例から、構造体をソートするような複雑なパターンまで解説したいと思います。
Go言語でsort
パッケージを利用するには、パッケージをインポートする必要があります。具体的には、以下のコードを書く必要があります。
import (
"sort"
)
Visual Studio Codeなどで、Golang向けのプラグインを使っている場合は、必要があればimportが自動的に挿入されるのであまり気にする必要はないかもしれません。
整数型(int)のソート
昇順にソート
整数型を昇順ソートする場合は、sort.Ints()
を利用します。
a := []int{4, 3, 10, 2, 1, 5}
sort.Ints(a)
fmt.Println(a)
// [1 2 3 4 5 10]
降順ソート
大きい順(降順)にソートしたい場合は、以下のようにsort.Sort()
とSort.Reverse()と
、Sort.IntSlice()
を使います。
個人的には、この方法より後で説明するsort.Slice()
を使った方が簡単だと思います。
a := []int{4, 3, 10, 2, 1, 5}
sort.Sort(sort.Reverse((sort.IntSlice(a))))
fmt.Println(a)
// [10 5 4 3 2 1]
文字列型(string)のソート
文字列の昇順ソートにはsort.Strings()
を利用します
s := []string{"test", "hello", "world", "abc"}
sort.Strings(s)
fmt.Println(s)
// [abc hello test world]
浮動小数点型(float64)のソート
浮動小数点を昇順ソートするには、sort.Float64()
を利用します。
f := []float64{5.6, 22.3, 1.0, 0.44, 2.11}
sort.Float64s(f)
fmt.Println(f)
// [0.44 1 2.11 5.6 22.3]
sort.Sliceを使って、複雑なソートを行う
Go言語のsort.Slice()
がかなり便利です。
sort.Slice()
を使えば、複雑なソートもこれを利用すれば記述できます。個人的には、この関数の使い方だけ覚えておいたらソートはOKだと思っています。
sort.Slice関数
sort.Slice
は、第一引数がソートしたいスライス、第二引数がソートを行うためのless関数になります。
例えば、整数型を降順ソートしたい場合は以下のように記述します。
func(i, j int)bool
は、条件を満たした場合にtrueを返す関数で、条件によりソートが行われます。
a := []int{4, 3, 10, 2, 1, 5}
sort.Slice(a, func(i, j int) bool {
return a[i] > a[j]
})
fmt.Println(a)
// [10 5 4 3 2 1]
もし、昇順にソートしたい場合は、a[i] > a[j]
をa[i] < a[j]
に書き換えればOKです。昇順も降順も同じようにかけるので、こちらを使う方がわかりやすい気がします。
sort.Slice
は、安定ソートではありません。同値の場合に元の要素順を保ちたい場合はsort.SliceStable
を利用します。使い方は同じです。
この書き方で、任意のスライスを好きな条件でソートすることができます。
構造体の特定の要素の値でソートする
以下のような構造体がある場合に、特定の要素でソートしたい場合も、sort.Slice()
を利用します。
type S struct {
x, y int
}
x
でソートしたい場合には、ソート条件をs[i].x < s[j].x
とします。
y
でソートしたい場合には、s[i].y < s[j].y
とします。
このように、構造体の特定の要素でソートすることも簡単にできます。
以下は、x
でソートした後に、y
でソートする例です。
type S struct {
x, y int
}
s := []S{{3, 10}, {1, 20}, {5, 1}}
sort.Slice(s, func(i, j int) bool {
return s[i].x < s[j].x
})
fmt.Println(s)
// [{1 20} {3 10} {5 1}]
sort.Slice(s, func(i, j int) bool {
return s[i].y < s[j].y
})
fmt.Println(s)
// [{5 1} {3 10} {1 20}]
複数のキーでソート
少し複雑になりますが、複数の項目でソートすることも可能です。
この例では、$\frac{x_i}{y_i} <\frac{x_j}{y_j}$の順でソートさせます。
除算をそのまま使ってソートしても良いですが、整数で除算を行うと丸めが発生しますし、浮動小数点に変換して除算を行うと誤差が発生し、ソート順がおかしくなってしまう可能性があります。
ここでは、式を以下のように展開してソートを行います。
$$\begin{eqnarray}
\frac{x_i}{y_i} &<&\frac{x_j}{y_j} \\
x_i \times y_j &<& x_j \times y_i
\end{eqnarray}$$
sort.Slice
を使うと、このように、左辺がs[i].x*s[j].y
, 右辺がs[j].x*s[i].y
というようなソートも簡単に記述することができます。
以下がプログラム例になります。
type S struct {
x, y int
}
s := []S{{3, 10}, {1, 20}, {5, 2}, {1, 3}, {3, 4}}
sort.Slice(s, func(i, j int) bool {
return s[i].x*s[j].y < s[j].x*s[i].y
})
fmt.Println(s)
// [{1 20} {3 10} {1 3} {3 4} {5 2}]
このように、i,jが左片にも右辺にも含まれるような式を直接記述できるのがGoのsort.Slice
のメリットになります。
PyThonの場合
同様のソートをPythonで行う場合は以下のようにします。
from functools import cmp_to_key
def compare(obj1, obj2):
return (obj1[0] * obj2[1]) - (obj2[0] * obj1[1])
s = [(3, 10), (1, 20), (5, 2), (1, 3), (3, 4)]
s.sort(key=cmp_to_key(compare))
print(s)
具体的には、cmp_to_key
で比較を行う関数を設定します。compare
は、obj1<obj2
であれば負を、同じであれば0を、obj1>obj2
であれば正の値を返す関数です。
Pythonでソートを書く方法については以下の記事を参考にしてください。
要素xは昇順、要素yは降順でソート
少し複雑な例として、「要素x
については昇順、要素xが同値の場合は要素をy
について降順」でソートする場合の例です。
以下、コードになります。func(i,j int)bool
に記述できるのであれば、複雑なパターンのソートもいくらでも行うことが可能です。
type S struct {
x, y int
}
s := []S{{3, 10}, {1, 22}, {5, 20}, {1, 20}, {5, 2}, {1, 3}, {3, 4}}
sort.Slice(s, func(i, j int) bool {
if s[i].x == s[j].x {
return s[i].y > s[j].y
}
return s[i].x < s[j].x
})
fmt.Println(s)
// [{1 22} {1 20} {1 3} {3 10} {3 4} {5 20} {5 2}]
PyThonの場合
同様のソートをPythonで行う場合は以下のようにします。
sorted_a = sorted(a, key=lambda x: (x[0], -x[1]))
こちらは少しテクニカルです。Go言語の方が直感的だと思います。
まとめ
以上、Go言語のソートについて説明しました。Go言語のソートはかなり使いやすいと思います。特に、sort.Slice
だけ使いこなせれば大体のソートを簡単に書くことが可能です。sort.Slice
の書き方は、覚えておいて損はないと思います。