Thread overview
Y Combinator
May 02, 2013
Salih Dinçer
May 02, 2013
Salih Dinçer
May 02, 2013
Salih Dinçer
May 02, 2013

İlginç bir şey buldum:

auto Y(R, P...) (R delegate(P) delegate(R delegate(P)) lambda)
{
   struct RFunc {R delegate(P) delegate(RFunc) f;}
   auto r = RFunc( (RFunc w) => lambda((P x) => w.f(w)(x)) );
   return r.f(r);
}

/*
* Y Combinator
* ----------------------------------------------------------------------------------
* We're all familiar with the idea of a function as something that takes some
* input value and returns some output value. Say, the function for squaring numbers:
*
*   f(x) = x*x;
*
* The fixed points of a function are any input values for which f(x) is equal to x.
* So, the fixed points of f(x) = x*x are 0 and 1.
*
* Now, we have things called higher-order functions. These are functions that take another
* function as input, or return a function as output, or both.
*
* The fixed point of a higher-order function f is another function p such that f(p) = p.
* It may be more helpful to think in terms of functions actually being executed.
* The previous statement is equivalent to the statement that f(p)(x) = p(x) for all values of x.
*
* Y (the Y combinator) is a special function that returns the fixed points of higher-order
* functions, that is to say:
*
*   f(Y(f)) = Y(f)
*
* Y combinator is commonly use to allow anonymous recursion without assuming your host
* language supports it.
*/

import std.array, std.algorithm;

void main () {
   auto factorial = Y((int delegate(int) self) =>
       (int n) => 0 == n ? 1 : n * self(n - 1));

   auto ackermann = Y((ulong delegate(ulong, ulong) self) =>
       (ulong m, ulong n) => (!m && n)?
           (n+1) : (m && !n)? self(m-1, 1) : self(m-1, self(m, n-1)));

   auto qsort = Y((int[] delegate(int[]) self) =>
       (int[] arr) => arr.length?
           self(arr.filter!(a => a < arr[0]).array)
         ~ arr[0]
         ~ self(arr.filter!(a => a > arr[0]).array) : []);

   assert(factorial(6) == 720);
   assert(ackermann(3, 5) == 253);
   assert(qsort([8, 5, 10, 2, 16, 9, 1, 100, 3])
             == [1, 2, 3, 5, 8, 9, 10, 16, 100]);
}

Bu kodu DMD 2.058 sürümünden itibaren derleyebilirsiniz. Çünkü lambda işaretimiz (=>) var. Üç örnek verilmiş ama henüz anlayamadım...:)

Factorial basit olsa da, Erdem ve Ali hocayla birlikte bir zamanlar tartıştığımız qsort örneğini karşılaştırabiliriz...

Alıntı:

>
> T[] qSort_partition3(T)(T[] asılElemanlar)
> {
>     /* Asıl algoritma bu. */
>     void qSort_partition3_iç(T)(T[] elemanlar)
>     {
>         if (elemanlar.length >= 2) {
>             auto parçalar = partition3(elemanlar, elemanlar[$ / 2]);
>             qSort_partition3_iç(parçalar[0]);
>             qSort_partition3_iç(parçalar[2]);
>         }
>     }
>
>     T[] sonuç = asılElemanlar.dup;
>
>     qSort_partition3_iç(sonuç);
>
>     return sonuç;
> }
>
> unittest
> {
>     assert(qSort_partition3([8, 5, 10, 2, 16, 9, 1, 100, 3])
>                          == [1, 2, 3, 5, 8, 9, 10, 16, 100]);
> }
> ```

>

Çünkü bu daha basit ve partition3'ün ne yaptığını bilince anlaması kolay. Aslında Y Combinator'de de özyineleme yapılıyor. Ancak hızlı sıralama yapılırken, std.algorithm.filter ile aralık oluşturulup std.array.array ile diziye dönüştürülmekte.

Aşağıya bunu tekrar naklettim ve bu haliyle hala karışık görülmekte...:)

auto qsort = Y((int[] delegate(int[]) self) =>
(int[] arr) => arr.length?
self(arr.filter!(a => a < arr[0]).array)
~ arr[0]
~ self(arr.filter!(a => a > arr[0]).array) : []);

assert(qsort([8, 5, 10, 2, 16, 9, 1, 100, 3])
== [1, 2, 3, 5, 8, 9, 10, 16, 100]);


Sevgiler, saygılar...

-- 
[ Bu gönderi, <http://ddili.org/forum>'dan dönüştürülmüştür. ]
May 02, 2013

Bu arada Ackermann işlevi A(m, n) (http://en.wikipedia.org/wiki/Ackermann_function) çok basit ama ilginçmiş...

Örneğin m = 4 iken, n sıfırdan büyük olmak kaydıyla ilk sayı 65533 ama sonraki sayı 19729 rakamlı büyük bir sayı...:)

Özyinelemeli kodu ise şöyle:

ulong ackermann(in ulong m, in ulong n) {
   if (m == 0) return n + 1;
   if (n == 0) return ackermann(m - 1, 1);
   return ackermann(m - 1, ackermann(m, n - 1));
}

unittest {
   assert(ackermann(2, 4) == 11);
}

İşlevin bir de BigInt sürümü RosettaCode (http://rosettacode.org/wiki/Ackermann_function#More_efficient_version)'da yer almakta...

http://rosettacode.org/mw/images/math/0/a/e/0ae4053de098cc9554752b190a38bc56.png

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 02, 2013

Şimdi soru şu:

Bu Y Combinator, özyinelemeli işlevleri daha kolay oluşturmamızı mı sağlıyor; yoksa sistem kaynaklarını daha etkin mi kullanıyor? Örneğin Wolfram Alpha'a sorduğumuzda (http://www.wolframalpha.com/input/?i=ackermann%282%2C+4%29), A(2, 4) için en az 44 defa özyineleme yapacağını söylüyor.

Ayrıca orada Ackermann'ı anlamak için 2 güzel graphics generate edilmiş...

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]

May 04, 2013

Tam olarak ne yaptığını anlamak hiç de kolay değil. :)

Şu kadarını görebiliyorum: Y, herhangi bir özyinelemeli işlev tanımlamıyor, özyineleme kavramının kendisini tanımlıyor.

Bir yararı, özyinelemeli işlemi isimsiz olarak (lambda function olarak) tanımlayabiliyor. Dikkat edersek, gösterdiğin ilk örnekte factorial, ackermann, veya qsort isminde işlev yok. Onlar aslında birer değişken. O değişkenler belirli bir türden temsilci olduklarından sonradan işlev gibi çağrılıyorlar ama kendileri geleneksel işlev söz dizimi ile tanımlanmış değiller. (Özyilenemeli işlev olarak açıkça yazmak istediğimizde ise 'int factorial(int n)' gibi isim vererek yazmak zorundayız.)

Bu gibi örneklerde bir yararını görebileceğimizi sanmıyorum ve tabii ki daha okunaksız :). Aklımızda bulunsun...

Ali

--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]