Menu
Coddy logo textTech

sort.Interface

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 74/107。

sort パッケージは、インターフェースベースの設計のもう一つの優れた例を提供します。カスタムコレクションをソートするには、独自の型が sort.Interface を実装する必要があります:

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

これら3つのメソッドは、ソートアルゴリズムに必要なすべての情報(コレクションの長さ、要素を比較する方法、および要素を入れ替える方法)を提供します。カスタム構造体のスライスをソート可能にする方法は次のとおりです。

type Person struct {
    Name string
    Age  int
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func main() {
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Carol", 35},
    }
    
    sort.Sort(ByAge(people))
    fmt.Println(people)
    // [{Bob 25} {Alice 30} {Carol 35}]
}

重要なポイントは、スライスに基づいて名前付き型(ByAge)を作成することです。これにより、同じデータに対して異なるソート動作を定義できるようになります。代わりに、アルファベット順にソートするために、異なる Less 実装を持つ ByName を作成することもできます。

作成した型が sort.Interface を満たせば、自動的に sort.Sort()sort.Reverse()、および sort.IsSorted() で動作するようになります。

challenge icon

チャレンジ

簡単

Goのsort.Interfaceの強力さを示す、ソート可能な商品在庫システムを構築しましょう!Product型を作成し、複数のソート戦略を実装することで、同じコレクションを異なる方法でソートできるようにします。

コードは2つのファイルに分けて構成します:

  • product.go: 商品型とソートの実装を定義します。

    3つのフィールドを持つProduct構造体を作成します:Name (string)、Price (float64)、Quantity (int)。

    []Productに基づいた2つの名前付き型を作成します:

    • ByPrice - 商品を価格の昇順でソートするため
    • ByQuantity - 商品を在庫数の降順(在庫数が多い順)でソートするため

    各型は、sort.Interfaceで要求される3つのメソッドを実装する必要があります:Len()Less(i, j int)、およびSwap(i, j int)Lessメソッドが各型のソート順序を決定します。

  • main.go: 商品在庫を構築し、ソートします。

    ソートモード(priceまたはquantity)を読み取り、次に商品数、続いて商品の詳細を読み取ります。各商品は、名前、価格、在庫数の3行で提供されます。

    商品のスライスを作成し、モードに基づいて適切なソート型を使用してソートし、各商品を次の形式で出力します:

    [Name]: $[Price] (x[Quantity])

    価格は小数点以下2桁まで表示してください。

以下の入力が提供されます:

  • 1行目:ソートモード(priceまたはquantity
  • 2行目:商品の数
  • 続く行:商品の詳細(名前、価格、在庫数 - 1商品につき3行)

例えば、以下が与えられた場合:

price
3
Laptop
999.99
5
Mouse
29.99
50
Keyboard
79.99
25

出力は以下のようになります:

Mouse: $29.99 (x50)
Keyboard: $79.99 (x25)
Laptop: $999.99 (x5)

また、以下が与えられた場合:

quantity
3
Laptop
999.99
5
Mouse
29.99
50
Keyboard
79.99
25

出力は以下のようになります:

Mouse: $29.99 (x50)
Keyboard: $79.99 (x25)
Laptop: $999.99 (x5)

異なる名前付き型を使用するだけで、同じ商品データを異なる方法でソートできることに注目してください。型がsort.Interfaceを満たせば、標準ライブラリのsort.Sort()とシームレスに連携します。

チートシート

sort パッケージでは、型をソート可能にするために sort.Interface を実装する必要があります。

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

カスタムスライスをソート可能にするには、名前付き型を作成し、必要な3つのメソッドを実装します。

type Person struct {
    Name string
    Age  int
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

// Sort the collection
sort.Sort(ByAge(people))

同じスライスに対して複数の名前付き型を作成し、異なるソート動作を実装することができます。各型は、ソート順を決定するために独自の Less メソッドを定義します。

型が sort.Interface を満たすと、sort.Sort()sort.Reverse()、および sort.IsSorted() で使用できるようになります。

自分で試してみよう

package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
	"strconv"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// ソートモードを読み込む
	var mode string
	fmt.Fscanln(reader, &mode)

	// 商品の数を読み込む
	var count int
	fmt.Fscanln(reader, &count)

	// 商品を読み込む
	products := make([]Product, count)
	for i := 0; i < count; i++ {
		name, _ := reader.ReadString('\n')
		name = name[:len(name)-1] // 改行を削除

		priceStr, _ := reader.ReadString('\n')
		priceStr = priceStr[:len(priceStr)-1]
		price, _ := strconv.ParseFloat(priceStr, 64)

		qtyStr, _ := reader.ReadString('\n')
		qtyStr = qtyStr[:len(qtyStr)-1]
		quantity, _ := strconv.Atoi(qtyStr)

		products[i] = Product{Name: name, Price: price, Quantity: quantity}
	}

	// TODO: モードに基づいて商品をソートする
	// モードが "price" の場合は ByPrice 型を使用する
	// モードが "quantity" の場合は ByQuantity 型を使用する
	// 適切な型で sort.Sort() を使用する

	// TODO: 各商品を以下の形式で出力する:
	// [Name]: $[Price] (x[Quantity])
	// 価格のフォーマットには fmt.Printf と %.2f を使用する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン