Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11954] ファイルの概要

このコミットは、Go言語の標準ライブラリである sort パッケージに、sort.Interface の使用例を追加するものです。具体的には、カスタム型をソートする方法と、既存の sort.Interface を利用して逆順ソートを実現する方法を示す新しいテストファイルが追加されています。これにより、sort パッケージの利用者が、より簡単に独自のデータ構造をソートできるよう、具体的なコード例が提供されます。

コミット

  • コミットハッシュ: 8bb7f7791b20d6d1b287e728b76ef95a8dd6af7c
  • Author: Andrew Gerrand adg@golang.org
  • Date: Thu Feb 16 13:16:07 2012 +1100
  • 変更ファイル:
    • src/pkg/sort/example_interface_test.go (新規追加)
    • src/pkg/sort/example_reverse_test.go (新規追加)
  • 変更行数: 2ファイルで合計107行の追加

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/8bb7f7791b20d6d1b287e728b76ef95a8dd6af7c

元コミット内容

sort: add interface examples

R=golang-dev, bradfitz, r
CC=golang-dev
https://golang.org/cl/5677060

変更の背景

Go言語の sort パッケージは、スライスやカスタムコレクションをソートするための強力な機能を提供します。しかし、その中心となる sort.Interface の概念は、Go言語に慣れていない開発者にとっては直感的ではない場合があります。特に、Len(), Less(i, j int), Swap(i, j int) の3つのメソッドを実装する必要があるため、具体的な使用例が不足していると、どのようにカスタム型をソートすれば良いか迷うことがあります。

このコミットは、このような背景から、sort.Interface の具体的な実装例と、それを利用したソートのデモンストレーションを提供することを目的としています。これにより、開発者が sort パッケージをより効果的に活用し、カスタムデータ構造のソートを容易に行えるようになることが期待されます。また、Reverse のような汎用的な逆順ソートの例も提供することで、より高度なソート要件にも対応しやすくなります。

前提知識の解説

Go言語の sort パッケージ

Go言語の sort パッケージは、組み込み型(int, float64, string)のスライスをソートするための関数(sort.Ints, sort.Float64s, sort.Strings)を提供しますが、最も重要なのはカスタム型をソートするための sort.Interface インターフェースです。

sort.Interface インターフェース

sort.Interface は以下の3つのメソッドを定義しています。

  1. Len() int: ソート対象の要素数を返します。
  2. Less(i, j int) bool: インデックス i の要素がインデックス j の要素よりも「小さい」(ソート順で前に来る)場合に true を返します。このメソッドがソートの基準を決定します。
  3. Swap(i, j int): インデックス i とインデックス j の要素を入れ替えます。

カスタム型をソートするには、そのカスタム型(またはそのカスタム型をラップする型)が sort.Interface を実装する必要があります。

Go言語の fmt パッケージ

fmt パッケージは、フォーマットされたI/O(入力/出力)を実装するためのパッケージです。fmt.Printlnfmt.Printf などを使用して、標準出力に文字列や変数の値を出力するために広く使われます。

  • fmt.Sprintf(format string, a ...interface{}) string: フォーマット文字列と引数に基づいて文字列を生成し、その文字列を返します。
  • fmt.Printf(format string, a ...interface{}) (n int, err error): フォーマット文字列と引数に基づいて文字列を生成し、標準出力に出力します。
  • fmt.Println(a ...interface{}) (n int, err error): 引数をスペースで区切り、改行を追加して標準出力に出力します。

また、カスタム型が String() string メソッドを実装している場合、fmt パッケージのフォーマット動詞(例: %v)はそのメソッドを呼び出して値を文字列として表現します。これは、デバッグ出力やユーザーフレンドリーな表示に非常に便利です。

Go言語の埋め込み(Embedded Structs)

Go言語では、構造体の中に別の構造体をフィールドとして宣言することで、その埋め込まれた構造体のメソッドを外側の構造体が「継承」したかのように振る舞わせることができます。これは、コードの再利用性を高めるための強力なメカニズムです。

例えば、type ByName struct { Organs } のように Organs を埋め込むと、ByName 型のインスタンスは Organs 型が持つ Len()Swap() メソッドを直接呼び出すことができます。これにより、ByNameLess() メソッドだけを独自に実装すれば sort.Interface を満たすことができます。

技術的詳細

このコミットでは、sort パッケージの利用方法を具体的に示すために、2つの新しいテストファイルが追加されています。これらのファイルは、Goのテストフレームワークの Example 関数として機能し、ドキュメントの一部として自動的に実行され、出力が検証されます。

src/pkg/sort/example_interface_test.go

このファイルは、カスタムデータ構造をソートする方法を詳細に示しています。

  1. Grams: int を基底とするカスタム型で、String() メソッドを実装しています。これにより、fmt.Printf などで Grams 型の値を人間が読みやすい形式で出力できるようになります(例: "1340g")。
  2. Organ 構造体: Name (string) と Weight (Grams) を持つ構造体で、ソート対象の個々の要素を表します。
  3. Organs: []*Organ のスライス型で、sort.InterfaceLen()Swap() メソッドを実装しています。これにより、Organs 型はソート可能なコレクションとしての基本的な振る舞いを持ちます。
  4. ByName 構造体: Organs を埋め込み、Less() メソッドを実装しています。この Less() メソッドは OrganName フィールドに基づいて比較を行います。これにより、Organs のスライスを名前順にソートできます。
  5. ByWeight 構造体: Organs を埋め込み、Less() メソッドを実装しています。この Less() メソッドは OrganWeight フィールドに基づいて比較を行います。これにより、Organs のスライスを重さ順にソートできます。
  6. ExampleInterface() 関数: 実際のソート処理と出力の例を示しています。
    • Organ のスライスを初期化します。
    • sort.Sort(ByWeight{s}) を使用して重さ順にソートし、結果を出力します。
    • sort.Sort(ByName{s}) を使用して名前順にソートし、結果を出力します。
    • Output: コメントブロックにより、期待される出力が明示されています。これはGoのテストフレームワークが自動的に検証します。

src/pkg/sort/example_reverse_test.go

このファイルは、既存の sort.Interface の実装を逆順にソートする方法を示しています。

  1. Reverse 構造体: sort.Interface を埋め込んでいます。
  2. Less(i, j int) bool メソッド: 埋め込まれた sort.InterfaceLess メソッドを呼び出し、その結果を反転させます (r.Interface.Less(j, i))。これにより、元のソート順とは逆の順序でソートが行われます。
  3. ExampleInterface_reverse() 関数: 実際の逆順ソートの例を示しています。
    • int のスライスを初期化します。
    • sort.Sort(Reverse{sort.IntSlice(s)}) を使用して逆順にソートします。sort.IntSlice[]intsort.Interface に適合させるためのアダプターです。
    • 結果を出力し、期待される出力 ([6 5 4 3 2 1]) が Output: コメントブロックで示されています。

これらの例は、sort.Interface の柔軟性と、Goのインターフェースと埋め込みの強力な組み合わせを示しています。

コアとなるコードの変更箇所

src/pkg/sort/example_interface_test.go

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sort_test

import (
	"fmt"
	"sort"
)

type Grams int

func (g Grams) String() string { return fmt.Sprintf("%dg", int(g)) }

type Organ struct {
	Name   string
	Weight Grams
}

type Organs []*Organ

func (s Organs) Len() int      { return len(s) }
func (s Organs) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

// ByName implements sort.Interface by providing Less and using the Len and
// Swap methods of the embedded Organs value.
type ByName struct{ Organs }

func (s ByName) Less(i, j int) bool { return s.Organs[i].Name < s.Organs[j].Name }

// ByWeight implements sort.Interface by providing Less and using the Len and
// Swap methods of the embedded Organs value.
type ByWeight struct{ Organs }

func (s ByWeight) Less(i, j int) bool { return s.Organs[i].Weight < s.Organs[j].Weight }

func ExampleInterface() {
	s := []*Organ{
		{"brain", 1340},
		{"heart", 290},
		{"liver", 1494},
		{"pancreas", 131},
		{"prostate", 62},
		{"spleen", 162},
	}

	sort.Sort(ByWeight{s})
	fmt.Println("Organs by weight:")
	printOrgans(s)

	sort.Sort(ByName{s})
	fmt.Println("Organs by name:")
	printOrgans(s)

	// Output:
	// Organs by weight:
	// prostate (62g)
	// pancreas (131g)
	// spleen   (162g)
	// heart    (290g)
	// brain    (1340g)
	// liver    (1494g)
	// Organs by name:
	// brain    (1340g)
	// heart    (290g)
	// liver    (1494g)
	// pancreas (131g)
	// prostate (62g)
	// spleen   (162g)
}

func printOrgans(s []*Organ) {
	for _, o := range s {
		fmt.Printf("%-8s (%v)\n", o.Name, o.Weight)
	}
}

src/pkg/sort/example_reverse_test.go

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sort_test

import (
	"fmt"
	"sort"
)

// Reverse embeds a sort.Interface value and implements a reverse sort over
// that value.
type Reverse struct {
	// This embedded Interface permits Reverse to use the methods of
	// another Interface implementation.
	sort.Interface
}

// Less returns the opposite of the embedded implementation's Less method.
func (r Reverse) Less(i, j int) bool {
	return r.Interface.Less(j, i)
}

func ExampleInterface_reverse() {
	s := []int{5, 2, 6, 3, 1, 4} // unsorted
	sort.Sort(Reverse{sort.IntSlice(s)})
	fmt.Println(s)
	// Output: [6 5 4 3 2 1]
}

コアとなるコードの解説

example_interface_test.go の解説

このファイルは、Goの sort.Interface をカスタム型に適用する典型的なパターンを示しています。

  • Grams 型と String() メソッド: type Grams int は、int 型を基底とする新しい型 Grams を定義しています。 func (g Grams) String() string { return fmt.Sprintf("%dg", int(g)) } は、Grams 型が fmt.Stringer インターフェース(String() string メソッドを持つインターフェース)を満たすようにします。これにより、fmt.Printffmt.PrintlnGrams 型の値を表示する際に、自動的にこの String() メソッドが呼び出され、例えば 13401340g と表示されるようになります。これは、出力の可読性を高めるためのGoの慣用的なパターンです。

  • Organ 構造体: type Organ struct { Name string; Weight Grams } は、ソート対象となる個々の「臓器」を表す構造体です。Name は文字列、Weight は先ほど定義した Grams 型です。

  • Organs 型と Len(), Swap() メソッド: type Organs []*Organ は、Organ 構造体へのポインタのスライスを Organs という新しい型として定義しています。 func (s Organs) Len() int { return len(s) } は、スライスの長さを返します。 func (s Organs) Swap(i, j int) { s[i], s[j] = s[j], s[i] } は、スライス内の2つの要素を交換します。 これらのメソッドの実装により、Organs 型は sort.InterfaceLen()Swap() の要件を満たします。

  • ByName 構造体と Less() メソッド: type ByName struct{ Organs } は、Organs 型を埋め込んだ新しい構造体 ByName を定義しています。Organs を埋め込むことで、ByNameOrgans が持つ Len()Swap() メソッドを自動的に「継承」します。 func (s ByName) Less(i, j int) bool { return s.Organs[i].Name < s.Organs[j].Name } は、sort.InterfaceLess() メソッドを実装しています。このメソッドは、Organs スライス内の i 番目の要素の Namej 番目の要素の Name よりも辞書順で小さい場合に true を返します。これにより、ByName 型は sort.Interface の全ての要件を満たし、名前順でのソートが可能になります。

  • ByWeight 構造体と Less() メソッド: type ByWeight struct{ Organs } も同様に Organs を埋め込んでいます。 func (s ByWeight) Less(i, j int) bool { return s.Organs[i].Weight < s.Organs[j].Weight } は、Weight フィールドに基づいて比較を行います。これにより、ByWeight 型は重さ順でのソートを可能にします。

  • ExampleInterface() 関数: この関数は、上記の型定義とメソッド実装を使って実際にソートを行う例です。 sort.Sort(ByWeight{s}) は、Organs スライス sByWeight 型にラップして sort.Sort 関数に渡すことで、重さ順にソートを実行します。 sort.Sort(ByName{s}) も同様に、名前順にソートを実行します。 printOrgans ヘルパー関数は、ソートされた結果を整形して出力するために使用されます。 // Output: コメントブロックは、Goのテストシステムがこの例を実行した際に期待する出力を定義しており、実際の出力と一致するかどうかを検証します。

example_reverse_test.go の解説

このファイルは、既存の sort.Interface の実装を逆順にソートするための汎用的なアダプターを示しています。

  • Reverse 構造体: type Reverse struct { sort.Interface } は、sort.Interface を埋め込んだ構造体です。これにより、Reverse 型は、埋め込まれた sort.Interface が持つ Len()Swap() メソッドを自動的に利用できます。

  • Less(i, j int) bool メソッド: func (r Reverse) Less(i, j int) bool { return r.Interface.Less(j, i) } がこのアダプターの核心です。通常の Less(i, j)ij より小さい場合に true を返しますが、この実装では r.Interface.Less(j, i) を呼び出しています。これは、元の Less メソッドの引数を逆にして呼び出すことで、比較結果を反転させ、結果として逆順ソートを実現します。

  • ExampleInterface_reverse() 関数: この関数は、Reverse アダプターの使用例です。 s := []int{5, 2, 6, 3, 1, 4} は、ソートされていない整数のスライスです。 sort.Sort(Reverse{sort.IntSlice(s)}) は、sort.IntSlice(s) を使用して []intsort.Interface に適合させ、それをさらに Reverse 型でラップして sort.Sort 関数に渡しています。これにより、整数のスライスが降順にソートされます。 // Output: コメントブロックは、期待される出力 [6 5 4 3 2 1] を示しています。

これらの例は、Goのインターフェースと埋め込みの強力な組み合わせが、柔軟で再利用可能なソートロジックをどのように構築できるかを示しています。

関連リンク

参考にした情報源リンク