コンテンツにスキップ

Go/iter.Seq版エラトステネスの篩

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go

iter.Seqの応用 - エラトステネスの篩を実装する

[編集]

Go 1.23で導入されたiterパッケージは、イテレータの新しい標準的な仕組みを提供します。その中でもiter.Seq[T]型は、遅延評価やストリーミング処理を可能にする強力な抽象化です。この記事では、古典的なアルゴリズムである「エラトステネスの篩」をiter.Seqを使って実装し、その応用例を紹介します。

iter.Seqとは

[編集]

iter.Seq[T]は、型Tの値のシーケンスを表すイテレータです。以下のような関数型として定義されています:

type Seq[V any] func(yield func(V) bool) bool

この型は、yield関数を呼び出すことで値を一つずつ生成し、yieldfalseを返した場合に処理を停止します。これにより、必要な分だけ値を生成する遅延評価が可能になります。

エラトステネスの篩の実装

[編集]

エラトステネスの篩は、素数を効率的に見つけるアルゴリズムです。基本的な考え方は以下の通りです:

  1. 2から始まる自然数の列を用意する
  2. 最初の数(素数)を取り出す
  3. その数の倍数をすべて除去する
  4. 残った数から次の素数を取り出し、2-3を繰り返す

無限数列の生成

[編集]

まず、2から始まる無限の数列を生成するジェネレータを作成します:

func generate() iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 2; yield(i); i++ {
        }
    }
}

この関数はiter.Seq[int]を返し、2から始まって無限に続く数列を生成します。yield(i)falseを返すまで値を生成し続けます。

フィルタリング

[編集]

次に、特定の素数で割り切れない数だけを通すフィルタを作成します:

func filter(inputSeq iter.Seq[int], prime int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := range inputSeq {
            if i%prime != 0 {
                if !yield(i) {
                    return
                }
            }
        }
    }
}

この関数は、入力シーケンスinputSeqから値を受け取り、primeで割り切れない値のみを新しいシーケンスとして返します。

篩アルゴリズムの実装

[編集]

最後に、エラトステネスの篩のメインロジックを実装します:

func sieve() iter.Seq[int] {
    return func(yield func(int) bool) {
        inputSeq := generate() // 最初の入力は無限数列
        for {
            next, stop := iter.Pull(inputSeq) // プル方式に変換
            {
                defer stop() // イテレータを停止
                // 次の素数を取得
                prime, ok := next()
                if !ok {
                    break
                }
                // 素数を出力
                if !yield(prime) {
                    break
                }
                // 新しいフィルタリングされたシーケンスを作成
                inputSeq = filter(inputSeq, prime)
            }
        }
    }
}

このアルゴリズムの重要なポイントは以下の通りです:

  1. iter.Pull()の使用: iter.Pull()を使ってプッシュ方式のイテレータをプル方式に変換し、必要な時に値を取得できるようにします
  2. 動的フィルタリング: 新しい素数が見つかるたびに、その素数の倍数を除去する新しいフィルタを適用します
  3. 遅延評価: 実際に値が要求されたときにのみ計算が実行されます

実行例

[編集]

実装したエラトステネスの篩を使って、100以下の素数を出力してみましょう:

func main() {
    for prime := range sieve() {
        if prime > 100 {
            break
        }
        fmt.Print(prime, " ")
    }
}

実行結果:

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

iter.Seqの利点

[編集]

この実装から、iter.Seqの以下の利点が見えてきます:

  1. メモリ効率
    無限数列を扱っているにも関わらず、必要な分だけメモリを使用します。全ての値を事前に配列に保存する必要がありません。
  2. 組み合わせ可能性
    generate()filter()sieve()といった各部品を独立して実装し、組み合わせることができます。これにより、コードの再利用性と保守性が向上します。
  3. 遅延評価
    値は実際に必要になったときにのみ計算されます。これにより、大きなデータセットや無限シーケンスを効率的に扱えます。
  4. 自然な制御フロー
    for rangeループを使った自然な記述が可能で、従来のスライスやチャネルと同じような感覚で使用できます。

まとめ

[編集]

iter.Seqを使ったエラトステネスの篩の実装を通じて、Go 1.23の新しいイテレータ機能の強力さを確認できました。遅延評価、メモリ効率、そして組み合わせ可能性といった特徴により、従来は難しかった無限シーケンスの処理や、大規模データの効率的な処理が可能になります。

この機能を活用することで、よりエレガントで効率的なGoプログラムを書くことができるでしょう。iter.Seqは、関数型プログラミングのパラダイムをGoに自然な形で取り入れる重要な機能と言えます。

附録

[編集]
全体の実装
package main

import (
	"fmt"
	"iter"
)

// 2, 3, 4, ...という無限の数列を生成するジェネレータ関数
func generate() iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := 2; yield(i); i++ {

		}
	}
}

// フィルタジェネレータ関数
// 'inputSeq'から値を受け取り、'prime'で割り切れない値を返す
func filter(inputSeq iter.Seq[int], prime int) iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := range inputSeq {
			if i%prime != 0 {
				if !yield(i) {
					return
				}
			}
		}
	}
}

// 素数篩 (Sieve) のジェネレータ関数
// 素数を生成する
func sieve() iter.Seq[int] {
	return func(yield func(int) bool) {
		inputSeq := generate() // 最初の入力は無限数列のシーケンス

		for {
			next, stop := iter.Pull(inputSeq) // イテレータ関数をpull方式に変換する
			{
				defer stop() // イテレータを止める
				// 次の素数を取得
				prime, ok := next()
				if !ok {
					break
				}

				// 素数を出力
				if !yield(prime) {
					break
				}

				// 取得した素数でフィルタリングする新しいシーケンスを作成
				inputSeq = filter(inputSeq, prime)
			}
		}
	}
}

// 素数を100まで出力してテストする
func main() {
	for prime := range sieve() {
		if prime > 100 {
			break
		}
		fmt.Print(prime, " ")
	}
}