secretbase.log

.NET/C#/Pythonなど

C# , Python , Ruby の yield の違い

毎週木曜日に社内でC#の勉強会をやっていて、そこで yield なる文がなにをやっているかわからないという話題に。そういえば Pythonフィボナッチ数列 を取得するときに yield 使っていたっけ。同じかな?って思ったので試してみました。

結論

  • C#(イテレータブロック) と、Python(ジェネレータ)は非常によく似ている
  • Ruby異なる(主従が異なる感じ)。

C#

C#の場合、yieldはイテレータブロック内で列挙子オブジェクトに値を生成する場合に、 yield return 式として使用します。
yield break として反復処理の終了を通知することもできるらしい。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;      // f.Take で LINQ使用

namespace Fib
{
    class Fibonacci : IEnumerable<int> {
        int x = 0;
        int y = 1;
        public IEnumerator<int> GetEnumerator() {
            // 無限に求める
            while (true) {
                yield return x;
                int tmp_x = x;
                x = y;
                y = tmp_x + y;
            }
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return this.GetEnumerator();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Fibonacci f = new Fibonacci();
            // f(0)..F(9)までを列挙する
            foreach (var i in f.Take(10)) {
                Console.WriteLine(i.ToString());
            }
            Console.ReadLine();
        }
    }
}

Python

Python の場合、ジェネレータ関数内において データを返す場合 yield を使います。
こんな感じ。

#!/usr/bin/env python
# coding:utf-8

#  ジェネレータ関数fib() の nextメソッド
#  (ジェネレータ関数では__init__メソッドと
#  nextメソッドが自動で定義される)が呼び出されると
#  yield文までが実行されて、yieldで指定されている
#  xの値が戻り値として返される。
#
#    以降、nextメソッドが呼び出されると、yieldの直後の文
#  から処理を再開し、次のyield文までが実行されて、
#  yieldで指定されているxの値が戻り値として返される
#
#  フィボナッチ数列のような無限回の繰り返し処理を表現するのに都合が良い

def fib():
    x, y = 0, 1
    while True:
        yield x
        x, y = y, x + y

z = fib()
for i in range(10):
    print z.next()

Ruby

Rubyは初めて書いてみました。
Rubyだと yield の挙動が異なるようです。
うまく書けなかったので、ideone に書いて呟いてみました。

光の速さで猛者たちからリプライが...!

ruby の yield を使ったサンプル*1

rubyの場合は、C#Pythonと違い、渡されたブロックに値を送り込むというものです。
主従が異なるので、rubyは"逆"と覚えてください。

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

def fib(n)
  x,y = 0,1
  n.times do 
    yield x 
    x, y = y, x+y
  end
end

# rubyのyieldは渡されたblockに値を送り込むためのもの。
fib(10){|n| puts n}

謝辞

Replyを頂いた @さん @さん @さん @ さん、ありがとうございます!

*1:trmmyさんのコードが答えそのままでした