Quels conseils généraux avez-vous pour jouer au golf dans Clean? Veuillez ne publier que des idées qui peuvent être appliquées aux problèmes de golf de code en général et qui sont au moins quelque peu spécifiques à Clean.
Si vous n'avez jamais entendu parler de Clean, vous pouvez en savoir plus ici .
Ou, vous pouvez rejoindre la salle de chat .
Pour accéder aux fonctions intégrées, même en apparence élémentaires comme (==)ou map, une instruction d'importation est nécessaire, généralement import StdEnvparce qu'elle importe les modules les plus courants comme StdInt, StdBoolet ainsi de suite (voir ici pour plus d'informations sur StdEnv).
Cependant, il peut être possible d'éviter cette importation pour certains défis et d'utiliser simplement les fonctionnalités du langage de base telles que les compréhensions de liste et la correspondance de modèles.
Par exemple, au lieu de
import StdEnv
map f list
on peut écrire
[f x\\x<-list]
Liste des alternatives:
Certaines fonctions ou invocations de fonctions qui ont besoin import StdEnv, une alternative qui n'a pas besoin de l'importation et une estimation approximative des octets enregistrés.
hd-> (\[h:_]=h), ~ 6 octets
tl-> (\[_:t]=t), ~ 6 octets
map f list-> [f x\\x<-list], ~ 10 octets
filter p list-> [x\\x<-list|p x], ~ 11 octets
(&&)-> %a b|a=b=a;%, ~ 6 octets
(||)-> %a b|a=a=b;%, ~ 6 octets
not-> %a|a=False=True;%, ~ 1 octet
and-> %[a:r]|a= %r=a;%_=True, ~ 0 octet
or-> %[a:r]|a=a= %r;%_=False, ~ 0 octet
Il est peu probable que les derniers enregistrent réellement des octets, car un remplacement direct produit plus d'octets que l'importation, mais cela pourrait être possible dans les cas où la récursivité sur la liste est de toute façon nécessaire.
N'est-ce pas import StdEnv+ a and b(21 octets) plus petit que %[a:r]|a= %r=a;%_=True(22 octets), cependant? Ou serait-ce import StdEnv+ a=True and b=True(31 octets), auquel cas il est en effet nettement plus court? (Je n'ai jamais programmé en Clean, en passant.)
Kevin Cruijssen
@KevinCruijssen Nous venions de discuter de cela dans le chat . Il est vrai que ceux-ci sont peu susceptibles d'économiser des octets, sauf peut-être lorsque le programme doit de toute façon récurrencer une liste.
Laikoni
4
Ah ok. Il serait peut-être également utile d'indiquer le nombre d'octets enregistrés avec l'alternative (c'est-à-dire map f list -> [f x\\x<-list] (11 bytes saved)(ou quelque chose de similaire)).
Kevin Cruijssen
@KevinCruijssen Done.
Laikoni
5
Savoir apprendre la langue
Après tout, comment peut-on jouer au golf dans une langue qu'il ne peut pas utiliser!
En ligne
Clean n'est pas un langage bien connu ou bien documenté, et le nom ne permet certainement pas de trouver facilement les ressources nécessaires pour remédier à ces problèmes ... ou le fait-il?
Clean était à l'origine appelé Concurrent Clean , qui est toujours utilisé dans la préface de presque tous les documents liés à Clean - donc si vous recherchez Clean, recherchez plutôt Concurrent Clean.
L'une des similitudes les plus remarquables de Clean avec Haskell (dont il y en a beaucoup) est l'existence de Cloogle , qui est un moteur de recherche de fonctions couvrant les bibliothèques avec lesquelles Clean est livré.
Localement
Les bibliothèques avec lesquelles Clean est fourni se présentent sous la forme de fichiers source Clean assez documentés et décemment commentés, qui peuvent être parcourus à l'aide de l'EDI.
(Il est également livré avec des exemples de programmes complets, ci-dessous $INSTALL/Examples.)
En parlant de cela, la version Windows de Clean est livrée avec un IDE - bien qu'elle soit assez limitée par les normes modernes, c'est bien mieux que d'utiliser un éditeur de texte et la ligne de commande.
Les deux fonctionnalités les plus utiles (dans le cadre de l'apprentissage) sont:
Vous pouvez double-cliquer sur une erreur pour voir sur quelle ligne elle se trouve
Vous pouvez mettre en surbrillance un nom de module et appuyer sur [Ctrl]+[D]pour ouvrir le fichier de définition (ou l'utiliser [Ctrl]+[I]pour le fichier d'implémentation), et basculer entre le fichier de définition et d'implémentation avec[Ctrl]+[/]
Le compilateur de Clean ne se soucie pas de l'encodage que vous pensez avoir enregistré le fichier source, juste des valeurs d'octets dans le fichier. Cela a des conséquences intéressantes.
Dans le corps du code source, seuls les octets avec des points de code correspondant aux caractères ASCII imprimables sont autorisés, en plus de ceux de \t\r\n.
Littéraux:
Dans Stringet [Char]littéraux ( "stuff"et ['stuff']respectivement), tous les octets à l'exception de 0 sont autorisés, avec la mise en garde que "et 'doivent être échappés (pour Stringet [Char]respectivement), et que les retours à la ligne et les retours carraige doivent être remplacés par \net \r(également respectivement).
En Charlittéraux, tout octet sauf 0 est autorisé, ce qui signifie que:
'\n'
'
'
Sont les mêmes, mais le second est un octet plus court.
Échapper:
À part les lettres d'échappement standard \t\r\n(etc.), toutes les séquences d'échappement non numériques de Clean sont destinées à la barre oblique ou à la citation utilisée pour délimiter le littéral dans lequel l'échappement se trouve.
Pour les séquences d'échappement numériques, le nombre est traité comme une valeur octale terminée après trois chiffres. Cela signifie que si vous voulez un null suivi du caractère 1dans a String, vous devez utiliser "\0001"(ou "\0\61") et non"\01" . Cependant, si vous suivez l'évasion avec autre chose que des chiffres, vous pouvez omettre les zéros de tête.
Conséquences:
Cette bizarrerie avec la façon dont Clean gère ses fichiers sources permet Stringet ['Char']devient effectivement des séquences de nombres à 256 chiffres à la base - ce qui a une multitude d'utilisations pour le code-golf, comme le stockage d'index (jusqu'à 255, bien sûr).
Lors de la définition d'une fonction, il est souvent plus court d'utiliser une combinaison de !@$%^&*~-+=<:|>.?/\que d'utiliser des caractères alphanumériques, car cela vous permet d'omettre les espaces blancs entre les identifiants.
Par exemple: ?a=a^2est plus court que f a=a^2, et l'invoquer est également plus court.
Cependant :
Si l'identifiant de fonction est utilisé à côté d' autres symboles, qui peuvent se combiner pour former un identifiant différent mais valide , ils seront tous analysés comme un identifiant et vous verrez une erreur.
Par exemple: ?a+?banalyse comme? a +? b
Aditionellement:
Il est possible d'écraser les identifiants importés dans Clean, et donc les seuls identifiants de symboles à un seul caractère qui ne sont pas déjà utilisés dans le StdEnvsont @$?. L'écrasement ^-+(etc.) peut être utile si vous avez besoin de plus d'identifiants symboliques, mais attention à ne pas écraser celui que vous utilisez.
Certaines des constructions les plus solides (pour le golf) dans les langages fonctionnels sont let ... in ....
Propre bien sûr, a cela, et quelque chose de mieux - le #.
Qu'est-ce qu'un nœud?
Les nettoyages #et l'omniprésence |(pattern guard) sont tous deux connus comme des «expressions de nœuds».
Notamment, ils vous permettent de programmer imperatively- ish en propre ( ce qui est vraiment bon ici!).
Le #(let-before):
Ces deux calculent la valeur d'un entier donné sous forme de chaîne, multiplié par la somme de ses caractères
f s=let i=toInt s;n=sum[toInt c\\c<-:s]in n*i
f s#i=toInt s
#s=sum[toInt c\\c<-:s]
=s*i
Notez comment la version avec #est plus courte et comment nous pouvons redéfinir s. Ceci est utile si nous n'avons pas besoin de la valeur d'une variable lorsque nous la recevons, nous pouvons donc simplement réutiliser le nom. ( letpeut rencontrer des problèmes lorsque vous faites cela)
Mais l'utilisation letest plus facile lorsque vous avez besoin de quelque chose commeflip f = let g x y = f y x in g
Le |(modèle de garde):
Le modèle Pattern Guard peut être utilisé comme ceux de nombreux autres langages fonctionnels - mais il peut également être utilisé comme un impératif if ... else .... Et une version plus courte de l'expression ternaire.
Par exemple, ceux-ci renvoient tous le signe d'un entier:
s n|n<>0|n>0=1= -1
=0
s n=if(n<>0)if(n>0)1(-1)0
s n|n>0=1|n<0= -1=0
Bien sûr, la dernière qui utilise la garde plus traditionnellement est la plus courte, mais la première montre que vous pouvez les imbriquer (mais seules deux clauses de retour inconditionnelles peuvent apparaître sur la même ligne dans la règle de disposition), et la seconde montre ce que le le premier le fait logiquement.
Une note:
Vous pouvez utiliser ces expressions pratiquement n'importe où. En lambdas, case ... of, let ... in, etc.
Si vous utilisez un, Stringvous devriez utiliserText
La conversion en cordes et la manipulation de cordes (le {#Char}/ Stringkind, pas le [Char]kind) est assez longue et mauvaise pour le golf. Le Textmodule y remédie.
Conversion:
Textdéfinit l'opérateur <+pour deux types quelconques qui ont été toStringdéfinis.
Cet opérateur, utilisé comme a<+best le même que toString a+++toString b- économisant au moins 19 octets . Même si vous incluez l'importation supplémentaire ,Textet que vous ne l'utilisez qu'une seule fois, elle économise toujours 14 octets!
Manipulation:
Textdéfinit quelques agrafes de manipulation de chaînes manquantes dans StdEnv:
L'opérateur +pour les chaînes, qui est beaucoup plus court que +++(de StdEnv)
indexOf, avec le comportement de type C de retour -1au lieu d' Nothingéchec
concat, qui concatène une liste de chaînes
join, qui joint une liste de chaînes à l'aide d'une chaîne de séparation
split, qui divise une chaîne en une liste de chaînes sur une sous-chaîne
Parfois, vous vous retrouvez à utiliser une expression lambda (pour passer à map, ou sortBy, etc.). Lorsque vous faites cela (en écrivant des lambdas), il y a plusieurs façons de le faire.
Le droit chemin:
C'est sortBy, avec une liste de tri lambda idiomatique du plus long au plus court
sortBy (\a b = length a > length b)
L'autre bonne façon:
Si vous utilisez Data.Func, vous pouvez également faire
sortBy (on (>) length)
Le chemin court:
C'est la même chose, mais avec une syntaxe golfeur
sortBy(\a b=length a>length b)
L'autre côté:
L'utilisation de la composition n'est pas plus courte cette fois, mais elle peut parfois être plus courte
sortBy(\a=(>)(length a)o length)
L'autre autre manière:
Bien que ce soit un peu artificiel ici, vous pouvez utiliser des gardes dans les lambdas
sortBy(\a b|length a>length b=True=False)
Et aussi des expressions de noeud avant
sortBy(\a b#l=length
=l a>l b)
Une note:
Il existe deux autres formes de lambda, (\a b . ...)et (\a b -> ...), la dernière est identique à la =variante, et la première existe pour une raison quelconque et semble souvent que vous essayez d'accéder à une propriété de quelque chose au lieu de définir une lambda, alors ne le faites pas ne l'utilise pas.
Après avoir vu certains de vos programmes de golf, j'avais l'impression que la syntaxe lambda habituelle de Clean \a=...était : P
Ørjan Johansen
Vous pouvez également ajouter les gardes dans lambda, comme utilisé ici . Ceci n'est pas documenté (cela contredit même le rapport linguistique), mais fonctionne. Aussi, ->et =pour lambdas sont identiques en ce qui concerne le compilateur ( ->est l'ancienne syntaxe). Seul .est différent (mais je ne sais pas exactement comment).
Et dans cet exemple particulier, vous pourriez envisager d'utiliser on(<)length, même si l' Data.Funcimportation vous interrompra, sauf si vous en avez déjà besoin.
@Keelan Cool. Je le mettrai à jour plus tard dans la journée. Je pense que vous pouvez également utiliser let-before ( #) dans les lambdas.
18urous
Oui, vous pouvez :-)
0
Utiliser des littéraux de liste de caractères
Un littéral de liste de caractères est une façon abrégée d'écrire quelque chose comme ['h','e','l','l','o']as ['hello'].
Ce n'est pas la limite de la notation, par exemple:
repeat'c'devient ['c','c'..]devient['cc'..]
['z','y'..'a'] devient ['zy'..'a']
['beginning']++[a,b,c]++['end'] devient ['beginning',a,b,c,'end']
['prefix']++suffix devient ['prefix':suffix]
Ils fonctionnent également en correspondance:
['I don't care about the next character',_,'but I do care about these ones!']
Clean a un tas de fonctions vraiment utiles dans les bibliothèques standard, dont certaines sont incroyablement verbeuses à utiliser sans accès *Worldet à l'aide*World dans le code-golf est généralement une mauvaise idée de toute façon.
Pour contourner ce problème, ccallvous pouvez souvent utiliser des codeblocs internes à la place.
Ce qui précède est de 58 octets, mais vous pouvez économiser 17 octets (jusqu'à 40 + 1) avec:
t::!Int->Int
t _=code{ccall time "I:I"
}
Nombres aléatoires
Celui-ci ne sauvegarde pas les octets seul, mais évite d'avoir à faire circuler une liste de genRandInt
s::!Int->Int
s _=code{ccall time "I:I"ccall srand "I:I"
}
r::!Int->Int
r _=code{ccall rand "I:I"
}
Autres utilisations
En plus de ces deux, qui sont probablement les principales utilisations de ceci dans code-golf, vous pouvez appeler n'importe quelle fonction nommée (y compris, mais sans s'y limiter, chaque syscall), incorporer un assemblage arbitraire avec instruction <byte>et incorporer du code pour la machine ABC.
import StdEnv
+a and b
(21 octets) plus petit que%[a:r]|a= %r=a;%_=True
(22 octets), cependant? Ou serait-ceimport StdEnv
+a=True and b=True
(31 octets), auquel cas il est en effet nettement plus court? (Je n'ai jamais programmé en Clean, en passant.)map f list -> [f x\\x<-list] (11 bytes saved)
(ou quelque chose de similaire)).Savoir apprendre la langue
Après tout, comment peut-on jouer au golf dans une langue qu'il ne peut pas utiliser!
En ligne
Clean n'est pas un langage bien connu ou bien documenté, et le nom ne permet certainement pas de trouver facilement les ressources nécessaires pour remédier à ces problèmes ... ou le fait-il?
Clean était à l'origine appelé Concurrent Clean , qui est toujours utilisé dans la préface de presque tous les documents liés à Clean - donc si vous recherchez Clean, recherchez plutôt Concurrent Clean.
L'une des similitudes les plus remarquables de Clean avec Haskell (dont il y en a beaucoup) est l'existence de Cloogle , qui est un moteur de recherche de fonctions couvrant les bibliothèques avec lesquelles Clean est livré.
Localement
Les bibliothèques avec lesquelles Clean est fourni se présentent sous la forme de fichiers source Clean assez documentés et décemment commentés, qui peuvent être parcourus à l'aide de l'EDI.
(Il est également livré avec des exemples de programmes complets, ci-dessous
$INSTALL/Examples
.)En parlant de cela, la version Windows de Clean est livrée avec un IDE - bien qu'elle soit assez limitée par les normes modernes, c'est bien mieux que d'utiliser un éditeur de texte et la ligne de commande.
Les deux fonctionnalités les plus utiles (dans le cadre de l'apprentissage) sont:
[Ctrl]+[D]
pour ouvrir le fichier de définition (ou l'utiliser[Ctrl]+[I]
pour le fichier d'implémentation), et basculer entre le fichier de définition et d'implémentation avec[Ctrl]+[/]
la source
Oubliez l'encodage des caractères
Le compilateur de Clean ne se soucie pas de l'encodage que vous pensez avoir enregistré le fichier source, juste des valeurs d'octets dans le fichier. Cela a des conséquences intéressantes.
Dans le corps du code source, seuls les octets avec des points de code correspondant aux caractères ASCII imprimables sont autorisés, en plus de ceux de
\t\r\n
.Littéraux:
Dans
String
et[Char]
littéraux ("stuff"
et['stuff']
respectivement), tous les octets à l'exception de 0 sont autorisés, avec la mise en garde que"
et'
doivent être échappés (pourString
et[Char]
respectivement), et que les retours à la ligne et les retours carraige doivent être remplacés par\n
et\r
(également respectivement).En
Char
littéraux, tout octet sauf 0 est autorisé, ce qui signifie que:Sont les mêmes, mais le second est un octet plus court.
Échapper:
À part les lettres d'échappement standard
\t\r\n
(etc.), toutes les séquences d'échappement non numériques de Clean sont destinées à la barre oblique ou à la citation utilisée pour délimiter le littéral dans lequel l'échappement se trouve.Pour les séquences d'échappement numériques, le nombre est traité comme une valeur octale terminée après trois chiffres. Cela signifie que si vous voulez un null suivi du caractère
1
dans aString
, vous devez utiliser"\0001"
(ou"\0\61"
) et non"\01"
. Cependant, si vous suivez l'évasion avec autre chose que des chiffres, vous pouvez omettre les zéros de tête.Conséquences:
Cette bizarrerie avec la façon dont Clean gère ses fichiers sources permet
String
et['Char']
devient effectivement des séquences de nombres à 256 chiffres à la base - ce qui a une multitude d'utilisations pour le code-golf, comme le stockage d'index (jusqu'à 255, bien sûr).la source
Fonctions de nom avec symboles
Lors de la définition d'une fonction, il est souvent plus court d'utiliser une combinaison de
!@$%^&*~-+=<:|>.?/\
que d'utiliser des caractères alphanumériques, car cela vous permet d'omettre les espaces blancs entre les identifiants.Par exemple:
?a=a^2
est plus court quef a=a^2
, et l'invoquer est également plus court.Cependant :
Si l'identifiant de fonction est utilisé à côté d' autres symboles, qui peuvent se combiner pour former un identifiant différent mais valide , ils seront tous analysés comme un identifiant et vous verrez une erreur.
Par exemple:
?a+?b
analyse comme? a +? b
Aditionellement:
Il est possible d'écraser les identifiants importés dans Clean, et donc les seuls identifiants de symboles à un seul caractère qui ne sont pas déjà utilisés dans le
StdEnv
sont@$?
. L'écrasement^-+
(etc.) peut être utile si vous avez besoin de plus d'identifiants symboliques, mais attention à ne pas écraser celui que vous utilisez.la source
Connaissez vos nœuds
KCertaines des constructions les plus solides (pour le golf) dans les langages fonctionnels sont
let ... in ...
.Propre bien sûr, a cela, et quelque chose de mieux - le
#
.Qu'est-ce qu'un nœud?
Les nettoyages
#
et l'omniprésence|
(pattern guard) sont tous deux connus comme des «expressions de nœuds».Notamment, ils vous permettent de programmer imperatively- ish en propre ( ce qui est vraiment bon ici!).
Le
#
(let-before):Ces deux calculent la valeur d'un entier donné sous forme de chaîne, multiplié par la somme de ses caractères
Notez comment la version avec
#
est plus courte et comment nous pouvons redéfinirs
. Ceci est utile si nous n'avons pas besoin de la valeur d'une variable lorsque nous la recevons, nous pouvons donc simplement réutiliser le nom. (let
peut rencontrer des problèmes lorsque vous faites cela)Mais l'utilisation
let
est plus facile lorsque vous avez besoin de quelque chose commeflip f = let g x y = f y x in g
Le
|
(modèle de garde):Le modèle Pattern Guard peut être utilisé comme ceux de nombreux autres langages fonctionnels - mais il peut également être utilisé comme un impératif
if ... else ...
. Et une version plus courte de l'expression ternaire.Par exemple, ceux-ci renvoient tous le signe d'un entier:
Bien sûr, la dernière qui utilise la garde plus traditionnellement est la plus courte, mais la première montre que vous pouvez les imbriquer (mais seules deux clauses de retour inconditionnelles peuvent apparaître sur la même ligne dans la règle de disposition), et la seconde montre ce que le le premier le fait logiquement.
Une note:
Vous pouvez utiliser ces expressions pratiquement n'importe où. En lambdas,
case ... of
,let ... in
, etc.la source
Si vous utilisez un,
String
vous devriez utiliserText
La conversion en cordes et la manipulation de cordes (le
{#Char}
/String
kind, pas le[Char]
kind) est assez longue et mauvaise pour le golf. LeText
module y remédie.Conversion:
Text
définit l'opérateur<+
pour deux types quelconques qui ont ététoString
définis.Cet opérateur, utilisé comme
a<+b
est le même quetoString a+++toString b
- économisant au moins 19 octets . Même si vous incluez l'importation supplémentaire,Text
et que vous ne l'utilisez qu'une seule fois, elle économise toujours 14 octets!Manipulation:
Text
définit quelques agrafes de manipulation de chaînes manquantes dansStdEnv
:+
pour les chaînes, qui est beaucoup plus court que+++
(deStdEnv
)indexOf
, avec le comportement de type C de retour-1
au lieu d'Nothing
échecconcat
, qui concatène une liste de chaînesjoin
, qui joint une liste de chaînes à l'aide d'une chaîne de séparationsplit
, qui divise une chaîne en une liste de chaînes sur une sous-chaînela source
Utilisez des lambdas plus courts
Parfois, vous vous retrouvez à utiliser une expression lambda (pour passer à
map
, ousortBy
, etc.). Lorsque vous faites cela (en écrivant des lambdas), il y a plusieurs façons de le faire.Le droit chemin:
C'est
sortBy
, avec une liste de tri lambda idiomatique du plus long au plus courtL'autre bonne façon:
Si vous utilisez
Data.Func
, vous pouvez également faireLe chemin court:
C'est la même chose, mais avec une syntaxe golfeur
L'autre côté:
L'utilisation de la composition n'est pas plus courte cette fois, mais elle peut parfois être plus courte
L'autre autre manière:
Bien que ce soit un peu artificiel ici, vous pouvez utiliser des gardes dans les lambdas
Et aussi des expressions de noeud avant
Une note:
Il existe deux autres formes de lambda,
(\a b . ...)
et(\a b -> ...)
, la dernière est identique à la=
variante, et la première existe pour une raison quelconque et semble souvent que vous essayez d'accéder à une propriété de quelque chose au lieu de définir une lambda, alors ne le faites pas ne l'utilise pas.la source
\a=...
était : P->
et=
pour lambdas sont identiques en ce qui concerne le compilateur (->
est l'ancienne syntaxe). Seul.
est différent (mais je ne sais pas exactement comment).on(<)length
, même si l'Data.Func
importation vous interrompra, sauf si vous en avez déjà besoin.#
) dans les lambdas.Utiliser des littéraux de liste de caractères
Un littéral de liste de caractères est une façon abrégée d'écrire quelque chose comme
['h','e','l','l','o']
as['hello']
.Ce n'est pas la limite de la notation, par exemple:
repeat'c'
devient['c','c'..]
devient['cc'..]
['z','y'..'a']
devient['zy'..'a']
['beginning']++[a,b,c]++['end']
devient['beginning',a,b,c,'end']
['prefix']++suffix
devient['prefix':suffix]
Ils fonctionnent également en correspondance:
['I don't care about the next character',_,'but I do care about these ones!']
la source
Parfois
code
plus courtClean a un tas de fonctions vraiment utiles dans les bibliothèques standard, dont certaines sont incroyablement verbeuses à utiliser sans accès
*World
et à l'aide*World
dans le code-golf est généralement une mauvaise idée de toute façon.Pour contourner ce problème,
ccall
vous pouvez souvent utiliser descode
blocs internes à la place.Quelques exemples:
Le temps du système
Ce qui précède est de 58 octets, mais vous pouvez économiser 17 octets (jusqu'à 40 + 1) avec:
Nombres aléatoires
Celui-ci ne sauvegarde pas les octets seul, mais évite d'avoir à faire circuler une liste de
genRandInt
Autres utilisations
En plus de ces deux, qui sont probablement les principales utilisations de ceci dans code-golf, vous pouvez appeler n'importe quelle fonction nommée (y compris, mais sans s'y limiter, chaque syscall), incorporer un assemblage arbitraire avec
instruction <byte>
et incorporer du code pour la machine ABC.la source