シンプルなタスクと機能的な金髪のアプローチ

悲しい少女とラムダ式



数ヶ月前、私は独学にコミットしました。 他のオフィスにもそのようなトピックがあります。従業員は6か月ごとにレビューし、「次のレビューに来て、春、パターン(何?)そして関数型プログラミングを勉強します!」と言います。 春とパターンをスキップしましょう-週末に急いでFPを攻撃しました。



もちろん、FPについての一般的で曖昧な情報は持っていました-Javaで匿名クラスを書くことができます-PythonとJavaScriptの同様の機能に精通しています。



まず、Scalaでの簡単な演習から始めます。 Scalaを選んだのは、主な作業ツールとしてJavaを使用しているからです-まあ、Clojure、Groovy、およびJava8(他の何か)のバリアントもありましたが、多分後で試してみます。



自分で目標を設定しました(AFを正しく理解しましたか?):





簡単な演習もあれば、控えめに言ってもそれほどではない演習もありました。 ここで、これについて簡単に話してみます-新しい知識を合理化します。 この記事が将来誰かを助けることができるのか、それとも、誰かがエラーを指摘したり、改善を提案することで私を助けるのかどうかを言うのは困難です。



合計とフィルタリング



最初の段階-ロックのダウンロードと「メインの起動方法」を求めてのグーグル検索はスキップします。 管理-そして大丈夫。



最初の例として、 ProjectEulerを使用し最初のタスク1〜999の数字の合計を3または5で除算します。一般に、FizzBu​​zzのように見えます。



Googleは範囲の生成とフィルタリングの例を見つけるのを助けてくれました。

object Main extends App { def res = (1 to 999).filter(x => x % 3 == 0 || x % 5 == 0) System.out.println(res.sum) }
      
      







しかし、私は書いて考えました:既成の範囲タスクと既成の合計関数を使用します。 関数の集約を漠然と思い出し、10分後にreduceを使用して合計を書き直しました(Pythonから思い出しました)。 そして、1から999までの数字のリストを生成する方法は? 約1時間の苦痛の後、私は再帰関数を生成しました(申し訳ありませんが、それなしではできませんでした)。



 import scala.annotation.tailrec import scala.collection.immutable.Vector object Main extends App { @tailrec def genList(sz: Int, res: Vector[Int]): Vector[Int] = { if (sz == 0) res else genList(sz - 1, sz +: res) } def res = genList(999, Vector()).filter(x => x % 3 == 0 || x % 5 == 0) System.out.println(res.reduce((a, b) => a + b)) }
      
      







もちろん、これはもうしません-何らかのライブラリ関数の書き方を知っていれば、それを使用できると信じています。



UPD

コメントのヒントの後、私はストリームを生成に使用できることに気づきました(後で知りました)-正しい方向へのキックに感謝します:

  def list = Stream.iterate(1)(x => x + 1).takeWhile(x => x < 1000) def res = list.filter...
      
      







入出力



コンソールから1つの番号を入力するのは難しくありませんでした。 例として、古い問題の1つである三角数を選択しました 。 入力した数値が三角形かどうかを答える必要があります。 三角形の数のリストをい方法で作成し、そこに何かが入力されているかどうかを確認しましたが、マップ関数(主にJavaScriptでよく知っている)を習得しました。



 import scala.io.StdIn object Main extends App { def tris = (1 to 500).map(n => n * (n + 1) / 2) val x = StdIn.readLine.toInt System.out.println(if (tris.contains(x)) "YES" else "NO") }
      
      







分岐せずに、それはまだうまくいきません-私は彼らが小さいことを確信しています。



たくさんの数字を入力する必要がある場合はどうしますか? 数組の数字を合計する練習をしました 。 最初にペアの数が表示され、次に別の行にペア自体が表示されます。



私はより一般的なタスクを得ました-各行の金額を見つける必要があります(ペアのオプション):



 import scala.io.StdIn object Main extends App { val n = StdIn.readLine.toInt val samples = Iterator.continually(StdIn.readLine).take(n).toList val output = samples.map((x) => x.split(" ").map(_.toInt).sum) System.out.println(output.mkString(" ")) }
      
      







サイクル、変換、出力で数えるために、もう5つの同様の問題を決めました。疲れるまでです。



「昼食前」のストリームと反復



他の子供向けの本のCollat​​z仮説を覚えています-その後、ほぼ1日、紙の上で97番の数字をチェックしました(成功しませんでした)。 適切なタスクを見つけたので、私はすぐにそれをクラックすると思いましたが、実際には次の日だけそれをマスターしました。



最初に、再帰関数を使用して作成しました(上記と同様)が、その後、より既製のアプローチを探し始めました。 これのおかげで、Stream、iterate、takeWhileに出会いました。



そのため、行に数値を設定します。各数値のCollat​​z変換が1つになるまでの反復回数を計算する必要があります。



 import scala.io.StdIn object Main extends App { def collatz(a:Long) = if (a % 2 == 1) a * 3 + 1 else a / 2 val input = StdIn.readLine.split(" ").map(_.toLong) val output = input.map(m => Stream.iterate(m)(collatz).takeWhile(_ > 1).size) System.out.println(output.mkString(" ")) }
      
      







それは非常に短かったので、私はすでにトラブルに対応する準備ができていると思っていました。 しかし、いくつかの演習の後、私は真の災害に直面しました。



単純な数字



トライアル部門を使用して生成するために使用された素数I(主にJavaをマスターしたときからProjectEulerで同じ演習を使用)。 機能的な形式では、(少なくとも私にとって)書くのは非常に難しいことが突然判明しました。 結局、サイクルでは、ますます多くの新しい数値をチェックして、結果の配列に追加する必要があります。これは同時にこれを検証するために使用されます...



問題は次のとおりです。特定のインデックスに対応するインデックス番号を使用して素数を導出します。 配列を生成するだけで十分だと思いました-そして...



半日を費やして、私はすでに手がかりを求めてインターネットを閲覧することに着手しました。 残念ながら、 この「ワンライナー」のようなフリークは、私を解決策に近づけなかっただけでなく、逆に自分自身の劣等感を強めました。



最終的に、すでに見つかった番号と次の検証済み番号のリストが渡される繰り返し関数を発明することで勝ちました。 これがこのモンスターです:



 def generate(n: Int) = { @tailrec def mainLoop(test: Int, found: Vector[Int]): Vector[Int] = { if (found.size == n) { return found } mainLoop(test + 2, if (found.find(test % _ == 0).nonEmpty) found else found :+ test) } mainLoop(3, Vector(2)) } val values = StdIn.readLine.split(" ").map(_.toInt) val primeList = generate(values.max) val output = values.map(x => primeList(x - 1)) System.out.println(output.mkString(" "))
      
      







私はもっ​​と短い解決策を持っていましたが、それらは100未満の数では満足に機能しました-暗黙の内部反復のために明らかに速度が低下しました...



私は何が起こったのが好きではありません。 機能的スタイルの素数の生成に関する明らかに科学的な記事を見つけましたが、AFではエラトステネスのふるいでアプローチを試みることが好ましいと思われます。 しかし、ふるいを不変に埋める方法を思い付くには、まだScalaに弱いと感じています。 今-このテキストを書いている間-不変のHashSetのようなものを使うべきだと思いました。



実験の継続について書けるようになるまでに(これが関連する場合)、よりよい解決策が得られることを願っています。



おわりに



これについて、私の不運について読んでくれてありがとう。 まず第一に、私がそれらについて書いたのは、何らかの理由で出会ったAFのいくつかのチュートリアルが、機能的なスタイルで簡単に書くことができる例を示し、脳が膨らむものを避けたためです。



他のエクササイズに気づいた場合-見た目はシンプルですが、機能的な形で実装するのはそれほど簡単ではありません(少なくとも初心者向け)-それらを共有してください!



All Articles