C'est un bon exercice pour devenir plus fluide dans ce langage de programmation que vous vouliez apprendre, mais que vous n'avez que légèrement modifié. Cela implique de travailler avec des objets, d'utiliser ou de simuler des fermetures et d'étirer le système de texte.
Votre tâche consiste à écrire du code pour gérer les listes paresseuses, puis à l'utiliser pour implémenter cet algorithme pour générer des nombres de Fibonacci:
Les exemples de code sont en Haskell
let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
in take 40 fibs
Résultat:
[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]
L'implémentation de votre liste paresseuse doit respecter ces directives:
- Un nœud de liste est l'une des trois choses:
- Nil - Liste vide.
[]
- Inconvénients - Un seul élément, associé à une liste des éléments restants:
1 : [2,3,4,5]
(:
est l'opérateur des inconvénients à Haskell) - Thunk - Un calcul différé qui produit un noeud List en cas de besoin.
- Nil - Liste vide.
- Il prend en charge les opérations suivantes:
- nil - Construit une liste vide.
- cons - Construit une cellule contre.
- thunk - Construit un Thunk, étant donné une fonction qui ne prend aucun argument et renvoie un Nil ou un Cons.
- force - Étant donné un nœud de liste:
- Si c'est un néant ou un inconvénient, renvoyez-le simplement.
- S'il s'agit d'un Thunk, appelez sa fonction pour obtenir un Nil ou un Cons. Remplacez le thunk par ce Nil ou Contre, et retournez-le.
Remarque: le remplacement du thunk par sa valeur forcée est une partie importante de la définition de "paresseux" . Si cette étape est ignorée, l'algorithme de Fibonacci ci-dessus sera trop lent.
- empty - Voir si un noeud List est Nil (après l'avoir forcé).
- head (aka "car") - Obtenez le premier élément d'une liste (ou lancez un ajustement si c'est nul).
- tail (aka "cdr") - Obtenez les éléments après la tête d'une liste (ou lancez un ajustement si c'est Nil).
- zipWith - Étant donné une fonction binaire (par exemple
(+)
) et deux listes (éventuellement infinies), appliquez la fonction aux éléments correspondants des listes. Exemple:
zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
- take - Étant donné un nombre N et une liste (éventuellement infinie), prenez les N premiers éléments de la liste.
- print - Imprime tous les éléments d'une liste. Cela devrait fonctionner de manière incrémentielle lorsque vous disposez d'une liste longue ou infinie.
fibs
s'utilise dans sa propre définition. La mise en place d'une récursivité paresseuse est un peu délicate; vous devrez faire quelque chose comme ceci:- Attribuer un thunk pour
fibs
. Laissez-le dans un état factice pour l'instant. - Définissez la fonction thunk, qui dépend d'une référence à
fibs
. - Mettez à jour le thunk avec sa fonction.
Vous souhaiterez peut-être masquer cette plomberie en définissant une fonction
fix
qui appelle une fonction de retour de liste avec sa propre valeur de retour. Pensez à faire une petite sieste pour que cette idée puisse s'installer.- Attribuer un thunk pour
Le polymorphisme (la capacité de travailler avec des listes de tout type d'élément) n'est pas requis, mais voyez si vous pouvez trouver un moyen de le faire qui soit idiomatique dans votre langue.
- Ne vous inquiétez pas de la gestion de la mémoire. Même les langages avec garbage collection ont tendance à transporter des objets que vous n'utiliserez plus jamais (par exemple sur la pile d'appels), alors ne soyez pas surpris si votre programme perd de la mémoire en parcourant une liste infinie.
N'hésitez pas à dévier légèrement de ces lignes directrices pour tenir compte des particularités de votre langue, ou pour explorer des approches alternatives aux listes paresseuses.
Règles:
- Choisissez une langue que vous ne connaissez pas bien. Je ne peux pas "exiger" cela, d'où la balise "honor-system". Cependant, les électeurs peuvent vérifier votre historique pour voir dans quelles langues vous avez posté.
N'utilisez pas le support de liste paresseuse intégré de votre langue pour tout faire. Postez quelque chose de substantiel ou du moins intéressant.
Haskell est à peu près sorti. Autrement dit, à moins que vous ne fassiez quelque chose comme ceci:
data List a = IORef (ListNode a) data ListNode a = Nil | Cons !a !(List a) | Thunk !(IO (ListNode a))
Remarque: l'évaluation non stricte de Haskell n'est pas interdite, mais l'implémentation de votre liste paresseuse ne doit pas en tirer directement la capacité. En fait, il serait intéressant de voir une solution efficace et purement fonctionnelle qui ne nécessite pas de paresse.
Python:
- N'utilisez pas itertools.
- Les générateurs vont bien, mais vous les utilisez, vous devrez trouver un moyen de mémoriser les valeurs forcées.
la source
zipWith
sur deux listes de longueurs différentes?zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]
. Cependant, cela n'a pas d'importance pour l'algorithme de Fibonacci ci-dessus, car les deux arguments de zipWith sont des listes infinies.fibs
correctement, car cela dépend de lui-même. J'ai mis à jour la question pour élaborer sur la récursivité paresseuse. FUZxxl l'a compris lui-même.Réponses:
PostScript
J'ai déjà joué avec PostScript , mais je ne dirais pas que je le connais particulièrement bien (en fait, je suppose que vous pouvez compter le nombre de personnes dans le monde qui connaissent vraiment PostScript d'une seule main).
J'ai dévié de vos spécifications en ce que la fonction utilisée pour créer un thunk est autorisée à renvoyer un autre thunk;
force
continuera à évaluer jusqu'à ce que le résultat soit unnil
ou uncons
.Les listes sont implémentées sous forme de dictionnaires:
Le code suit. Notez que nous remplaçons certains opérateurs intégrés (en particulier
print
; je n'ai pas vérifié s'il y en a plus); dans l'utilisation du monde réel, cela devrait être surveillé. Bien sûr, il n'y aura pas d'utilisation réelle, donc ça va.Les commentaires avant les procédures doivent être lus comme
c'est-à-dire montrant le contenu attendu de la pile avant l'appel et le contenu résultant de la pile après l'appel. Les commentaires dans les procédures montrent le contenu de la pile après l'exécution de la ligne particulière.
Chargez ceci dans Ghostscript, en ignorant la page affichée - nous travaillons uniquement avec l'interpréteur. Voici l'algorithme de Fibonacci:
Deux fonctions supplémentaires intéressantes:
Commencez à compter à 5, multipliez chaque élément de la liste résultante par 3 et affichez les dix premières valeurs:
Concernant le polymorphisme: Même si PostScript est fortement typé, il autorise des types arbitraires comme valeurs de dictionnaire, vous pouvez donc y ajouter tout ce que vous voulez:
Notez que les erreurs de type, par exemple en essayant d'ajouter des chaînes aux nombres, ne se produiront qu'au moment de l'évaluation:
la source
force
mémorise- t- il les valeurs renvoyées?copy
opérateur copie le contenu de la version évaluée dans l'original, en écrasant/type
et en définissant éventuellement d'autres valeurs. Après avoir évalué récursive jusqu'à ce que nous avonsnil
oucons
, elle a aussi (viaundef
) Retire/func
et, le cas échéant,/data
. La dernière étape n'est pas strictement nécessaire (/func
et/data
serait simplement ignorée), mais laisser cette étape en dehors entraînerait une fuite de mémoire supplémentaire :)C
Je suis un débutant total en C, ce code est en fait la première vraie chose que j'ai codée en C. Il se compile sans aucun avertissement et fonctionne bien sur mon système.
Comment construire
Tout d'abord, récupérez l'archive tar de mon serveur . Il comprend un makefile, alors il suffit de l'exécuter
make
pour le créer, puismake run
de l'exécuter. Le programme imprime ensuite une liste des 93 premiers nombres de fibonacci. (Après le numéro 94, un entier 64 bits non signé déborde)Explication
Le noyau du programme est le fichier
lazy-list.c
. Dans le fichier d'en-tête correspondant, je définis une structurelist
, qui est notre liste paresseuse. Cela ressemble à ceci:Le membre
kind
est une sorte de tag. Il marque, que l'on rechète les listes end (NIL
), une cellule déjà évaluée (CONS
) ou un thunk (THUNK
). Puis, suit une union. Il estLe contenu de l'union est affirmé par la balise. Si la balise est
NIL
, le contenu de l'union n'est pas défini.En définissant les fonctions d'assistance mentionnées dans la spécification ci-dessus, on peut généralement extraire la définition des listes de son utilisation, par exemple. vous pouvez simplement appeler
nil()
pour obtenir une liste vide au lieu d'en créer une par vous-même.Les trois fonctions les plus intéressantes sont
zipWith
,take
etfibonaccis
. Mais je ne veux pas expliquertake
, car il est très similaire àzipWith
. Toutes les fonctions, qui fonctionnent paresseusement, ont trois composantes:Dans le cas de
zipWith
, ce sontzipWith
,__zipWith
et__zipArgs
. Je les montre juste ici sans autre explication, leur fonction devrait être assez claire:L'autre fonction intéressante est
fibonaccis()
. Le problème est que nous devons passer un pointeur de la première et de la deuxième cellule au thunk de la troisième, mais pour créer ces cellules, nous avons également besoin d'un pointeur sur le thunk. Pour résoudre ce problème, j'ai rempli le pointeur sur le thunk avec uneNULL
première et l' ai changé en thunk, après sa création. Voici l'écoute:Améliorations possibles
content_t
, que l'on peut changer pour ce qui convient.la source
void*
comme type decontent_t
.void*
aussi, mais je pensais que cela éluderait trop le système de caractères. N'est-ce pas possible en utilisant des modèles?void*
ni amis.kind
est en quelque sorte une étiquette. » Vous pouvez simplement l' appelertag
, comme c'est un joli accepté terme pour le concept (par exemple union taggés , Spineless Tagless G machine . D'autre part, « genre » a un sens différent dans un . contexte Haskell: le type d'un typeInt
a genre*
,[]
a en quelque sorte* -> *
, et(,)
a en quelque sorte* -> * -> *
.C ++
C'est la plus grande chose que j'aie jamais écrite en C ++. J'utilise normalement Objective-C.
C'est polymorphe mais ça ne libère jamais rien.
Ma
main
fonction (et laadd
fonction àZipWith
) a fini par ressembler à ceci:Cela donne
Les cours fonctionnent comme ceci:
Source complète: ici . C'est un gâchis, principalement parce que c'est dans un gros fichier.
Edit: changé le lien (l'ancien était mort).
la source
()
opérateur), et d'utiliser l'héritage pour éviter d'avoir à utiliservoid*
. Voir ici pour un exemple trivial de le faire.Python
N'utilise pas de générateurs pour implémenter la liste, juste pour implémenter la
__iter__
méthode à utiliser avecfor
.La liste Fibonacci est créée comme suit:
la source
self.__class__ = node.__class__
. Notez que cela frappe une exception NotImplemented lorsqu'il atteint 2971215073 (long), ce qui est apparemment un argument non valide pour int .__ add__. Pour prendre en charge les grands nombres entiers, faitesfib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Rubis
Mon premier programme Ruby. Nous représentons tous les nœuds sous forme de tableaux, où la longueur du tableau détermine le type:
Le code est alors assez simple, avec un hack pour réinitialiser la fonction thunk pour configurer le fib récursif.
la source
[...]
place deArray[...]
.Google Go
Une langue relativement nouvelle, et je l'ai apprise en
CTRL+F
ing le Spec .Le problème a été résolu en traitant les thunk-within-a-thunks. Cependant, il semble que le compilateur en ligne ne puisse pas prendre 40 éléments, peut-être à cause de la mémoire. Je le testerai plus tard sur mon Linux.
J'ai testé le code avec le compilateur en ligne , car je ne peux pas installer Go sur Windows facilement.
la source
iota
générateur de constantes. Voir un exemple dans la spécification du langage de programmation Go et une réponse sur StackOverflow .Fibs
fonction ne fonctionne pas car Go utilise une évaluation stricte et seFibs
reproduit sur elle-même sans condition de terminaison.Fibs0
/Fibs1
utilise une approche de générateur simple plutôt que l'algorithme décrit dans mon article, donc il ne répond pas aux "exigences". J'ai mis à jour mon message pour élaborer sur la récursivité paresseuse, qui est nécessaire pour implémenterfibs = 0 : 1 : zipWith (+) fibs (tail fibs)
.Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs)))))
, ça sort de la mémoireCons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))
et j'obtiens une erreur d'adresse mémoire invalideCristal
Malgré le fait de suivre le référentiel GitHub, je n'ai jamais utilisé Crystal jusqu'à présent. Crystal est une variante Ruby de type statique avec une inférence de type complète. Même s'il existe déjà une réponse Ruby, le typage statique de Crystal m'a amené à utiliser le polymorphisme, plutôt qu'un tableau, pour représenter les nœuds. Parce que Crystal n'autorise pas la modification de
self
, j'ai créé une classe wrapper, nomméeNode
, qui envelopperait tout le reste et gérerait les thunks.En plus des cours, j'ai créé les fonctions de constructeur
lnil
,cons
etthunk
. Je n'ai jamais utilisé Ruby depuis plus d'un script de 20 lignes auparavant, donc le truc de bloc m'a beaucoup déstabilisé.J'ai basé la
fib
fonction sur la réponse Go .la source
J'ai un peu plié les règles car il n'y a pas encore de solution .NET ici - ou plus généralement une solution OOP à l'exception de celle en Python qui utilise l'héritage, mais elle est suffisamment différente de ma solution pour rendre les deux intéressantes (en particulier depuis Python permet de modifier l'
self
instance, ce qui rend l'implémentation du thunk simple).C'est donc C # . Divulgation complète: je suis loin d'être un débutant en C # mais je n'ai pas touché la langue depuis un moment car je n'ai actuellement aucune utilité pour cela au travail.
Les points saillants:
Toutes les classes (
Nil
,Cons
,Thunk
) proviennent d'une classe de base abstraite commune,List
.La
Thunk
classe utilise le modèle Enveloppe-Lettre . Cela émule essentiellement l'self.__class__ = node.__class__
affectation dans la source Python, car lathis
référence ne peut pas être modifiée en C #.IsEmpty
,Head
EtTail
sont des propriétés.Toutes les fonctions appropriées sont implémentées de manière récursive et paresseuse (à l'exception de
Print
, qui ne peut pas être paresseux) en renvoyant les thunks. Par exemple, c'estNil<T>.ZipWith
:… Et c'est
Cons<T>.ZipWith
:Malheureusement, C # n'a pas de répartition multiple, sinon je pourrais également me débarrasser de la
if
déclaration. Hélas, pas de dés.Maintenant, je ne suis pas vraiment satisfait de ma mise en œuvre. Je suis heureux jusqu'à présent, car tout ce qui précède est totalement simple. Mais . Je pense que la définition de
Fib
est inutilement compliquée car je dois envelopper les arguments dans les thunks pour le faire fonctionner:(Ici,
List.Cons
,List.Thunk
etList.ZipWith
enveloppent de commodité.)Je voudrais comprendre pourquoi la définition beaucoup plus simple suivante ne fonctionne pas:
donné une définition appropriée de
Concat
, bien sûr. C'est essentiellement ce que fait le code Python - mais cela ne fonctionne pas (= lancer un ajustement)./ EDIT: Joey a souligné le défaut évident de cette solution. Cependant, le remplacement de la deuxième ligne par un thunk génère également une erreur (Mono segfaults; je soupçonne un débordement de pile que Mono ne gère pas bien):
Le code source complet peut être trouvé en tant qu'essentiel sur GitHub .
la source
fib.ZipWith
etfib.Tail
utiliser l'ancienfib
, qui reste[0,1]
et ne change pas. Ainsi, vous obtenez[0,1,1]
(je pense), et votreTake
fonction ne vous permet pas de prendre de null (la prise de Haskell le fait, cependant). Essayez de placer la valeur r de la deuxième ligne dans un thunk, afin qu'elle se réfère à la nouvellefib
plutôt qu'à l'ancienne.Pico
pour mémoire, cette solution utilise une traduction de la force de retard du schéma telle que définie dans srfi-45 . et construit des listes paresseuses en plus de cela.
les regards de sortie comme ceci: (mais en fonction de la façon dont
tpico
. est patché , il pourrait avoir plus des guillemets doubles dans cedisplay
. imprime normalement des chaînes avec des citations -à- dire toutes les apparences de[
,,
,]
aurait des citations autour d' eux comme"["
.)en raison des limites du type de données entier dans tpico, cela échoue lors du calcul du 45e (ou 46e décalage) nombre de Fibonacci.
notez que tpico 2.0pl11 est cassé en cela
begin(a,b)
(qui est communément écrit comme{a;b}
) et que laif
fonction n'est pas récursive en queue. sans compter qu'il m'a fallu 5 ans pour comprendre pourquoi labegin
queue n'était pas récursive. à cette époque, j'ai également écrit la traduction de srfi-45 en Pico. il s'est avéré qu'ilbegin
attendait la valeur deb
avant de revenir alors qu'il n'avait pas besoin d'attendre. et une fois que j'ai obtenu cela, j'ai également pu le réparerif
car il avait le même problème. et il y avait cette autre erreur qui a rendu le constructeur de niveau métamake
inopérant.Pico permet à une fonction de contrôler si ses arguments sont évalués avant que la fonction soit appelée ou simplement empaquetée en tant que thunks. pour ce code, je peux faire des points de suspension sur les bizarreries de l' appel par fonction .
Pico n'a aucune inférence de type. J'ai réfléchi à cela pendant un certain temps mais j'ai rencontré un problème en raison des bizarreries de l' appel par fonction . je suis venu avec la déclaration que les types doivent coder l'existence de noms de variables liés . mais je pensais principalement à la façon d'adapter l'inférence de type Hindley-Milner à un sous-ensemble de Pico sans mutation. l'idée principale était que le vérificateur de type renvoie plusieurs schémas possibles s'il existe plusieurs liaisons possibles et que le contrôle de type réussit s'il existe au moins un schéma de type possible . un schéma possible est celui qu'aucun conflit d'attribution de type n'entre en conflit.
la source