8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHP/PythonのジェネレータをGolang/Rubyで実現

Last updated at Posted at 2017-06-04

Golang(Go)/Rubyでジェネレータを実現する

こちらでPHPとPython3でのジェネレータを調べていた際に、GolangとRubyではどのように実現できるのか調査をしてみた。

PHP、Python3、Golang、Ruby2.4でジェネレータ

numbers.txtを読み込み、以下の出力結果となるような簡単なジェネレータを用意した。

numbers.txt
zero
one
two
three
four
five
出力結果
0:zero
1:one
2:two
3:three
4:four
5:five

PHPの場合

PHP5.6で検証した。yield fromは今回は利用していないので、PHP7は選択しなかった。

<?php

function my_generator($name)
{
    $file = fopen($name, "r");

    if ($file) {
        while ($line = fgets($file)) {
            yield $line;
        }
    }
    fclose($file);
}

$g = my_generator("numbers.txt");
foreach ($g as $k => $v) {
    print($k. ":". $v);
}

Pythonの場合

PHPと同等な方法で実現できる。Pythonのバージョンは3.5を利用した。

def my_generator(name):
    with open(name) as lines:
        for line in lines:
           yield line

g = my_generator("numbers.txt")
for k, v in enumerate(g):
    print("%s:%s" % (k, v), end="")

Golangの場合

GolangにはPHPやPythonのyieldに相当した機能がない(参考はこちら)。そのため、ゴルーチン・チャンネルまたクロージャを利用した方法で類似のことを実現した。

ゴルーチン・チャンネルを利用した方法

numbers.txtの行数が膨大になることを想定するとパフォーマンス面で懸念がある。

package main

import (
    "bufio"
    "fmt"
    "os"
)

func yield(fp *os.File) chan string {
    ch := make(chan string)

    go func() {
        defer close(ch)

        scanner := bufio.NewScanner(fp)
        for scanner.Scan() {
            ch <- scanner.Text()
        }
        if err := scanner.Err(); err != nil {
            panic(err)
        }
    } ()

    return ch
}

func main() {
    fp, err := os.Open("numbers.txt")
    if err != nil {
        panic(err)
    }
    defer fp.Close()

    i := 0
    for s := range(yield(fp)) {
        fmt.Printf("%d:%s\n", i, s)
        i++
    }
}

クロージャを利用した方法

sync.Mutexで同期をとる方法もありかもしれない。

package main

import (
    "bufio"
    "fmt"
    "os"
)

func yield(fp *os.File) func() (bool, string) {
    scanner := bufio.NewScanner(fp)
    return func() (bool, string) {
        t := scanner.Scan()
        if err := scanner.Err(); err != nil {
            panic(err)
        }
        return t, scanner.Text()
    }
}

func main() {
    fp, err := os.Open("numbers.txt")
    if err != nil {
        panic(err)
    }
    defer fp.Close()

    y := yield(fp)
    for i := 0;;i++ {
        t, s := y()
        if !t { break }
        fmt.Printf("%d:%s\n", i, s)
    }
}

Ruby2.4の場合

Rubyにもyieldがあるが、PHP/Pythonのようなジェネレータではなくブロックを受け取る関数定義となる。PHP/Pythonのジェネレータに相当するような機能にEnumeratorがあり、下記のよう記載する。
※kts_hさんからご提案いただいたコードに修正しました。

def my_generator(name)
  return to_enum(__method__, name) unless block_given?
  IO.foreach(name) do |line|
    yield(line.chomp)
  end
end

g = my_generator('numbers.txt')
g.with_index { |l, i| p '%d:%s' % [i, l] }

ここでは、ブロックがあるとブロックの処理をし、ブロックがないと Enumerator を返すメソッドとして扱うことができる。

8
4
6

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
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?