1
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?

記事投稿キャンペーン 「2024年!初アウトプットをしよう」

Goのスライスを効率的にソートする方法

Last updated at Posted at 2024-01-26

はじめに

Go言語はそのシンプルさと効率の良さで人気を集めていると思っている。特に、スライス(動的配列)の操作はGoの重要な特徴の一つです。今回は、Go言語でスライスをソートする具体的な方法を、実用的な例を交えてご紹介しようと思います。

サンプルコードと説明

package main

import (
	"fmt"
	"sort"
	"time"
)

type Article struct {
	Title     string    // 記事のタイトル
	CreatedAt time.Time // 記事が作成された日時
}

// Articles は複数のArticleを含むスライスです。
type Articles []Article

// SortByCreatedAt はArticlesをCreatedAtフィールドに基づいて昇順にソートします。
func (a Articles) SortByCreatedAt() {
	sort.SliceStable(a, func(i, j int) bool {
		return a[i].CreatedAt.Before(a[j].CreatedAt)
	})
}

func main() {
	// サンプルの記事データを作成します。
	articles := Articles{
		{Title: "Goの便利な機能", CreatedAt: time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC)},
		{Title: "GoでWebアプリケーションを作る", CreatedAt: time.Date(2024, 1, 5, 0, 0, 0, 0, time.UTC)},
		{Title: "Goの基本", CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)},
	}

	// ソート前の記事を表示します。
	fmt.Println("ソート前:")
	for _, article := range articles {
		fmt.Printf("  %v: %v\n", article.CreatedAt.Format("2006-01-02"), article.Title)
	}

	// 記事をソートします。
	articles.SortByCreatedAt()

	// ソート後の記事を表示します。
	fmt.Println("\nソート後:")
	for _, article := range articles {
		fmt.Printf("  %v: %v\n", article.CreatedAt.Format("2006-01-02"), article.Title)
	}
}

  // ソート前: 
  // 2024-01-10: Goの便利な機能
  // 2024-01-05: GoでWebアプリケーションを作る
  // 2024-01-01: Goの基本

  // ソート後:
  // 2024-01-01: Goの基本
  // 2024-01-05: GoでWebアプリケーションを作る
  // 2024-01-10: Goの便利な機能

上記のようなコードがあるとして、実際にSortを実行する関数はSortByCreatedAt()です。
このメソッドは、Articles 型(Article 構造体のスライス)に定義されています。Article 構造体には、少なくとも CreatedAt というフィールドが含まれており、これは記事が作成された日時を表しています。このメソッドの目的は、Articles スライス内の Article 要素を、その CreatedAt フィールドの値に基づいてソートすることです。

具体的には、sort.SliceStable 関数を使用しています。この関数はGo言語の標準ライブラリに含まれる関数で、指定されたスライスを安定的にソートするために使用されます。安定的なソートとは、等しい要素の相対的な順序がソート前後で保持されることを意味します。

  1. ソートするスライス:この場合は a、つまり Articles 型のインスタンスです。
  2. 比較関数:func(i, j int) bool というシグネチャを持つ関数です。この関数はスライス内の2つの要素(ij の位置にある要素)を比較し、i 番目の要素が j 番目の要素より「小さい」場合に true を返します。
    この場合の比較関数は、a[i].CreatedAt.Before(a[j].CreatedAt) を使用しています。これは Article 構造体の CreatedAt フィールド(time.Time 型)を比較し、i 番目の記事が j 番目の記事よりも早く作成されたかどうかを判断します。Before メソッドは、一つの time.Time 値が別の time.Time 値よりも時刻的に前であるかを確認するためのものです。

結果として、この SortByCreatedAt メソッドは Articles スライスを、各 Article が作成された時間の昇順(古いものから新しいものへ)でソートします。これは、例えばブログ記事を投稿日時の順に表示する場合などに便利です。

SortByCreatedAt関数の書き方の違い、パフォーマンス観点

func (a Articles) SortByCreatedAt() {
	sort.SliceStable(r, func(i, j int) bool {
		return a[i].CreatedAt.UnixNano() < a[j].CreatedAt.UnixNano()
	})
}
------
func (a Articles) SortByCreatedAt() {
	sort.SliceStable(a, func(i, j int) bool {
		return a[i].CreatedAt.Before(a[j].CreatedAt)
	})
}

この二つの SortByCreatedAt メソッドの結果には基本的な違いはありませんが、比較の方法とパフォーマンスの観点で微妙な差異があります。

  1. r[i].CreatedAt.UnixNano() < r[j].CreatedAt.UnixNano() を使用する方法:
    この方法では、time.Time 型の CreatedAt フィールドを UnixNano メソッドでナノ秒単位の整数に変換して比較しています。
    ナノ秒単位での比較は非常に正確で、ほんのわずかな時間差も検出できます。
    ただし、整数比較のため、特定の条件下でのオーバーフローの可能性がわずかに存在します(非常に稀ですが、理論上は可能)。

  2. a[i].CreatedAt.Before(a[j].CreatedAt) を使用する方法:
    Before メソッドは、一つの time.Time 値が別の time.Time 値よりも時刻的に前であるかを判断します。
    この方法は、time.Time 型の値を直接比較するため、より自然で読みやすいコードになります。
    Before メソッドは、内部的に time.Time 型の値をナノ秒まで比較するので、精度は UnixNano メソッドを使用する方法と同等です。

パフォーマンスの観点からは、これらの方法の間に顕著な差はないと考えられます。両方とも、比較にかかる時間は非常に短く、実際のソートアルゴリズムの実行時間に比べれば無視できる程度です。しかし、Before メソッドを使う方がコードの可読性が高く、Goの標準ライブラリの関数を利用するため、一般的には好まれる方法です。

結論として、どちらの方法も同等の結果と精度を提供しますが、Before メソッドを使用する方が一般的なGoの慣習により適合し、コードの可読性が高いと言えます。

参考文献

1
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
1
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?