Cleanでのゴルフのヒント


17

クリーンでゴルフをするための一般的なヒントは何ですか?一般的なゴルフの問題をコード化するのに適用でき、少なくともCleanに特定のアイデアのみを投稿してください。

クリーンについて聞いたことがない場合は、こちらで詳細を確認できます。
または、チャットルームに参加できます。

回答:


10

import StdEnv可能な限り避ける

アクセスするには組み込み関数、のようにも一見ベーシックなもの(==)map、import文は通常、必要とされるimport StdEnvことが最も一般的なモジュールが好きインポートしているためStdIntStdBoolなど(参照にここで詳細はStdEnv)。

ただし、いくつかの課題のためにこのインポートを回避し、リスト内包表記やパターンマッチングなどのコア言語機能を使用することもできます。

たとえば、代わりに

import StdEnv 
map f list

書ける

[f x\\x<-list]

代替のリスト:

を必要import StdEnvとする一部の関数または関数呼び出し、インポートを必要としない代替、および保存されたバイトの大まかな見積もり。

  • hd-> (\[h:_]=h)、最大6バイト
  • tl-> (\[_:t]=t)、最大6バイト
  • map f list-> [f x\\x<-list]、最大10バイト
  • filter p list-> [x\\x<-list|p x]、最大11バイト
  • (&&)-> %a b|a=b=a;%、最大6バイト
  • (||)-> %a b|a=a=b;%、最大6バイト
  • not-> %a|a=False=True;%、最大1バイト
  • and-> %[a:r]|a= %r=a;%_=True、〜0バイト
  • or-> %[a:r]|a=a= %r;%_=False、〜0バイト

最後のいくつかは実際にバイトを節約する可能性は低いです。なぜなら、直接置換はインポートよりも多くのバイトを生成するからです。

このヒントは、ここで使用されています


ただし、import StdEnv+ a and b(21バイト)は%[a:r]|a= %r=a;%_=True(22バイト)より小さくありませんか?または、import StdEnv+ a=True and b=True(31バイト)になりますが、その場合は実際に確実に短くなりますか?(私はところで、クリーンでプログラムたことがありません。)
ケビンCruijssen

@KevinCruijssen チャットでそれについて話し合っていたところです。とにかくプログラムがリストを再帰する必要がある場合を除いて、バイトを節約する可能性は低いことは事実です。
ライコニ

4
わかった 代替案で保存されたバイト数map f list -> [f x\\x<-list] (11 bytes saved)(または(または同様の))を示すことも役立つかもしれません。
ケビンクルーッセン

@KevinCruijssenできました。
ライコニ

5

言語を学ぶ方法を知っている

結局のところ、誰もが使用できない言語でゴルフをすることができます!

オンライン

Cleanはよく知られた言語でも文書化された言語でもありません。名前は確かにこれらの問題を解決するために必要なリソースを見つけるのを容易にしません...

クリーンはもともとコンカレントクリーンと呼ばれていましたれ、Cleanに関連するほとんどすべてのドキュメントの序文でまだ使用されています。したがって、Cleanを探している場合は、代わりにConcurrent Cleanを探してください。

CleanのHaskell(その多くがあります)とのより顕著な類似点の1つは、Cloogleの存在です。

局所的に

Cleanに同梱されているライブラリは、IDEを使用して参照できる、適切にコメントされた、ある程度自己文書化されたCleanソースファイルの形式です。
(また、完全なサンプルプログラムが付属してい$INSTALL/Examplesます。)

そういえば、WindowsバージョンのCleanにはIDEが付属しています。最新の標準ではかなり制限されていますが、テキストエディターとコマンドラインを使用するよりも優れています。
(学習のコンテキストで)最も便利な2つの機能は次のとおりです。

  • エラーをダブルクリックして、どの行にあるかを確認できます
  • モジュール名を強調表示し、を押し[Ctrl]+[D]て定義ファイルを開き(または[Ctrl]+[I]実装ファイルに使用)、定義ファイルと実装ファイルを切り替えることができます[Ctrl]+[/]

4

文字エンコードを忘れる

Cleanのコンパイラは、ソースファイルを保存したエンコーディングとは関係なく、ファイル内のバイト値だけを考慮します。これにはいくつかのきちんとした結果があります。

ソースコードの本文では、に加えて、印刷可能なASCII文字に対応するコードポイントを持つバイトのみが許可され\t\r\nます。

リテラル:

Stringおよび[Char]リテラル("stuff"および['stuff']それぞれ)、0以外の任意のバイトは、その警告で、許可されている"'(のためにエスケープする必要がありString、および[Char]それぞれ)、およびその改行とcarraige戻るしなければならないと置き換えること\n\r(それぞれ)。

ではCharリテラル、0以外の任意のバイトはその意味を、許可されています:

'\n'

'
'

同じですが、2番目は1バイト短くなっています。

エスケープ:

標準の文字エスケープ\t\r\n(など)を除き、Cleanのすべての非数値エスケープシーケンスは、スラッシュ、またはエスケープが含まれるリテラルを区切るために使用される引用のいずれかです。

数値エスケープシーケンスの場合、数値は3桁で終了する8進数値として扱われます。つまり、nの後1にa の文字が続く場合はString"\0001"(または"\0\61")を使用する必要があり、ではありません "\01"。あなたは何でエスケープをたどる場合は、しかし、数字、あなたが先頭のゼロを省略することができます。

結果:

Cleanがソースファイルをどのように処理するかというこの奇妙なことはString['Char']256を超える1桁の数字のシーケンスになることを効果的に可能にします。


3

関数にシンボルを付ける

関数を定義するとき!@$%^&*~-+=<:|>.?/\、識別子間の空白を省略することができるため、英数字を使用するよりもいくつかの組み合わせを使用する方が短いことがよくあります。

たとえば、:?a=a^2はよりも短くf a=a^2、呼び出しも短くなります。

ただし

関数識別子が他のシンボルに隣接して使用されている場合、それらは結合して別の有効な識別子を形成できるため、すべて1つの識別子として解析され、エラーが表示されます。

たとえば、次のように?a+?b解析します? a +? b

さらに:

Cleanでインポートされた識別子を上書きすることが可能であるため、まだ使用されStdEnvていない単一文字のシンボル識別子はのみ@$?です。上書き^-+(など)は、より多くのシンボリック識別子が必要な場合に役立ちますが、使用している識別子を上書きしないように注意してください。


3

Kノードを知る

関数型言語で最も強力な構造(ゴルフ用)の一部は次のとおりlet ... in ...です。
もちろんきれいで、これがあり、より良いものがあります- #

ノードとは何ですか?

Clean's #およびユビキタス|(パターンガード)は、どちらも「ノード式」として知られています。
特に、彼らはあなたがimperatively-プログラムできるようっぽいクリーンで(ここでは本当に良いです!)。

#(レット前):

これらは両方とも文字列として与えられた整数の値を計算し、その文字の合計を乗算します

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

のバージョンがどのように#短くなり、どのように再定義できるかに注意してくださいs。これは、変数を受け取るときに変数が持っている値が必要ない場合に便利です。そのため、名前を再利用できます。(letそれを行うと問題が発生する可能性があります)

しかし、使用して let次のようなものが必要な場合はが簡単ですflip f = let g x y = f y x in g

|(パターンガード):

Cleanのパターンガードは、他の多くの関数型言語と同様に使用できますが、命令型のように使用することもできます if ... else ...ます。そして、三項式の短縮版。

たとえば、これらはすべて整数の符号を返します。

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

もちろん、より伝統的にガードを使用する最後のものが最短ですが、最初のものはネストできることを示しています(ただし、レイアウトルールの同じ行に表示できる無条件のreturn句は2つだけです)。最初は論理的に行います。

注:

これらの式は基本的にどこでも使用できます。ラムダではcase ... oflet ... inなど、


1

を使用しているString場合は、使用する必要がありますText

文字列への変換、および文字列の操作(種類ではなく{#Char}/ 種類)は非常に長く、ゴルフには適してStringいません[Char]Textモジュールの救済本。

変換:

Text定義さ<+れている任意の2つのタイプの演算子をtoString定義します。
として使用されるこの演算子a<+bは、少なくとも19バイトをtoString a+++toString b節約します。追加のインポートを含めて,Text1回だけ使用しても14バイト節約されます!

操作:

Textから欠落しているいくつかの文字列操作ステープルを定義しますStdEnv

  • +文字列の演算子。これは+++(からStdEnv)よりずっと短い
  • indexOf、失敗の-1代わりに戻るというCのような動作Nothing
  • concat、文字列のリストを連結します
  • join、区切り文字列を使用して文字列のリストを結合します
  • split、文字列を部分文字列の文字列のリストに分割します

1

短いラムダを使用する

ラムダ式を使用している場合があります(map、またはsortByなどに渡すため)。これを行う(ラムダを書く)とき、それを行う方法はたくさんあります。

正しい方法:

これはsortBy、慣用的なラムダソートリストを最長から最短に並べたものです

sortBy (\a b = length a > length b)

他の正しい方法:

を使用している場合はData.Func、次のこともできます

sortBy (on (>) length)

短い方法:

これは同じですが、ゴルファーの構文を使用します

sortBy(\a b=length a>length b)

反対に:

今回はコンポジションの使用が短くなることはありませんが、時には短くなることもあります

sortBy(\a=(>)(length a)o length)

他の方法:

ここでは少し工夫されていますが、ラムダでガードを使用できます

sortBy(\a b|length a>length b=True=False)

また、let-beforeノード式

sortBy(\a b#l=length
=l a>l b)

注:

2つのがあり、よりラムダのフォーム、あり(\a b . ...)(\a b -> ...)同じとなっている、後者=の変異体は、およびそのうちの前者は、何らかの理由で存在し、多くの場合、あなたが何かのプロパティにアクセスしようとする代わりに着るので、ラムダを定義しているように見えます使用しないでください。


1
ゴルフプログラムをいくつか見て、Cleanの通常のラムダ構文であるという印象\a=... 受けました:P
ØrjanJohansen

使用されるように、ラムダにガードを追加することもできます ここでいるます。これは文書化されていません(言語レポートとも矛盾します)が、機能します。また、->そして=ラムダのために限りコンパイラが懸念しているものと同一です(->古い構文です)。.異なるだけです(しかし、私は正確にどのように知りません)。

この特定の例on(<)lengthでは、を使用することを検討できますが、Data.Funcインポートは既に必要でない限り分割されます。

@キーランクール。本日はこれを後で更新します。#ラムダでlet-before()も使用できると思います。
Οurous

はい、できます:-)

0

文字リストリテラルを使用する

文字リストのリテラルは次のようなものを書くの簡略な方法である['h','e','l','l','o']として['hello']

これは、表記の制限ではありません。たとえば、次のとおりです。

  • repeat'c'['c','c'..]なる['cc'..]
  • ['z','y'..'a'] になる ['zy'..'a']
  • ['beginning']++[a,b,c]++['end'] になる ['beginning',a,b,c,'end']
  • ['prefix']++suffix になる ['prefix':suffix]

これらはマッチングでも機能します。

  • ['I don't care about the next character',_,'but I do care about these ones!']

0

時々code短い

Cleanには、標準ライブラリに非常に便利な関数がたくさんありますが、その一部は、アクセスせずに使用すると信じられないほど冗長になります *Worldで、*Worldコード・ゴルフでは、とにかく、一般的に悪い考えです。

この問題を回避するには、しばしば ccallcode代わりにブロック内で使用できるます。

いくつかの例:

システム時刻

import System.Time,System._Unsafe
t=toInt(accUnsafe(time))

上記は58バイトですが、次のようにして17バイト(40 + 1まで)節約できます。

t::!Int->Int
t _=code{ccall time "I:I"
}

乱数

これはそれ自体でバイトを節約しませんが、リストを渡す必要がないようにします genRandInt

s::!Int->Int
s _=code{ccall time "I:I"ccall srand "I:I"
}
r::!Int->Int
r _=code{ccall rand "I:I"
}

その他の用途

おそらくcode-golfでの主な用途であるこれら2つに加えて、任意の名前付き関数(すべてのsyscallを含むがこれに限定されない)を呼び出し、で任意のアセンブリをinstruction <byte>埋め込み、ABCマシンのコードを埋め込むことができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.