Scal関数式プログラミングのテーマ--scala集合と関数


前情摘要:
Scal関数式プログラミングのテーマ――関数式思想紹介
scala関数式プログラミングテーマ――scala基礎文法紹介
前にはscalaの常用文法と対象に向けての簡単な知識を紹介しましたが、今回は前の章を追加して、集合と関数を紹介します。
注意してください。関数と方法は違っています。方法はクラスで定義されています。関数は単独で存在することができます。
一.scala集合紹介
前の章で紹介したobjectのapply方法を覚えていますか?多くのデータ構造は実際にそれを使っていますので、直接List(...)を使ってListを作成します。自分で手動newを使わないでください。
PS:scalaのデフォルトのデータ構造は可変ではなく、つまりListでは、新しい要素を削除または追加することができません。もちろん、「可変」のデータ構造もあります。後で紹介します。
1.1 List
apply法のおかげで、newを通してデータ構造を新規に作成することができます。

//    ,    List,  List     
scala> val numbers = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
numbers: List[Int] = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
1.2元グループTuple
Tupleという概念はpythonで広く応用されています。多様なデータタイプ(Int、String、Doubleなど)を一緒に包装することができます。

scala> val tup = (1,1,2.1,"tuple",'c') //             ,     
tup: (Int, Int, Double, String, Char) = (1,1,2.1,tuple,c)
しかし、scalaでは、Tupleは長さ制限があります。つまり、一つのTupleは最大22個の要素しかありません。

scala> val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
tup: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)

//  Tuple  22   ,   
scala> val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)
<console>:1: error: too many elements for tuple: 23, allowed: 22
val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)
上のように見えますが、一つのTupleが22つの要素を超えると、エラーが発生します。なぜ22という不思議な数字なのかというと、一貫した論調はないようです。23才だと冗談を言っている人がいます。映画の名前は「The Number 23」です。
1.3 MapとOption
Mapを言う前に、Optionを紹介します。MapにはOptionがありますので、この後に紹介します。
Option翻訳はオプションです。本質的には、存在しないかを教えてくれるオプションです。実例をもって説明しましょう。

//Option           
scala> val optionInt = Option(1)
optionInt: Option[Int] = Some(1)

scala> optionInt.get
res8: Int = 1
//   Option     
scala> val optionNone = Option(null)
optionNone: Option[Null] = None
// Option       , get     ,    
scala> optionNone.get
java.util.NoSuchElementException: None.get
 at scala.None$.get(Option.scala:347)
 at scala.None$.get(Option.scala:345)
 ... 32 elided
//             
scala> optionNone.isEmpty
res11: Boolean = true
//    getOrElse()  , Option        ,       ,      ,   getOrElse()      
scala> optionNone.getOrElse("this is null") //     
res12: String = this is null
scala> optionInt.getOrElse("this is null") //     
res15: Any = 1
それにMap、Mapの中のvalueのタイプはあなたが割り当てたデータタイプではなく、Optionです。つまりMap(key->>Option、key 1->Option)です。

scala> val map = Map("test1" -> 1,"test2" -> 2)
map: scala.collection.immutable.Map[String,Int] = Map(test1 -> 1, test2 -> 2)

scala> map.get("test1")
res16: Option[Int] = Some(1)

scala> map.get("test3")
res17: Option[Int] = None

scala> map.get("test3").getOrElse("this is null")
res18: Any = this is null
このようなメリットは何ですか?javaの中で、毎回javaが空の状況を判断する苦痛を覚えていますか?scalaでは、これらの悩みは存在しません。Optionがあったら、お母さんはもう心配しなくても大丈夫です。
1.4常用関数の組み合わせ
匿名関数
セットの関数をサブに結合するには、まず匿名関数を使う必要があります。pythonのlamband表現を使ったことがあれば、このような方式をよく知っているはずです。
前述のように、scalaでは関数がオブジェクトであり、匿名関数も関数である。簡単な例を挙げます。

//        
scala> val addOne = (x: Int) => x + 1
addOne: (Int) => Int = <function1>

scala> addOne(1)
res4: Int = 2
なお、関数内では、returnを使わなくてもいいです。一番後ろのx+1は匿名関数の戻り値です。
map,reduce
hadoopが出てきたので、MapReduceは腐ったと言われました。HadoopのMapreduceは関数式のmapとreduceから起源しますが、両者は違います。興味があるのは前に書いた本を見てもいいです。分治アルゴリズムからHadoop MapReduceまで。
関数式の中のmapは、大まかに言えば、戻り値があるforサイクルに相当します。

scala> val list = List(1,2,3)
list: List[Int] = List(1, 2, 3)

scala> list.map(_ + 1) //   (_+1)           // List      +1,   
res29: List[Int] = List(2, 3, 4)
reduceというのは、forサイクルのように、mapのように毎回循環するのが現在の要素ではないので、reduceは現在の要素以外に、前回の循環の結果があります。例を見てみましょう。

scala> list.reduce((i,j) => i + j) //        ,        
res28: Int = 6
例えば上記の例は、1、2、3個の数があります。一回目の循環の二つは1、2、1+2は3に等しく、2回目の循環は前回の結果3と元の3、3+3は6に等しく、結果は6です。
filter
filter故名思とはフィルタリングという意味で、filterから匿名関数が入ってブール値に戻ります。trueを返したら保留してfalseの廃棄に戻ります。

scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)

//    2  0 
scala> numbers.filter((i: Int) => i % 2 == 0)
res0: List[Int] = List(2, 4)
foldLeft
これはreduceと似ています。現在の要素以外にも前回の繰り返しの結果があります。違いはFoldLeftには初期値があります。

scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)

//(m: Int, n: Int) => m + n          
scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n)
res30: Int = 10
二.scala関数
一番前に紹介されていますが、関数は対象です。なぜ関数は直接運行されますか?これは実はobjectのapplyという文法飴のおかげです。
偏関数
偏関数は、ある意味、偏関数もscalaの中でとても重要な文法飴です。
このような偏関数の長さ:
PartalFunction[A,B]//Aタイプのパラメータを受信し、Bタイプのパラメータを返します。
ちなみに、scalaにはキーワードcaseがあります。偏り関数を使います。次の例:

//   case       PartialFunction[Int, String]
scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
one: PartialFunction[Int,String] = <function1>

scala> one(1)
res11: String = Int

scala> one("one")
<console>:13: error: type mismatch;
 found : String("one")
 required: Int
 one("one")
caseキーワードは条件に合った種類や値にマッチします。内部実装は紹介しません。常用文法飴だと知ったらいいです。
このcaseキーワードもscalaモードマッチングを実現するために必要な一環です。興味のある子供靴は私のこの本を見てもいいです。
ここでもう一つよく使うようにしましょう。関数の組み合わせにはcollectがあります。必要なパラメータは偏り関数です。面白い例を見ます。

//  list   Any  
scala> val list:List[Any] = List(1, 3, 5, "seven")
list: List[Any] = List(1, 3, 5, seven)

//  map   ,  map         
scala> list.map { case i: Int => i + 1 }
scala.MatchError: seven (of class java.lang.String)
 at $anonfun$1.apply(<console>:13)
 at $anonfun$1.apply(<console>:13)
 at scala.collection.immutable.List.map(List.scala:277)
 ... 32 elided

 //    collect     ,  collect         ,              ,                 
scala> list.collect { case i: Int => i + 1 }
res15: List[Int] = List(2, 4, 6)
collectで受信したパラメータは偏り関数なので、自動的に偏り関数の特性を使って、不適合なデータタイプを自動的にフィルタリングします。mapはできません。
部分関数
一部アプリケーションという意味は、一つの関数を呼び出すと、一部のパラメータだけを伝えることができるということです。このようにして新しい関数が生成されます。例を挙げてみましょう。

//               
scala> def partial(i:Int,j:Int) : Unit = {
 | println(i)
 | println(j)
 | }
partial: (i: Int,j: Int)Unit

//           ,        ,        
scala> val partialFun = partial(5,_:Int)
partialFun: Int => Unit = <function1>

//            
scala> partialFun(10)
5
10
部分的な応用関数は、主にコード多重化という役割を果たし、同時に一定のコード可読性を高めることができる。
もちろんもっと面白い使い方があります。後でまた話す機会があります。
関数コリック化
最初、カリー化と聞いた時はおかしいです。コリ?何ですか?
後になって分かったのですが、実はケリーから音訳されたのです。これは人名で、カリー化の発明者です。
コリゼーションは、複数のパラメータを受け入れる関数を単一のパラメータ(最初の関数の最初のパラメータ)を受け入れる関数に変換し、残りのパラメータを受け入れて結果を返す新しい関数の技術です。
具体的にどのように使うかを見てください。

//            ,        
scala> def curring(i:Int)(j: Int): Boolean = {false}
curring: (i: Int)(j: Int)Boolean

//              ,       
scala> val curringVal:(Int => (Int => Boolean)) = curring _
curringVal: Int => (Int => Boolean) = <function1>

//             ,         
scala> val curringVal_1 = curringVal(5)
curringVal_1: Int => Boolean = <function1>

//            ,        
scala> curringVal_1(10)
res32: Boolean = false
実はカリー化は一つの関数を一つのチェーンに変える過程で、上の部分の応用関数と似ているように見えます。
これらの部分は初めて見て、どのような用途があるのか分かりませんが、実はこれらの機能の一つの主な用途は関数式の依存注入です。この部分技術により,依存関数をパラメータの形で上位関数に渡すことができる。紙面に限りますが、ここは省略して、後で紹介します。
結語:
今回紹介したのはscala集合のいくつかの内容と関数の特性です。もう一度言ってください。関数は対象です。
私はいつも一つの観点を持っています。新しいものを勉強する時、文法のような固定的な規則のものがあります。全部覚えなくてもいいです。大体の原理を知っていれば、映像があればいいです。
例えば、scalaの関数式プログラミングとか、javaのOOPとか、先に文法を勉強してから関連のプログラミング理念を学ぶ必要はないです。これは私から見れば本末転倒です。
私の一般的なやり方はまず大体の文法を熟知して、それから言語の精髄を学びます。分からないことがあったら、逆に具体的な文法を調べて、目標があったら、文法はかえってつまらなくなります。
以上は私の個人的な見方だけです。これで本編は終わります。
ここでScara関数式プログラミングのテーマであるscala集合と関数に関する記事を紹介します。もっと関連したscala関数式プログラミングの内容は私達の以前の文章を検索してください。または次の関連記事を引き続きご覧ください。これからもよろしくお願いします。