【C#】F#のSeq.unfold関数をC#で実装してみた。
- 2012.03.03 Saturday
- 23:13
【関連】
【F#】Seq.unfoldは理解するのは難しいが・・・分かると簡単??
http://pro.art55.jp/?eid=1303926
【F#】Seq.unfoldは理解するのは難しいが・・・分かると簡単??その2
http://pro.art55.jp/?eid=1303927
【C#】F#のSeq.unfold関数をC#で実装してみた。
http://pro.art55.jp/?eid=1303928
【C#】F#のSeq.unfold関数をC#で実装してみた。その2
http://pro.art55.jp/?eid=1303929
-----------------------------------------------------------------------------
F#のSeq.unfold関数ですが、C#でそんなメソッドは見たこと事ありません。なのでC#で実装したら、どういうコードになるんだろうって思っちゃいますよね。なので書いてみました。
public static class EnumerableUtils
{
public static IEnumerable<TResult> Unfold<TState, TResult>(TState currentState, Func<TState, Tuple<TResult, TState>> getCurrentResultAndNextSeed)
{
if (getCurrentResultAndNextSeed == null)
{
throw new ArgumentNullException("getCurrentResultAndNextSeed");
}
TState state = currentState;
while (true)
{
Tuple<TResult, TState> tuple = getCurrentResultAndNextSeed(state);
if (tuple != null)
{
yield return tuple.Item1;
state = tuple.Item2;
}
else
{
yield break;
}
}
}
}
Sytem.Tuple型をF#のタプルに見立てて書いてみたんですが・・・うーんどうなんでしょう。使いづらそうですが、とりあえず使ってみます。
static void Main()
{
var result = EnumerableUtils.Unfold(Tuple.Create(0, 1), seed => Tuple.Create(seed.Item1, Tuple.Create(seed.Item2, seed.Item1 + seed.Item2)))
.TakeWhile(number => number <= 100)
.ToList();
foreach (var i in result)
{
Console.WriteLine(i);
}
}
やっぱり・・・使いづらいですね。引数にタプルを渡すとか・・・正直しっくりこない。型が複雑すぎて記述するのが大変でした。型推論ならぬ型断定を自分でしなければならないのがつらい(笑)
ということで、Tupleは使う側には見えないようにしてみました。
public static class EnumerableUtils
{
public static IEnumerable<TResult> Unfold<TState, TResult>(TState currentState, Func<TState, TResult> getCurrentResult, Func<TState, TState> getNextState)
{
return Unfold(currentState, getCurrentResult, getNextState, s => true);
}
public static IEnumerable<TResult> Unfold<TState, TResult>(TState currentState, Func<TState, TResult> getCurrentResult, Func<TState, TState> getNextState, Func<TState, bool> canNext)
{
if (getCurrentResult == null)
{
throw new ArgumentNullException("getCurrentResult");
}
if (getNextState == null)
{
throw new ArgumentNullException("getNextState");
}
return Unfold(currentState, state => canNext(state)
? Tuple.Create(getCurrentResult(state), getNextState(state))
: null);
}
...
}
}
Func<TState, Tuple<TResult, TState>> getCurrentResultAndNextSeed
というデリゲートのを3つのデリゲートに分解しました。
Func<TState, TResult> getCurrentResult カレントの値を求める関数
Func<TState, TState> getNextState ネクストの状態を求める関数
Func<TState, bool> canNext ネクストの状態へ移行できるかどうは判定する関数
static void Main()
{
var result = EnumerableUtils.Unfold(Tuple.Create(0, 1), currentState => currentState.Item1, currentState => Tuple.Create(currentState.Item2, currentState.Item1 + currentState.Item2), tuple => tuple.Item1 <= 100)
.TakeWhile(number => number <= 100)
.ToList();
foreach (var i in result)
{
Console.WriteLine(i);
}
}
呼び出し元が多少呼び出しやすくなりました・・・。
で、まあせっかく分解したのでコードをきれいにしていくと。
つまり、unfoldがやってるのはこういうこと。
public static IEnumerable<TResult> Unfold<TState, TResult>(TState currentState, Func<TState, TResult> getCurrentResult, Func<TState, TState> getNextState, Func<TState, bool> canNext)
{
for (TState state = currentState; canNext(state); state = getNextState(state))
{
yield return getCurrentResult(state);
}
}
for文で記述すると、unfoldってこういうことなのかー!?って新しい発見。
(このメソッドには欠点があります http://pro.art55.jp/?eid=1303929)
------------------------------------------------
追記)
using System;
using System.Collections;
using System.Collections.Generic;
namespace Art55
{
class Program
{
static void Main()
{
foreach (var i in new FibonacciCollction(100))
{
Console.WriteLine(i);
}
}
class FibonacciCollction : IEnumerable<int>
{
public FibonacciCollction(int max)
{
_max = max;
}
private readonly int _max;
public IEnumerator<int> GetEnumerator()
{
return EnumerableUtils.Unfold(FibonacciNumberPair.GetStartPair(),
currentState => currentState.Fn0,
currentState => FibonacciNumberPair.Create(currentState.Fn1, currentState.Fn0 + currentState.Fn1),
tuple => tuple.Fn0 <= _max).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
class FibonacciNumberPair : IEquatable<FibonacciNumberPair>
{
private FibonacciNumberPair(int fn0, int fn1)
{
_tuple = Tuple.Create(fn0, fn1);
}
public static FibonacciNumberPair Create(int fn0, int fn1)
{
return new FibonacciNumberPair(fn0, fn1);
}
private static readonly FibonacciNumberPair StartPair = new FibonacciNumberPair(0, 1);
public static FibonacciNumberPair GetStartPair()
{
return StartPair;
}
private readonly Tuple<int, int> _tuple;
public int Fn0 { get { return _tuple.Item1; } }
public int Fn1 { get { return _tuple.Item2; } }
public bool Equals(FibonacciNumberPair other)
{
if (other == null)
return false;
return other._tuple == _tuple;
}
}
}
}
public static class EnumerableUtils
{
public static IEnumerable<TResult> Unfold<TState, TResult>(TState currentState, Func<TState, TResult> getCurrentResult, Func<TState, TState> getNextState)
{
return Unfold(currentState, getCurrentResult, getNextState, s => true);
}
public static IEnumerable<TResult> Unfold<TState, TResult>(TState currentState, Func<TState, TResult> getCurrentResult, Func<TState, TState> getNextState, Func<TState, bool> canNext)
{
if (getCurrentResult == null)
{
throw new ArgumentNullException("getCurrentResult");
}
if (getNextState == null)
{
throw new ArgumentNullException("getNextState");
}
for (TState state = currentState; canNext(state); state = getNextState(state))
{
yield return getCurrentResult(state);
}
}
}
}
--------------------
(追記)
シンプルなのがいいのかなー。
using System;
using System.Collections.Generic;
namespace Art55
{
internal class Program
{
private static void Main()
{
foreach (int number in FibonacciSeq(100))
{
Console.WriteLine(number);
}
}
public static IEnumerable<int> FibonacciSeq(int maxNumber)
{
for (int i = 0, j = 1, tmp; i <= maxNumber; tmp = j, j = i + j, i = tmp)
{
yield return i;
}
}
}
}
-----------------------------
(追記)
書き方いろいろあるよね。
using System;
using System.Collections.Generic;
namespace Art55
{
internal class Program
{
private static void Main()
{
foreach (int number in FibonacciSeq(100))
{
Console.WriteLine(number);
}
}
public static IEnumerable<int> FibonacciSeq(int maxNumber)
{
for (var pair = new { F0 = 0, F1 = 1}; pair.F0 <= maxNumber; pair = new { F0 = pair.F1, F1 = pair.F0 + pair.F1 })
{
yield return pair.F0;
}
}
}
}