Achiral に Scan とか Pairwise とか条件変化可能な TakeWhile を入れてみた

以下の問題を解きながら Achiral を弄っていると,ついつい関係ないところまで弄っちゃって,あちこち breaking change ができてしまいました.なのでマイナーバージョンをひとつあげてみるテスト.いやまあバージョンとか適当なんですが.

プログラミング言語が与える影響

Kazzzz の日記の"言語が思考を規定する" というエントリが面白いので。確かに、プログラミング言語はそれが想定しているモデルがあるので問題解決に与える影響は少なくないと思います。

それをちょっと考えるのに一つの問題を設定して考えます。問題設定はnを1から初めてその2乗を足していき、和が2000を初めて超えたとき和はいくつになるかという問題を考えます。通常の、C++ 言語で考えれば以下のようになるでしょうか。

#include <iostream>
using namespace std;
int main() {
	int sum = 0;
	int n = 1;
	while(sum > 2000) {
		sum += n * n;
		n++;
	}

	cout << sum;
	return 0;
}

Code 1: C++ Version

これを、F# で記述すると以下のようになります。

open System
let square x = x * x;;
let rec squareAdd sum current maximum = 
	if (sum + square(current)) > maximum then
		(sum + square(current))
	else squareAdd (sum + square(current)) (current + 1) maximum;;
let main = System.Console.WriteLine(squareAdd 0 1 2000);;

Code 2: F# Version

コードを考えたときには、F# の方が考えやすかったです。というのも、F# の場合、ループではなく再帰で実現されているためです。再帰的な問題というのは再帰的な定義が書きやすい言語の方が解がイメージしやすいです。言語が変われば解を導く方法論も変わり、結果的にそれは成果物にも反映されます。異なる言語を学べばいろいろな解が得られるようになります。ポイントは異なる思想のもとに作られた言語を学んでいる方が得られるものが多いことでしょうか。

結局 C# 版はこんなコードになりました.

using System;
using System.Collections.Generic;
using System.Linq;

using Achiral;
using Achiral.Extension;

static class Program
{
    static void Main(string[] args)
    {
        var seq =
            1.UpToInfinity()                                 // [1, 2, 3, 4, ...]
             .Select(x => x * x)                             // [1, 4, 9, 16, ...]
             .Scan((sum, x) => sum + x)                      // [1, 5, 14, 30, ...]
             .TakeWhile((sum, _, __) => sum <= 2000,         // 2000 を超えるまで
                        (_, __, subIndex) => subIndex < 1);  // 次に,最初の要素まで

        Console.WriteLine("answer: {0}", seq.Last());
    }
}

追記

水島さんによる Scala 版のコードを読むまで,SkipWhile の存在をすっかり忘れてました.答えを出すだけならこっちの方が楽です.

using System;
using System.Collections.Generic;
using System.Linq;

using Achiral;
using Achiral.Extension;

static class Program
{
    static void Main(string[] args)
    {
        var answer =
            1.UpToInfinity()                // [1, 2, 3, 4, ...]
             .Select(x => x * x)            // [1, 4, 9, 16, ...]
             .Scan((sum, x) => sum + x)     // [1, 5, 14, 30, ...]
             .SkipWhile(sum => sum <= 2000) // 2000 を超えるまでは捨てる
             .First();                      // 最初の要素

        Console.WriteLine("answer: {0}", answer);
    }
}

追記2

nsharp さんのコメントにあるように,SkipWhile + First の組み合わせは,そもそも Enumerable.First の述語を取る版で OK なのでした……

using System;
using System.Collections.Generic;
using System.Linq;

using Achiral;
using Achiral.Extension;

static class Program
{
    static void Main(string[] args)
    {
        var answer =
            1.UpToInfinity()                // [1, 2, 3, 4, ...]
             .Select(x => x * x)            // [1, 4, 9, 16, ...]
             .Scan((sum, x) => sum + x)     // [1, 5, 14, 30, ...]
             .First(sum => sum > 2000);     // 2000 を超えた最初の要素

        Console.WriteLine("answer: {0}", answer);
    }
}