地球人のための関数型プログラミング-リスト

関数型プログラミングの簡単な紹介を続けます。 今回は、リストとそれらを機能的なスタイルで処理する方法について説明します。







だから、リスト。 AFの世界で彼がなぜそんなに重要視されているのですか? この質問に対する答えは、Lisp言語の概念的な基礎にあります。 リスト(ある形式または別の形式)は、プログラミング言語の必要なセマティック単位です。 リストがないと 、プログラム内の情報の任意の数のユニットを取得できません 。 一方、リストのみを追加すると、任意の複雑な-再帰的、無限(言語が遅延計算をサポートしている場合)のデータ構造を実装できます。 リスト+単純なデータ型は、プログラミング言語が必要とする最小限の要件です。 他のすべての複雑なデータタイプ-辞書、ツリー、グラフなど -リストを使用して実装できます。



だから、Lispの作成者は...を決定し、 プログラムがリストである言語を作成しました:)はい、すべてのLispプログラムは単なるリストです。 関数呼び出しは、関数名が最初に来て、その後に引数値が続くリストです。 関数定義は、最初にdefunキーワードを含むリストで、その後に関数名、引数名を含むネストされたリスト、次に演算子のリストが続きます。 などなど。 一見すると、これはかなりばかげて不便なように思えます-多くの人がLispに対する非難を信じられないほどの括弧で聞いています(リストは括弧に限定されています)。 しかし、一目で... プログラムがそのような単純化された構文構造を持っている場合、実行時にプログラムを直接修正することができます。



このため、Lispにはマクロメカニズムがあります-その実行結果が再実行される関数(動的プログラミング言語のeval



、はるかに柔軟性が高い)。 マクロメカニズムのおかげで、Lispは認識を超えて変更でき、新しい構文を導入して使用できます。 新しい演算子( for



ループのより便利な形式が必要ですか?Common Lispの壮大で柔軟なマクロfor



見てください-これは、通常のプログラミング言語のfor



ループの荒れた世界にあると言うことを恐れていません)。 構文的に言語に統合された独自のオブジェクトシステム(CLOSをご覧ください-マクロのセットでもありますが、言語の注入のように見えます)。 これがそのような柔軟性です。 もちろん、ハイライトブラケット付きのエディターが必要です-間違いなく:)



それでは、通常の命令型プログラミング言語でLispに戻りましょう-私たちにとってのリストは何ですか? リスト処理(配列、辞書)も、Pythonプログラムの大部分を占めています。 これには、データベースからのデータ選択の処理、構築する関数の計算、ファイルシステム内のファイルのリストの処理、ファイル内の行のリストの処理などが含まれます。



このような言語では、通常、さまざまな種類のループ-for、 while



do...while



を使用してリストを処理します。 これは問題ではありませんが、サイクル自体はセマンティックではありません。 つまり 彼はリストで何が行われているのか正確には言っていません。 ループの本体のコードを読み取り、それが何をするのかを理解する必要があります。 Lispで表されるFPは、リストを操作するためのより洗練された方法を提供します(これには、リストの一般的な変更操作-並べ替え、反転、連結などは含まれません)。







通常、これらの3つの機能は、リスト処理に関連するほとんどの問題に帰着します。 しかし、常にではありません。 たとえば、左巻きと右巻きの畳み込みが発生します。 リストをフィルターする必要はありませんが、何らかの基準に従ってリストを2つの部分に分ける必要があります。 はい、あなたは何を知っていることはありません。 一番下の行は、リストを入力として受け取り、元のリストを変更せずにリストまたは出力で単純な値を出力する特定の関数があることです。



Pythonでは、上記の関数は、同じ名前の関数の形式と、奇妙な名前リストの理解の下にある構文糖衣の形式の両方で存在します( リストを理解するなど、正しく理解できません)。



リスト内包表記(LC)





最も単純なLC構文は次のとおりです。



 [xのaの式(a)]




ここで、 x



はリスト、 a



はリスト項目、 expression(a)



は通常aが参加する式です。 LCはであり、その結果はリストです。 セマンティック用語では、上記のLCは次の形式のmap



関数に対応します。



マップ(ラムダa:式(a)、x)




さらに、最後の角括弧の前に、 if



分岐があるif



があります。



 [式(a)in in if if条件(a)]




ご想像のとおり、これはfilter



類似していfilter



。 関数を使用して、この式を次のように書き換えることができます。



 map(lambda a:expression(a)、 
  フィルター(ラムダa:条件(a)、x))




reduce



に構文上の類似物はありません。 LCの主な主要な目標は、リストを作成することです。 もう1つの興味深い点に注意する価値があります。 map



関数は複数のリストを受け入れることができます。 この場合、コンバーター関数が呼び出されるたびに、いくつかの引数が渡されます。最初の引数は最初のリストの現在の要素の値、2番目の引数は2番目のリストの現在の要素の値などになります。



地図(
  ラムダa1、a2、a3:a1 + a2 + a3、 
   [1、2、3]、 
   [4、5、6]、 
   [7、8、9])




LCには一見似たデザインがあります。



 [x1のa1、x2のa2の式(a1、a2)]




しかし、悲しいかな、これは同じではありません。 この構成の結果は、リストのデカルト積になりますが、実際にはあまり適用されません。 例:



 [[a]、[b]、[c]のa1の[a1 + a2] [[x]、[y ']]のa2の




=> ['ax', 'ay', 'bx', 'by', 'cx', 'cy']









実際には、LCは1から10までの数の2乗を取得するなど、単純なネストされていない操作に使用すると便利です。他の場合(以下の複雑な例を参照)は関数を使用することをお勧めします。



簡単な例





以下のこの投稿のすべての例のコードを参照してください。 次のようなリストを見てみましょう。



 x = [2、3、4、5、7、5]




簡単なものから始めましょう。 たとえば、リストのすべての要素を2乗します。



マップ(ラムダa:a ** 2、x)

 #同じことだがLC
 [a ** 2 in x]




=> [4, 9, 16, 25, 49, 25]







ここでフィルタリングを適用します-すべての偶数を除外します:



フィルター(ラムダa:a%2 == 1、x)

 #同じことだがLC
 [a%2 == 1の場合、a in x]




=> [3, 5, 7, 5]







次に結合します-リスト番号の奇数の二乗を導き出します:



フィルター(ラムダa:a%2 == 1、 
   map(lambda a:a ** 2、 
     x))

 #同じことだがLC
 [a ** 2 in a ** 2 a ** 2%2 == 1]




=> [9, 25, 49, 25]







ご覧のとおり、最初のケースでは、最初にリストを表示し、次に結果をフィルタリングします。 それでは、 reduce



遊んでみましょう。 開始するには、リスト内のすべての数値の合計を印刷します。



削減(ラムダa、b:a + b、x、0)




=> 26







既に理解しているように、最初のパラメーターはレデューサー関数(この場合は加算器)です。 2番目のパラメーターはリストであり、3番目は初期値または初期化子です。 正しい初期化子を選択することの重要性を示すために、乗算の場合と同じ例を示します。



削減(ラムダa、b:a * b、x、0)




=> 0







ここで0が得られました。そして、当然のことながら、次の式が満たされていることがわかりました:((((((((* 0 * 2)* 3)* 4)* 5)* 7)* 5)。 この例を修正するには、初期化子の値を1に設定します。



削減(ラムダa、b:a * b、x、1)




=> 4200







これで正しい値が得られました。 次に、リストから最大値を取得してみます。



削減(ラムダa、b:最大(a、b)、x)




=> 7







初期化子はなくなりました。 初期化子が指定されていない場合は、代わりにreduce



代わりにNone



を使用します。 このコードの動作は、視覚的に最も簡単に説明できます。







最後に、畳み込みによってリストを反転するタスクを取ります。 番号2、3、4、5、7、5のリストがあることを思い出させてください。逆のリストは次のようになります。5、7、5、4、3、2。ブラケットを配置して、適用する必要がある操作を確認しましょう。レデューサー関数:(5、(7、(5、(4、(3、(2))。明らかに、これは前の畳み込みステップの結果の先頭に新しいリスト項目追加する操作です。初期化子は空のリストである必要があります :( 5、(7、(5、(4、(3、(2、[]))。次に、特定のコードを記述します。



削減(ラムダa、b:[b] + a、x、[])




=> [5, 7, 5, 4, 3, 2]







もう一度、わかりやすくするために、視覚的に表示します。







この投稿では、リストを操作する単純な「非生命」の例のみを検討しましたが、在庫のより実行可能な例があり、パフォーマンス、適用性に関するいくつかの考慮事項とともに、すぐに新しい投稿に投稿しますこれは、コンピューターサイエンスの他の分野や一般に反映されています。 これが誰かにとって興味深いものになることを願っています。 シムについては、休暇を取りたいと思います。ご清聴ありがとうございました。



All Articles