コードファイトを使用したタスク例の最小のHaskellについて

KDPV(アーティストによる提示)
関数型プログラミングに興味がある場合、またはゆっくりと習得しようとする場合でも、通常の命令型アプローチとの主な違いは、プログラムが一般的なものから特定のものまで構築され、その逆ではないという事実を何度も聞いたことがあるでしょう。 つまり 最初に何を取得したいかを決定し、次にそれを達成する方法を決定します。 このような単純なことは、通常、脳に休息を与えず、有用なものを書こうとする試みで多くのフラストレーションを引き起こすと思われる。 この話があなたに関するものであるか、または少しHaskellとFPの学習に興味がある場合は、読み続けてください。それがいかに簡単かを示します。 「説明する時間がない、書く」というスタイルの記事。


codefights.comコンテストを完了した翌朝、この記事のアイデアが思い浮かびました 。ここでは、最短の解決策を得るためにさらに10文字をカットする必要がありました。 他のソリューションを見て、午前5時に決定を完了せず、1つのハックの使用を忘れなかった場合、それは最短であり、最も重要なこととして、シンプルさと理解しやすさを保つことがわかりました。 ちょっと待って、javascriptまたはpythonで書いているプログラマーにいくつかのニュアンスを説明したら、彼はすぐにそれを理解できると思ったので、Habrでそれについての記事を書きます!


だから挑戦。 男がいます。 男は駅に来て、毎日通過する電車の中で車を数えるのが好きです。 彼はそれをとても愛しているので、毎日そこに来ます。 時々、デュードはボウリングをして、白いロシア人を飲みに行きます。 そして、それは数日間現実から外れる可能性があります。 男が来ると、彼は最初に彼が逃した車の数を疑問に思います。 あなたはこれらの計算でおいを助ける必要があります。 列車内の車の数は可変であることが知られています。 月の最初の日に、列車は1つのキャリッジのみで構成されます。
1つの馬車で構成される列車(アーティストが提示)
翌日には、さらに2台の車があります。
3台の車で構成される列車(アーティストが提示)
など、来月の初めまで。 合計: month ∈ [1..12] 、日日month ∈ [1..12] 1. month ∈ [1..12] 、ボウリングに費やされた日数n ∈ [0..365] 、デュードが見逃す車の数を示す整数を返す必要があります。 たとえば、 month=1, day=1, n=1回答1です。 なぜなら、月の最初の日に列車は1台の馬車で構成され、1日だけがスキップされるからです。 その最初の日。 month=5, day=1, n=5答え25
月の日に対する車の数の比率(アーティストが提示)
month=2, day=1, n=30答えは788(描画できませんか?)などになります。


Haskellを入れてお気に入りのエディターを dude.hs、空の dude.hs ファイルを作成し実行可能にするchmod +x dude.hs (何もコンパイルせず、Haskellはスクリプト言語として正常に動作します)、 #!/usr/bin/env runhaskellをファイルの最初に書きます#!/usr/bin/env runhaskellと開始する準備ができました(または単にサービスを使用します)


 #!/usr/bin/env runhaskell dude month day n = … 

dude関数の名前。次に来るのは引数だけです。同じことが関数の本体に渡された後(これについては後で説明します)。


結果として何を得たいですか? その結果、逃した車の数を取得したいと思います。 この数は何に等しいですか? 男が駅に来なかったすべての日のワゴンの量。 したがって、最終的には、金額が必要です。 プレリュード -デフォルトですべてのモジュールにインポートされる標準ライブラリ-で探します。 うまく検索すると、 sum関数が見つかります。リスト内のすべての数値を合計します。 また、型、モナド、およびそれらがどのようにあなたを怖がらせるかについては何も言っていないことに注意してください。 実際、Haskellははるかにシンプルで、現代のjavascriptに非常によく似ています(javascriptから構文ゴミを捨てて、最終的にHaskellを取得する方法についてのLev Valkin投稿を思い出せませんか)。 ただし、次のことに気付きました。


 dude month day n = sum … 

まあ、私たちが見つける方法を知っている量。 不足しているワゴンのリストが必要になりました。 このとんでもない省略記号の代わりにテストリストを代用することから始めましょう。プログラムがまったく機能することを確認してください。 このように、例えば:


 dude month day n = sum [1, 3, 5, 7, 9] 

実行: ./dude.hs 動かない わいせつを宣誓しますか? エントリポイント、 main関数が必要なので、そうです。 その1つは、答えを文字列に変換して画面に印刷する必要があることです。 この関数は私たちに適しています:


 main = putStrLn (show (dude 1 1 1)) 

おおおおおお! 一度に非常に多くのブラケット、ほとんどLisp。 Haskellの関数呼び出しは左結合であるため、このように配置する必要がありました。 つまり 括弧を入れて記述しなかった場合:


 main = putStrLn show dude 1 1 1 

Haskellは、これを行うことを決定します。


 main = ((putStrLn show) dude) 1 1 1 

それは真実ではありません。 正しいmain関数で、最初に3つの引数(現時点では幸いなことに無視されます)で男を呼び出し、次にshow使用してこの関数の結果を文字列show変換し、その後putStrLnを使用して文字列を画面にputStrLnます。 間違った関数では、 putStrLnshow関数を印刷しようとします(あなたが推測するかもしれませんが、失敗します)、そして、 dude関数は仕事の結果への唯一の引数として与えられます(これは関数であるべきですが、そうなることはありません)、そしてこれがこのソドミーの結果です関数であることが判明します)これらの3つの引数が供給されます-それは機能しません。 したがって、ブラケットを配置しました。 また、素晴らしい演算子$もあります。これは、Haskellに次のように語っています。「最初に、自分の右にあるものをすべて実行し、結果を自分の場所に置きます。」 したがって、次のように正しいmain関数を書き換えることができます。


 main = putStrLn $ show $ dude 1 1 1 

カブに関するおとぎ話のようなものです。 印刷したいのですが、最初に文字列に変換する必要があります。 文字列に変換したいのですが、最初に男を呼び出します。 男は3つの引数を取り、金額を返します。 まあ、量は簡単に画面に簡単に印刷される文字列に変換されます。 いいね!


車に戻ります。 ここで、。 ./dude.hsを実行すると、テストリストの数値の合計である25得られます。 実際のリストを作成する方法は? 思い出させてください。Dudeが駅に来なかった当時の車の長さのリストを取得したいのです。 まあ、多分私たちは一年中毎日ワゴンのリストを作成し、それからその男がそうではなかった日からそれを選ぶでしょうか? あなたのことは知りませんが、私はこの考えが好きです。 プレリュードに登り、 take機能を見つけます。


 dude month day n = sum $ take n $ … 

リストからn日を取得しましたが、これらは年の最初の月の最初のn日になります。 take関数は、リストの先頭から最初のn値をtakeます。 良くない。 月の初めからdayを退却する必要があります(実際には、不在の最初の日も考慮されるため、 day - 1 )。 私たちは燃えているモスクワを後退させ、 dropの助けを借りdrop


 dude month day n = sum $ take n $ drop (day - 1) $ … 

そして、別のmonth - 1年の初めからmonth - 1か月。 しかし、この事実を少し後で考慮します。 ここでは数日間を扱っているため、数か月間だけ退却することはできません。 毎月、その日数は、論理と常識の影響を受けません。 それでは、新しい行のどこかに、毎月の日のリストを書き、便利にしましょう。


 months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 

そして、あなたは、デュードが31日に始まり、9日にのみ終わることが起こるかもしれないので、あなたは知っています。 この事実を計算で考慮する必要があります。 リストの最後(12月)から最初(1月)までスキップします。 怠azine。 このリストを無限のリストに変えて、常に前方にのみ進むことをお勧めします。


 months = cycle [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 

cycle機能は、終了リストに元のリストを循環的に繰り返します。 およそ次のように配置されます。


 cycle list = list ++ cycle list 

++演算子が2つのリストを1つに追加し、 listがリストであり、無限にしたい場合。 これはどのように可能ですか? これは無限の再帰です! もちろん、メモリは有限ですが、生成される再帰とリストはそうではありませんか? はい、それだけです。 実際、Haskellは怠け者で(プログラマのように、偶然のように)、したがって、結果が必要になるまで何もしません(プロラストネーション?)。 したがって、ここでは無限のリスト(およびツリー、そして他に無限の可能性があるもの、人間の愚かさ)を扱うことができますが、そのようなリストの有限のサブセットで作業するのとまったく同じです。 このように書いた場合:


 months = cycle [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] dude month day n = sum months --  

そのHaskellは、実行時間が超過しているため、4秒で死にます(ちなみに、自分で確認してください)。 無限リストの要素を無期限に要約する必要があるためです。 数え切れないほどのリストからたったn日しか取らないのは良いことです。
それでは、最も重要なことに移り、月のリストから日のリストを作成して、それぞれの車の台数を計算します。 ユニバース全体で何か(月のリスト)から別の何か(日のリスト)に移動するには、 map使用しmap


 months = cycle [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] to_days max_days = … dude month day n = sum $ take n $ drop (day - 1) $ map to_days months 

map関数への最初の引数は、月を日リストに変換する関数です。まだそれを思いつきません。 2番目の引数は無限のリストです。 心配しないでください、 mapも怠け者で、必要以上に進むことはありません。
今、非常に簡単です:


 months = cycle [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] to_days max_days = [1..max_days] dude month day n = sum $ take n $ drop (day - 1) $ map to_days months 

[1..max_days]は、1からmax_daysまでのすべての整数のリストを作成する構文糖衣です。 ここでプログラムを実行すると、何も機能しなくなります。 実際、 map呼び出すto_days関数は、1つの数値のみを受け取ります。これは、月の日リストの要素であり、今月の日リスト全体を返します。 リスト内のリストが判明します。
これがマップの仕組みみです(アーティストによる)
concatを呼び出して、リストを1次元にフラット化できます。


 months = cycle [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] to_days max_days = [1..max_days] dude month day n = sum $ take n $ drop (day - 1) $ concat $ map to_days months 

そしてすぐにconcatMap使用できます:


 months = cycle [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] to_days max_days = [1..max_days] dude month day n = sum $ take n $ drop (day - 1) $ concatMap to_days months 

少し残っています。 では、この日のワゴンのリストを、日のリストから作成しましょう。 条件は、月の初日に列車に1台の貨車があり、その後さらに2台が追加されることを示しています。 これを別の行に書きます:


 number_of_wagons x = x*2 - 1 

もちろん、 xは月の日数です。 この関数を前のステップの日数のリストにマッピングし、 month - 1か月をmonth - 1ことを忘れないでください:


 #!/usr/bin/env runhaskell months = cycle [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] to_days max_days = [1..max_days] number_of_wagons x = x*2 - 1 dude month day n = sum $ take n $ drop (day - 1) $ map number_of_wagons $ concatMap to_days $ drop (month - 1) months main = putStrLn $ show $ dude 1 1 1 

始めます。 動作します。 回答1 。 他の値、たとえば3 2 4を試します。 答えは24です。 では、12月31日について仮説をテストしてみましょう。 初期データ: 12 31 10 、回答142 。 多すぎる! ただし、答えは正しいです。




最短の解決策はどこですか? そのようなことがあり、一般に、文字mの単語があります。そして、私はそれを同じ形式で説明する方法を理解していません。 まあ、一般的に、多くの落書きがありました、1つの記事のためにバストがあるでしょう。 次回。
ラムダ(アーティストが代表)



Source: https://habr.com/ru/post/J319440/


All Articles