Tembel listeleri, tercihen iyi bilmediğiniz bir dilde uygulayın [kapalı]


21

Bu, öğrenmek istediğiniz anlamdaki programlama dilinde daha akıcı olmak için güzel bir alıştırmadır, ancak yalnızca hafifçe canlandırın. Bu, nesnelerle çalışmayı, kapakları kullanmayı veya taklit etmeyi ve tip sistemini genişletmeyi içerir.

Göreviniz tembel listeleri yönetmek için kod yazmak, ardından Fibonacci sayıları oluşturmak için bu algoritmayı uygulamak için kullanmaktır:

Kod örnekleri Haskell'de

let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
 in take 40 fibs

Sonuç:

[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986]

Tembel liste uygulamanızın aşağıdaki kurallara uyması gerekir:

  • Bir Liste düğümü üç şeyden biridir:
    • Nil - Boş liste.
      []
    • Eksileri - Kalan öğelerin bir listesiyle eşleştirilmiş tek bir öğe:
      1 : [2,3,4,5]
      ( :Haskell'deki eksileri operatörüdür)
    • Thunk - Gerektiğinde bir Liste düğümü üreten ertelenmiş bir hesaplama.
  • Aşağıdaki işlemleri desteklemektedir:
    • nil - Boş bir liste oluşturun.
    • cons - Bir cons hücresi oluşturun.
    • thunk - Argüman almayan ve bir Nil veya Eksileri döndüren bir işlev verilen bir Thunk oluşturun.
    • force - List düğümü verildiğinde:
      • Bir Nil veya Eksileri ise, sadece iade edin.
      • Thunk ise, bir Nil veya Eksileri için işlevini çağırın. Thunk'u Nil veya Cons ile değiştirin ve iade edin.
        Not: Thunk'u zorunlu değeri ile değiştirmek “tembel” tanımının önemli bir parçasıdır . Bu adım atlanırsa, yukarıdaki Fibonacci algoritması çok yavaş olacaktır.
    • boş - Bir Liste düğümünün Nil olup olmadığını görün (zorladıktan sonra).
    • head (aka "car") - Bir listenin ilk maddesini al (veya eğer Nil ise uygun bir yere at).
    • tail (aka "cdr") - Bir listenin başından sonraki öğeleri al (veya eğer Nil ise bir form at.)
    • zipWith - İkili işlev (örn. (+)) ve iki (muhtemelen sonsuz) liste verildiğinde, işlevi listelerin ilgili öğelerine uygular. Örnek:
      zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
    • almak - N sayısı ve (muhtemelen sonsuz) bir liste göz önüne alındığında, listenin ilk N öğesini alın.
    • yazdır - Listedeki tüm öğeleri yazdırın. Uzun veya sınırsız bir liste verildiğinde bu adım adım çalışmalıdır.
  • fibskendisini kendi tanımında kullanır. Tembel özyineleme kurmak biraz zor; Böyle bir şey yapmanız gerekecek:

    • Bir thunk tahsis et fibs. Şimdilik kukla bir durumda bırakın.
    • Bir referansa bağlı olan thunk fonksiyonunu tanımlayın fibs.
    • Thunk işlevini ile güncelleyin.

    Bu sıhhi tesisatı, fixListeden döndürme işlevini çağıran ve kendi dönüş değerine sahip bir işlev tanımlayarak gizlemek isteyebilirsiniz . Kısa bir şekerleme yapmayı düşünün, böylece bu fikir ortaya çıkabilir.

  • Polimorfizm (herhangi bir öğe türü ile çalışma yeteneği) gerekli değildir, ancak bunu kendi dilinizde aptalca bir şekilde yapmanın bir yolunu bulabilecek olup olmadığına bakın.

  • Bellek yönetimi konusunda endişelenmeyin. Çöp toplama özelliğine sahip dillerin bile bir daha asla kullanamayacağınız nesneleri (örneğin arama yığınında) taşıma eğilimi vardır, bu nedenle programınız sonsuz bir listeyi dolaşırken hafızadan sızarsa şaşırmayın.

Dilinizin özelliklerini karşılamak için bu yönergelerden biraz sapmaktan veya tembel listelere alternatif yaklaşımlar bulmaktan çekinmeyin.

Kurallar:

  • İyi bilmediğiniz bir dil seçin. Buna, "onur-sistem" etiketine "ihtiyaç duymayacağım". Ancak, seçmenler hangi dillerde yayın yaptığınızı görmek için geçmişinizi kontrol edebilir.
  • Her şeyi yapmak için dilinizin yerleşik tembel liste desteğini kullanmayın. Önemli veya en azından ilginç bir şey yayınlayın.

    • Haskell hemen hemen dışarıda. Böyle bir şey yapmazsan, bu:

      data List a = IORef (ListNode a)
      data ListNode a = Nil | Cons !a !(List a) | Thunk !(IO (ListNode a))
      

      Not: Haskell'in katı olmayan değerlendirmesi sınırsız değildir, ancak tembel liste uygulamanızın özelliğini doğrudan oradan almaması gerekir. Aslında tembellik gerektirmeyen verimli, tamamen işlevsel bir çözüm görmek ilginç olurdu.

    • Python:

      • İtertol kullanmayın.
      • Jeneratörler gayet iyi, ancak bunları kullanıyorsanız, zorunlu değerleri not almak için bir yol bulmanız gerekecek.

zipWithFarklı uzunluklarda iki liste çağırırken davranış ne olmalıdır ?
balpha

@ Balpha: Haskells davranışını seçtim: Listelerden herhangi biri sıfırsa, nil döndür.
FUZxxl

@ Balpha: Haskell'de, herhangi bir liste öğe tükendiğinde zipWith durur. Dolayısıyla zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3],. Bununla birlikte, yukarıdaki Fibonacci algoritması için önemi yoktur, çünkü her iki zipWith argümanı sonsuz listelerdir.
Joey Adams

Bu zorluğun içinde gizli bir sürpriz vardı: fibsDoğru uygulamak için özel bir şey yapmanız gerekiyor , çünkü kendinize bağlı. Tembel özyinelemeye odaklanmak için soruyu güncelledim. FUZxxl onu kendisi tarafından çözdü.
Joey Adams

Büyük bir liste yazdırırken "adım adım çalışmak" derken ne demek istiyorsunuz?
Lowjacker

Yanıtlar:


6

PostScript

Daha önce PostScript ile oynamıştım , ama özellikle iyi bildiğimi söyleyemem (aslında, sanırım dünyadaki PostScript'i bir el kullanarak gerçekten tanıyan insan sayısını sayabilirsiniz).

Bir speker oluşturmak için kullanılan işlevin başka bir spiker döndürmesine izin verildiğinden; forceSonuç a nilveya a olana kadar değerlendirmeye devam edecektir cons.

Listeler sözlük olarak uygulanır:

<< /type /nil >>

<< /type /cons
   /head someValue
   /tail someList >>

<< /type /thunk
   /func evaluationFunction >>

<< /type /dataThunk
   /func evaluationFunction
   /data someValueToBePassedToTheFunction >>

Kod takip ediyor. Bazı yerleşik operatörlerin üzerine printyazdığımızı unutmayın (özellikle ; daha fazla olup olmadığını kontrol etmedim); Gerçek dünyada kullanımda bunun izlenmesi gerekir. Elbette gerçek dünya kullanımı olmayacak, o yüzden sorun değil.

İşlemlerden önceki yorumlar şu şekilde okunmalıdır:

% before2 before1 before0  <| procedure |>  after1 after0

yani aramadan önce beklenen yığın içeriğini ve aramadan sonra ortaya çıkan yığın içeriğini göstermek. Prosedürlerdeki yorumlar, belirli bir satır yürütüldükten sonra yığının içeriğini gösterir.

% Helper procedure that creates a dictionary with the top two elements as keys
% and the next two elements as values.
%
% value1 value2 key1 key2  <| _twodict |>  << /key1 /value1 /key2 /value2 >>
/_twodict {
    << 5 1 roll    % << value1 value2 key1 key2
    4 2 roll       % << key1 key2 value1 value2
    3 2 roll       % << key1 value1 value2 key2
    exch >>
} def

/nil {
    << /type /nil >>
} def

% item list  <| cons |>  consCell
/cons {
    /head /tail _twodict
    dup /type /cons put
} def

% constructs a thunk from the function, which will be called with no
% arguments to produce the actual list node. It is legal for the function
% to return another thunk.
%
% func  <| thunk |>  lazyList
/thunk {
    /thunk /func /type _twodict
} def

% A dataThunk is like a regular thunk, except that there's an additional
% data object that will be passed to the evaluation function
%
% dataObject func  <| dataThunk |>  lazyList
/dataThunk {
    /data /func _twodict
    dup /type /dataThunk put 
} def

% lazyList  <| force |>  consOrNil
/force {
    dup /type get dup
    /thunk eq
    {
        pop
        dup /func get exec exch copy
        force
        dup /func undef
    }
    {
        /dataThunk eq
        {
            dup dup /data get exch
            /func get exec exch copy
            force
            dup dup /func undef /data undef
        } if
    } ifelse
} def

/empty {
    force
    /type get
    /nil eq
} def

/head {
    force /head get
} def

/tail {
    force /tail get
} def

/print {
    dup empty not
    {
        dup
        head ==
        tail
        print    
    }
    {
        pop
    } ifelse
} def

% sourceList n  <| take |>  resultingList
/take {
    /source /n _twodict
    {
        dup /source get exch    % source data
        /n get 1 sub dup        % source n-1 n-1
        -1 eq
        {
            pop pop nil
        }
        {                       % source n-1
            exch                % n-1 source
            dup head            % n-1 source head
            3 1 roll            % head n-1 source
            tail
            exch take           % head rest
            cons
        } ifelse
    }
    dataThunk
} def

% sourceList1 sourceList2 func  <| zipWith |>  resultList
/zipWith {
    3 1 roll
    2 array astore                  % func [L1 L2] 
    /func /sources _twodict
    {
        dup /sources get aload pop  % data L1 L2
        2 copy empty exch empty or
        {
            pop pop pop nil
        }
        {
            dup head exch tail      % data L1 H2 T2
            3 2 roll
            dup head exch tail      % data H2 T2 H1 T1
            exch                    % data H2 T2 T1 H1
            4 3 roll                % data T2 T1 H1 H2
            5 4 roll /func get      % T2 T1 H1 H2 func
            dup 4 1 roll            % T2 T1 func H1 H2 func
            exec                    % T2 T1 func NEWHEAD
            4 2 roll                % func NEWHEAD T2 T1
            exch 4 3 roll           % NEWHEAD T1 T2 func 
            zipWith cons
        } ifelse
    }
    dataThunk
} def

Bunu Ghostscript'e yükleyin, görüntülenen sayfayı görmezden gelin - biz sadece tercüman ile çalışıyoruz. İşte Fibonacci algoritması:

[balpha@localhost lazylist]$ gs lazylist.ps 
GPL Ghostscript 8.71 (2010-02-10)
Copyright (C) 2010 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
GS> /fibs 0 1 { fibs fibs tail { add } zipWith } thunk cons cons def
GS> fibs 40 take print
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269
2178309
3524578
5702887
9227465
14930352
24157817
39088169
63245986
GS>

İki ek ilginç fonksiyon:

% creates an infinite list that starts with the given value, incrementing
% by one for each additional element
%
% startValue  <| count |>  lazyList
/count {
    {
        dup
        1 add count
        cons
    }
    dataThunk
} def    

% apply the given function to each element of the source list, creating
% a (lazy) list that contains the corresponding results
%
% sourceList function  <| map |> resultList
/map {
    /source /func _twodict
    {
        dup /func get exch
        /source get                 % func source
        dup empty not
        {
            dup head                % func source head
            2 index                 % func source head func
            exec 3 1 roll           % newHead func source
            tail exch map cons
        }
        {
            pop pop nil
        } ifelse
    }
    dataThunk
} def

5'te saymaya başlayın, elde edilen listenin her bir öğesini 3 ile çarpın ve ilk on değeri gösterin:

GS> 5 count { 3 mul } map 10 take print
15
18
21
24
27
30
33
36
39
42

Polimorfizm ile ilgili olarak: PostScript çok iyi yazılmış olsa da, isteğe bağlı türlere sözlük değeri olarak izin veriyor, böylece istediğiniz herhangi bir şeyi atabilirsiniz:

GS> 1337 [ 42 3.14 ] << /key /value >> (Hello world) 3 count
GS<5> cons cons cons cons 10 take print
1337
[42 3.14]
-dict-
(Hello world)
3
4
5
6
7
8
GS>

Örneğin, sayılara karakter dizileri eklemeye çalışmaktan kaynaklanan tip hatalarının sadece değerlendirme zamanında gerçekleşeceğini unutmayın:

GS> (some string) (another string) nil cons cons
GS<1> 13 27 nil cons cons
GS<2> { add } zipWith      % no error yet
GS<1> print
Error: /typecheck in --add--

Şaşırtıcı. (Nasıl) forcememo iade değerleri?
Joey Adams

@JoeyAdams: Gerçekten de öyle. Bir thunk'ı değerlendirdikten sonra, copyoperatör değerlendirilen versiyonun içeriğini orijinalin üzerine kopyalar, üzerine yazar /typeve muhtemelen diğer değerleri belirler. Bir nilveya sahip olana kadar tekrarlı bir şekilde değerlendirdikten sonra cons, (yoluyla undef) da kaldırır /funcve uygulanabilirse /data. Son adım kesinlikle gerekli değildir ( /funcve /datasadece göz ardı
edilirdi

6

C

Ben C 'de tam bir acemiyim, bu kod aslında C de kodladığım ilk gerçek şey. Herhangi bir uyarı olmadan derleniyor ve sistemimde iyi çalışıyor.

Nasıl inşa edilir

İlk önce tarball'ı sunucumdan al . Bir makefile içerir, bu yüzden sadece makeinşa etmek ve daha sonra make runçalıştırmak için çalıştırın. Program daha sonra ilk 93 fibonacci sayısının listesini yazdırır. (94 sayısından sonra, işaretsiz bir 64 bit tam sayı taşması)

açıklama

Programlar çekirdeği dosyadır lazy-list.c. İlgili başlık dosyasında bir yapı tanımlarım list, bu bizim tembel listemizdir. Bu gibi görünüyor:

enum cell_kind {
  NIL,
  CONS,
  THUNK
};

typedef enum cell_kind cell_kind;

typedef long int content_t;

struct list {
  cell_kind kind;
  union {
    struct {
      content_t* head;
      struct list* tail;
    } cons;
    struct {
      struct list* (*thunk)(void*);
      /* If you want to give arguments to the thunk, put them in here */
      void* args;
    } thunk;
  } content;
};

Üye kindbir çeşit etiket. Listeleri bitirip bitmediğimizi ( NIL), zaten değerlendirilmiş bir hücreyi ( CONS) veya bir thunk ( THUNK) işaretler. Sonra bir sendika var. Bu

  • değeri ve kuyruğu olan önceden değerlendirilmiş bir hücre
  • veya gerekirse işlevle ilgili bazı argümanlar içerebilecek bir işlev işaretçisi ve bir yapı içeren bir yığın.

Birliğin içeriği etiketi ile belirtilmiştir. Etiket ise NIL, birliğin içeriği tanımsızdır.

Yukarıdaki tarifnamede belirtilen yardımcı fonksiyonların tanımlanmasıyla, bir genellikle liste tanımını kullanımından, örn. nil()kendiniz bir tane oluşturmak yerine boş bir liste almak için arayabilirsiniz .

Üç en ilginç fonksiyonlardır zipWith, takeve fibonaccis. Ama açıklamak istemiyorum take, çünkü buna çok benzer zipWith. Tembel olarak çalışan tüm fonksiyonlar üç bileşene sahiptir:

  • Bir thunk oluşturan bir sarmalayıcı
  • Bir hücre için hesaplamaları yapan bir işçi
  • Argümanları tutan bir yapı

Durumunda zipWith, bunlar zipWith, __zipWithve __zipArgs. Onları burada başka bir açıklama yapmadan gösteriyorum, orada oldukça açık bir fonksiyon olmalı:

struct __zipArgs {
  content_t* (*f)(content_t*,content_t*);
  list* listA;
  list* listB;
};

static list* __zipWith(void* args_) {
  struct __zipArgs* args = args_;
  list* listA = args->listA;
  list* listB = args->listB;
  list* listC;

  content_t* (*f)(content_t*,content_t*) = args->f;
  content_t* headA = head(listA);
  content_t* headB = head(listB);
  content_t* headC;

  if (NULL == headA || NULL == headB) {
    free(args);
    return nil();
  } else {
    headC = f(headA, headB);
    args->listA = tail(listA);
    args->listB = tail(listB);
    listC = thunk(__zipWith,args);
    return cons(headC,listC);
  }
}

list* zipWith(content_t* (*f)(content_t*,content_t*),list* listA, list* listB) {
  struct __zipArgs* args = malloc(sizeof(struct __zipArgs));
  args->f = f;
  args->listA = listA;
  args->listB = listB;
  return thunk(__zipWith,args);
}

Diğer ilginç fonksiyon fibonaccis(). Sorun şu ki, birinci ve ikinci hücrenin bir göstergesini üçüncü ünite sopasına geçmemiz gerekir, ancak bu hücreleri oluşturmak için sopanın da bir göstergesine ihtiyacımız var. Bu sorunu çözmek için, işaretçiyi NULLilk önce sopayla doldurdum ve oluşturulduktan sonra sopayı değiştirdim. İşte dinleme:

static content_t* __add(content_t* a,content_t* b) {
  content_t* result = malloc(sizeof(content_t));
  *result = *a + *b;
  return result;
}

list* fibonaccis() {
  static content_t one_ = 1;
  static content_t zero_ = 0;
  list* one  = cons(&one_,NULL);
  list* two  = cons(&zero_,one);
  list* core = zipWith(__add,one,two);
  one->content.cons.tail = core;
  return two;

Olası iyileştirmeler

  • Benim çözümüm polimorfizm kullanmıyor. Büyük olasılıkla mümkün olsa da, C becerilerim nasıl kullanılacağını bilmek için yeterli değil. Bunun yerine, bir tip kullandım content_t, ki o neye uyarsa onu değiştirebilir.
  • Birisi listeyi tanımlamasının dışına çıkarabilir ve sadece soyut olarak kullanmak, ancak bunu yapmak kodu daha karmaşık hale getirir.
  • Biri kodumun iyi olmayan kısımlarını iyileştirebilir.

Güzel bir C, özellikle C ilk zamanlayıcı olduğu için. Polimorfizm ile ilgili olarak, tüm içeriğinizi öbek üzerinde paylaştırmaya istekli iseniz void*, türünü kullanabilirsiniz content_t.
Casey

@Casey: Çok teşekkürler. Ben de kullanmayı düşündüm void*, ama bunun tip sistemini çok uzak tutacağını düşündüm. Bu şablon kullanarak mümkün değil mi?
FUZxxl

C'nin şablonları yok, yani C ++, ama evet, C ++ şablonlarını genel yapmak için kullanabilirsiniz.
Casey

Onları nasıl kullanacağımı bilmiyorum. Ama sanırım, bu sadece C'nin tip sistemi açısından sınırlı olduğu. - Bu programı kullanmadan void*ve arkadaş olmadan bile kodlayamadım .
FUZxxl

1
“Üye kindtür bir etiket taşımaktadır.” Sadece diyebiliriz tago kavram için oldukça kabul terim olarak, (örn birliği etiketli , Spineless Tagless G-machine . Öte yandan, "tür" bir başka anlam ifade ediyor. . Haskell bağlam: bir tür tipi Inttür vardır *, []nazik vardır * -> *ve (,)nazik vardır * -> * -> *.
Joey Adams

5

C ++

Bu C ++ 'da yazdığım en büyük şey. Normalde Objective-C kullanıyorum.

Polimorfik ama hiçbir şeyi serbest bırakmıyor.

Benim mainişlevi (ve addişlev ZipWith) Bu gibi bakarak sona erdi:

int add(int a, int b) {return a + b;}

int main(int argc, char **argv) {
    int numFib = 15; // amount of fibonacci numbers we'll print
    if (argc == 2) {
        numFib = atoi(argv[1]);
    }

    // list that starts off 1, 1...
    LazyList<int> fibo = LazyList<int>(new Cons<int>(1,
                     new LazyList<int>(new Cons<int>(1))));
    // zip the list with its own tail
    LazyList<int> *fiboZip = LazyList<int>::ZipWith(add, &fibo, fibo.Tail());
    // connect the begin list to the zipped list
    fibo.Tail() -> ConnectToList(fiboZip);

    // print fibonacci numbers
    int *fibonums = fibo.Take(numFib);    
    for (int i=0; i<numFib; i++) cout << fibonums[i] << " ";

    cout<<endl;

    return 0;
}

Bu verir

 ./lazylist-fibo 20
 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 

Sınıflar şu şekilde çalışır:

make a thunk:    LazyList<T>(new Thunk<T>( function, *args )) 
make empty list: LazyList<T>(new Nil<T>())
make cons:       LazyList<T>(new Cons<T>( car, *cdr ))

list empty:      list.Empty()
list's head:     list.Head()
list's tail:     list.Tail()
zipWith:         LazyList<T>::ZipWith(function, a, b)
take:            list.Take(n)
print:           list.Print()

Tam kaynak: burada . Çok karışık, çünkü büyük bir dosyada.

Düzenleme: bağlantıyı değiştirdi (eskisi ölmüştü).


3
Mükemmel iş ve kelimenin tam anlamıyla "zinde atmak" için teşekkürler :-) Hiçbir şekilde bir C ++ uzmanı değilim, ancak Thunk'u uygulamak için daha C ++ - y yolu bir işlev nesnesi (aka "functor") kullanmak olabilir . olduğu, aşırı ()) operatör ve kullanım miras kullanmak zorunda kalmamak için void*. Bunu yapmanın önemsiz bir örneği için buraya bakın .
Joey Adams,

Tam kaynak bağlantısı şimdi öldü. Tekrar yükler misin? gist.github.com koymak için iyi bir yer.
Joey Adams,

@JoeyAdams: bitti.
marinus

4

piton

Jeneratörleri listeyi uygulamak için kullanmaz, sadece uygulamak için kullanır. __iter__ için kullanmaz kullanma yönteminifor .

class Node(object):
    def __init__(self, head, tail):
        self.__head__ = head
        self.__tail__ = tail

    def force(self):
        return self

    def empty(self):
        return False

    def head(self):
        return self.__head__

    def tail(self):
        return self.__tail__

    def zip_with(self, func, other):
        def gen_func():
            if other.empty():
                return other
            return Node(func(self.head(), other.head()), self.tail().zip_with(func, other.tail()))
        return Thunk(gen_func)

    def __iter__(self):
        while not self.empty():
            yield self.head()
            self = self.tail()

    def append(self, other):
        while not self.tail().empty():
            self = self.tail()
        self.__tail__ = other

    def take(self, n):
        if n == 0:
            return NullNode()
        else:
            return Node(self.__head__, self.__tail__.take(n - 1))

    def _print(self):
        for item in self:
            print item

class NullNode(Node):
    def __init__(self):
        pass

    def empty(self):
        return True

    def head(self):
        raise TypeError("cannot get head of nil")

    def tail(self):
        raise TypeError("cannot get tail of nil")

    def zip_with(self, func, other):
        return self

    def append(self, other):
        raise TypeError("cannot append to nil")

    def take(self, n):
        return self

class Thunk(Node):
    def __init__(self, func):
        self.func = func

    def force(self):
        node = self.func()
        self.__class__ = node.__class__
        if not node.empty():
            self.__head__ = node.head()
            self.__tail__ = node.tail()
        return self

    def empty(self):
        return self.force().empty()

    def head(self):
        return self.force().head()

    def tail(self):
        return self.force().tail()

    def take(self, n):
        return self.force().take(n)

Fibonacci listesi şöyle yaratılır:

>>> from lazylist import *
>>> fib = Node(0, Node(1, NullNode()))
>>> fib.append(fib.zip_with(lambda a, b: a + b, fib.tail()))
>>> 

1
Bu güzel. En sevdiğim çizgi self.__class__ = node.__class__. Bunun, görünüşte int .__ add__ için geçersiz bir argüman olan 2971215073 (uzun) düzeyine ulaştığında NotImplemented istisnasına çarptığını unutmayın. Büyük tamsayıları desteklemek için, yapınfib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Joey Adams

1
Neden boş ya da thunk'a ekleyemiyorsun?
PyRulez

4

Yakut

İlk Ruby programım. Tüm düğümleri, dizi uzunluğunun türünü belirlediği dizi olarak temsil ederiz:

0: empty list
1: thunk (call the single element to get the cons cell)
2: cons cell (1st is head, 2nd is tail)

Kod daha sonra oldukça basittir, özyinelemeli fib'i ayarlamak için thunk fonksiyonunu sıfırlamak için bir hack ile.

def nil_()
  return Array[]
end

def cons(a, b)
  return Array[a, b]
end

def thunk(f)
  return Array[f]
end

def force(x)
  if x.size == 1
    r = x[0].call
    if r.size == 2
      x[0] = r[0]
      x.push(r[1])
    else
      x.pop()
    end
  end
end

def empty(x)
  force(x)
  return x.size == 0
end

def head(x)
  force(x)
  return x[0]
end

def tail(x)
  force(x)
  return x[1]
end

def zipWith(f, a, b)
  return thunk(lambda {
    if empty(a) or empty(b)
      return nil_()
    else
      return cons(f.call(head(a), head(b)), zipWith(f, tail(a), tail(b)))
    end
  })
end

def take(n, x)
  if n == 0
    return nil_()
  else
    return cons(head(x), take(n - 1, tail(x)))
  end
end

def print(x)
  while not empty(x)
    puts x[0]
    x = x[1]
  end
end

def add(x, y)
  return x + y
end

T=thunk(nil)  # dummy thunk function
fibs=cons(0, cons(1, T))
T[0]=zipWith(method(:add), fibs, tail(fibs))[0]  # overwrite thunk function

print(take(40, fibs))

Sen kullanabilirsiniz [...]yerine Array[...].
Lowjacker

3

Google Go

Nispeten yeni bir dil ve ben bunu öğrendim CTRL+Fing Spec .

package main
import "fmt"

type List struct {
  isNil, isCons, isThunk bool
  head *interface { }
  tail *List
  thunk (func() List)
}

func Nil() List {
  return List { true, false, false, nil, nil, Nil }
}

func Cons(a interface { }, b List) List {
  return List { false, true, false, &a, &b, Nil }
}

func Thunk(f(func() List)) List {
  return List { false, false, true, nil, nil, f }
}

func Force(x List) List {
  if x.isNil { return Nil()
  } else if x.isCons { return Cons(*x.head, *x.tail) }
  return Force(x.thunk())
}

func Empty(x List) bool {
  return Force(x).isNil;
}

func Head(x List) interface { } {
  y := Force(x)
  if y.isNil { panic("No head for empty lists.") }
  return *y.head
}

func Tail(x List) List {
  y := Force(x)
  if y.isNil { panic("No tail for empty lists.") }
  return *y.tail
}

func Take(n int, x List) List {
  if (n == 0) { return Nil() }
  return Thunk(func() List {
    y := Force(x)
    return Cons(*y.head, Take(n - 1, *y.tail))
  })
}

func Wrap(x List) List {
  return Thunk(func() List {
    return x
  })
}

func ZipWith(f(func(interface { }, interface { }) interface { }), a List, b List) List {
  return Thunk(func() List {
    x, y := Force(a), Force(b)
    if x.isNil || y.isNil {
      return Nil()
    }
    return Cons(f(*x.head, *y.head), ZipWith(f, *x.tail, *y.tail))
  });
}

func FromArray(a []interface { }) List {
  l := Nil()
  for i := len(a) - 1; i > -1; i -- {
    l = Cons(a[i], l)
  }
  return l
}

func Print(x List) {
  fmt.Print("[")
  Print1(x)
  fmt.Print("]")
}

func Print1(x List) {
  y := Force(x)
  if y.isCons {
    fmt.Print(Head(y))
    z := Force(Tail(y))
    if z.isCons { fmt.Print(", ") }
    Print1(z)
  }
}

func Plus(a interface { }, b interface { }) interface { } {
  return a.(int) + b.(int)
}

func Fibs() List {

  return Thunk(func() List {
    return Cons(0, Cons(1, Thunk(func() List {
      return ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs)))
    })))
  })
}

func Fibs0() List {
  // alternative method, working
  return Cons(0, Cons(1, Fibs1(0, 1)))
}

func Fibs1(a int, b int) List {
  c := a + b
  return Cons(c, Thunk(func() List { return Fibs1(b, c) }))
}

func CountUp(x int, k int) List {
  return Cons(x, Thunk(func() List {
    return CountUp(x + k, k)
  }))
}

func main() {
  //a := []interface{} { 0, 1, 2, 3 }
  //l, s := FromArray(a), FromArray(a)
  Print(Take(40, Fibs()))
}

Sorun, thun-a-thunks'larla uğraşarak çözüldü. Ancak, çevrimiçi derleyicinin belki de bellek yüzünden 40 öğe alamayacağı anlaşılıyor. Daha sonra Linux'umda test edeceğim.

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368runtime: address space conflict: map() = 
throw: runtime: address space conflict

panic during panic

Kodu çevrimiçi derleyiciyle test ettim , çünkü Go on Windows'a kolayca yükleyemiyorum.


Bu oldukça hoş ve basit. Bununla birlikte, 3 bool yerine, olası değerleri iotasabit jeneratör tarafından oluşturulan sabit olan tek bir etiket kullanabilirsiniz . Bkz Git Programlama Dili Şartname bir örnek ve StackOverflow'daki bir cevap .
Joey Adams

Kişisel FibsGit sıkı bir değerlendirme kullanır ve çünkü fonksiyon çalışmaz Fibsbir sonlandırma koşulu olmadan kendi üzerine recurses. Fibs0/ Fibs1Görevimde açıklanan algoritmadan ziyade basit bir üretici yaklaşımını kullanıyor, bu yüzden "gereksinimleri" karşılamıyor. Görevimi, uygulanması gereken tembel özyineleme üzerinde yoğunlaşacak şekilde güncelledim fibs = 0 : 1 : zipWith (+) fibs (tail fibs) .
Joey Adams

Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs))))), bellek
tükeniyor

Denedim Cons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))ve geçersiz bellek adresi hatası alıyorum
Ming-Tang

1
Halen öğrendiğiniz için Git: Listeler için arabirimleri ve Thunks, vb. İçin ayrı türleri kullanarak bundan daha zarif bir kod yapabilirsiniz.
cthom06

3

Kristal

GitHub deposunu takip etmeme rağmen, ben şimdiye kadar Crystal'ı hiç kullanmadım . Kristal, tam tür çıkarımlı, statik olarak yazılmış bir Yakut çeşididir. Zaten bir Ruby cevabı olsa bile, Crystal'in statik yazımı, düğümleri temsil etmek için bir dizi yerine polimorfizm kullanmamı sağladı. Crystal, değişiklik yapılmasına izin vermediğinden self, Nodebaşka her şeyi saracak ve topakları yönetecek adlı bir sarmalayıcı sınıfı oluşturdum .

Sınıflar ile birlikte, ben yapıcı fonksiyonları yarattı lnil, consve thunk. Ben de daha önce Ruby'yi 20 satırlık bir senaryodan daha önce hiç kullanmamıştım, bu yüzden blok işleri beni bir kenara attı.

Ben Go cevabındakifib fonksiyonu temel aldım .

class InvalidNodeException < Exception
end

abstract class LazyValue
end

class LNil < LazyValue
    def empty?
        true
    end

    def force!
        self
    end

    def head
        raise InvalidNodeException.new "cannot get head of LNil"
    end

    def tail
        raise InvalidNodeException.new "cannot get tail of Nil"
    end

    def take(n)
        Node.new self
    end
end

class Cons < LazyValue
    def initialize(@car, @cdr)
    end

    def empty?
        false
    end

    def force!
        @cdr.force!
        self
    end

    def head
        @car
    end

    def tail
        @cdr
    end

    def take(n)
        Node.new n > 0 ? Cons.new @car, @cdr.take n-1 : LNil.new
    end
end

class Thunk < LazyValue
    def initialize(&@func : (-> Node))
    end

    def empty?
        raise Exception.new "should not be here!"
    end

    def force!
        @func.call()
    end

    def head
        self.force!.head
    end

    def tail
        self.force!.tail
    end

    def take(n)
        self.force!.take n
    end
end

class Node
    def initialize(@value = LNil.new)
    end

    def empty?
        self.force!
        @value.empty?
    end

    def force!
        @value = @value.force!
        self
    end

    def head
        self.force!
        @value.head
    end

    def tail
        self.force!
        @value.tail
    end

    def take(n)
        self.force!
        return @value.take n
    end

    def print
        cur = self
        while !cur.empty?
            puts cur.head
            cur = cur.tail
        end
    end
end

def lnil
    Node.new LNil.new
end

def cons(x, r)
    Node.new Cons.new x, r
end

def thunk(&f : (-> Node))
    Node.new Thunk.new &f
end

def inf(st=0)
    # a helper to make an infinite list
    f = ->() { lnil }
    f = ->() { st += 1; cons st, thunk &f }
    thunk { cons st, thunk &f }
end

def zipwith(a, b, &f : Int32, Int32 -> Int32)
    thunk { a.empty? || b.empty? ? lnil :
            cons f.call(a.head, b.head), zipwith a.tail, b.tail, &f }
end

def fibs
    # based on the Go answer
    fibs2 = ->(a : Int32, b : Int32) { lnil }
    fibs2 = ->(a : Int32, b : Int32) { cons a+b, thunk { fibs2.call b, a+b } }
    cons 0, cons 1, thunk { fibs2.call 0, 1 }
end

fibs.take(40).print
zipwith(inf, (cons 1, cons 2, cons 3, lnil), &->(a : Int32, b : Int32){ a+b }).print

2

Kuralları biraz eğittim çünkü burada henüz bir .NET çözümü yoktu - ya da genel olarak Python'da devralma kullananlar dışında bir OOP çözümü var, ancak her ikisini de ilginç kılacak (özellikle Python'dan bu yana) benim çözümümden yeterince farklı selfthunk uygulamasını basitleştirerek örneği değiştirmeye izin verir ).

Yani bu C # . Tam açıklama: C # 'da aceminin yakınında değildim ama şu anda işte kullanamadığımdan beri bir süredir dile değmemiştim.

Belirgin noktalar:

  • Tüm sınıfları ( Nil, Cons, Thunk), ortak bir soyut temel sınıfından elde List.

  • ThunkSınıf kullanan Zarf-Letter deseni. Bu, esas self.__class__ = node.__class__olarak Python kaynağındaki atamaya öykünür , çünkü thisbaşvuru C # 'da değiştirilemez.

  • IsEmpty, HeadVe Tailözelliklerdir.

  • Tüm uygun fonksiyonlar, tekrarlayan ve tembel olarak (tembel Printolamaz, hariç ) topakları döndürülerek uygulanır. Örneğin, bu Nil<T>.ZipWith:

    public override List<T> ZipWith(Func<T, T, T> func, List<T> other) {
        return Nil();
    }
    

    … Ve bu Cons<T>.ZipWith:

    public override List<T> ZipWith(Func<T, T, T> func, List<T> other) {
        return Thunk(() => {
            if (other.IsEmpty)
                return Nil();
    
            return Cons(func(Head, other.Head), Tail.ZipWith(func, other.Tail));
        });
    }
    

    Ne yazık ki, C # 'nin çoklu gönderimi yok, aksi halde ififadeden de kurtulabilirim . Ne yazık ki zar yok.

Şimdi, uygulamamdan gerçekten memnun değilim. Şimdiye kadar mutluyum, çünkü yukarıdakilerin hepsi tamamen basittir. Ama . FibTanımının gereksiz yere karmaşık olduğunu hissediyorum çünkü çalışmasını sağlamak için argümanları topaklara sarmam gerekiyor:

List<int> fib = null;
fib = List.Cons(0, List.Cons(1,
    List.ZipWith(
        (a, b) => a + b,
        List.Thunk(() => fib),
        List.Thunk(() => fib.Tail))));

(Burada, List.Cons, List.Thunkve List.ZipWithkolaylık sarıcıları.)

Aşağıdaki çok kolay tanımlamanın neden işe yaramadığını anlamak istiyorum:

List<int> fib = List.Cons(0, List.Cons(1, List.Nil<int>()));
fib = fib.Concat(fib.ZipWith((a, b) => a + b, fib.Tail));

ConcatElbette uygun bir tanımı verilmiş . Bu aslında Python kodunun yaptığı şeydir - fakat işe yaramadı (= bir form atma).

/ EDIT: Joey, bu çözümdeki açık kusurlara dikkat çekti. Bununla birlikte, ikinci çizgiyi thunk ile değiştirmek de bir hata veriyor (Mono segfaults; Mono'nun iyi idare edemediği bir yığın taşması şüphesi var):

fib = List.Thunk(() => fib.Concat(fib.ZipWith((a, b) => a + b, fib.Tail)));

Kaynak kodun tamamı GitHub'da bir özet olarak bulunabilir .


"Maalesef, C # 'nın birden fazla gönderimi yok" - etkileyici olsa da olayları kullanarak etki elde edebilirsiniz.
Peter Taylor

Tembel değerlendirme konusundaki ironik şey, uygulanması için devlet gerektirmesidir. fib.ZipWithve kalan ve değişmeyen fib.Taileskisini kullanın . Böylece, (sanırım) alırsınız ve işleviniz boşa almanıza izin vermez (Haskell'in almasına rağmen). İkinci çizginin değerini bir thunkta silmeyi deneyin, bu yüzden eskiden ziyade yeniyi ifade edecek . fib[0,1][0,1,1]Takefib
Joey Adams

@Peter Evet; Birden fazla gönderi uygulamak için Ziyaretçi desenini de kullanabilirsiniz, ancak çözümün basit kalmasını istedim.
Konrad Rudolph

@Joey Duh. Şimdi gözle görülür derecede açık. Ancak thunk çözümü hala çalışmıyor (güncellenmiş cevaba bakınız) ancak şimdi araştırmak için çok meşgulüm.
Konrad Rudolph

2

Pico

Kayıt için, bu çözüm, srfi-45'te tanımlandığı gibi planın gecikme gücünün çevirisini kullanır . ve bunun üzerine tembel listeler oluşturur.

{ 
` scheme's srfi-45 begins here `

  _lazy_::"lazy";
  _eager_::"eager";

  lazy(exp())::[[_lazy_, exp]];
  eager(exp)::[[_eager_, exp]];
  delay(exp())::lazy(eager(exp()));

  force(promise)::
    { content:promise[1];
      if(content[1]~_eager_,
        content[2],
        if(content[1]~_lazy_,
          { promise_:content[2]();
            content:promise[1];
            if(content[1]~_lazy_, 
             { content_:promise_[1];
               content[1]:=content_[1];
               content[2]:=content_[2];
               promise_[1]:=content });
            force(promise) })) };

` scheme's srfi-45 ends here `

nil:delay([]);
is_nil(s):size(force(s))=0;
cons(a(),b()):delay([a(),b()]);
head(s):force(s)[1];
tail(s):force(s)[2];

zipWith(f,a,b):
  lazy(if(is_nil(a)|is_nil(b),
         nil,
         cons(f(head(a),head(b)), zipWith(f,tail(a),tail(b)))));

fibs:void;
fibs:=cons(0, cons(1, zipWith(+,fibs,tail(fibs))));

take(c,s):
  lazy(if((c=0)|(is_nil(s)),
         nil,
         cons(head(s),take(c-1,tail(s)))));

print(s):
  { comma(s):
      if(is_nil(s),
        void,
        { display(head(s));
          if(!(is_nil(tail(s))), display(","));
          comma(tail(s)) });
    display("[");
    comma(s);
    display("]");
    void };

print(take(40,fibs))

}

Böyle çıkış görünüyor: (ama nasıl bağlı tpico. bunun daha çift tırnak olabilir yamalı display. normalde tırnak dizeleri yazdıran yani her görünüşe [, ,, ]gibi çevrelerindeki tırnak olurdu "[".)

[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986]<void>

tpico'daki tamsayı veri tipinin sınırları nedeniyle, bu 45. (veya 46. ofset) Fibonacci sayısının hesaplanmasında başarısız olur.

tpico 2.0pl11'in begin(a,b)(genellikle olduğu gibi {a;b}) kırıldığını ve iffonksiyonun özyinelemeli olmadığını unutmayın. neden beginkuyruk özyineli olmadığını anlamak için 5 yıl sürdüğünden bahsetmiyorum . Aynı zamanda srfi-45'in Pico'daki çevirisini de yazdım. o olduğu ortaya çıktı begindeğerine bekliyordu bonu beklemek gerek yoktu ne zaman dönmeden önce. ve bir kere anladım da ifaynı sorunu olduğu gibi düzeltmek mümkün oldu . ve meta seviye yapıcısını makeçalışmaz kılan bu başka bir hata vardı .

Pico, işlev çağrılmadan önce argümanları değerlendirilirse veya sadece büyük parçalar olarak paketlenirse, bir işlev denetimine izin verir. Bu kod için, işlev çağrısı tuhaflıkları üzerinde elips olabilir .

Pico'nun tür çıkarımı yok. Bunu bir süre düşündüm ama işlevin tuhaflığına bağlı olarak bir sorunla karşılaştım . türlerin bağlı değişken adlarının varlığını kodlaması gerektiği ifadesiyle karşılaştım . ama esasen Hindley-Milner tipindeki çıkarımı mutasyona uğramadan bir Pico altkümesine nasıl uyarlayacağımı düşünüyordum. ana fikir, tip denetleyicisinin, birden fazla olası bağlama varsa birden fazla olası şema döndürmesi ve en az bir olası tip şeması varsa tip kontrolünün başarılı olmasıydı . olası bir şema, tür ataması çakışmayandır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.