Pavel Fatin Scala Collections Tips and Tricksの記事の翻訳を紹介します。 PavelはJetBrainsで働いており、IntelliJ IDEAのScalaプラグインを開発しています 。 さらに、物語は著者に代わっています。
この記事では、ScalaコレクションAPIの日常的な使用に共通する単純化と最適化について説明します 。
一部のヒントは、コレクションライブラリの実装の複雑さに基づいていますが、ほとんどのレシピは、実際には見落とされがちなスマートな変換です。
このリストは、 Scala IntelliJプラグインのために、 Scala コレクションの実用的な検査を開発しようとする私の試みに触発されました。 現在、これらの検査を実装しているので、IDEAでScalaプラグインを使用すると、静的コード分析から自動的に恩恵を受けます。
ただし、これらのレシピはそれ自体で価値があります。 これらは、Scala標準コレクションライブラリの理解を深め、コードをより高速で表現力豊かにするのに役立ちます。
更新:
冒険心があるなら、
適切なインスペクションを選択することで、 Scala用のIntelliJプラグインの開発を支援し、実装を試す方法を学ぶことができます。
内容:
1. 2. 3. 4. (Sequences) 4.1. 4.2. 4.3. 4.4. 4.5. 4.6. 4.7. 4.8. 4.9. 4.10. 5. (Sets) 6. Option- 6.1. 6.2. Null 6.3. 6.4. 7. 8.
すべてのサンプルコードはGitHubのリポジトリから入手できます 。
1.凡例
コード例をわかりやすくするために、次の表記を使用しました。
-
seq
は、Seq(1, 2, 3)
ようなSeq(1, 2, 3)
ベースのコレクションのインスタンスです -
set
-Set
インスタンス、たとえばSet(1, 2, 3)
-
array
-Array(1, 2, 3)
などのArray(1, 2, 3)
-
option
-Option
インスタンス、たとえばSome(1)
-
map
-Map
類似したMap
インスタンスMap(1 -> "foo", 2 -> "bar")
-
???
-任意の表現 -
p
-T => Boolean
型の関数の述部、たとえば_ > 2
-
n
は整数値です -
i
は整数インデックスです -
f
、g
は単純な関数、A => B
-
x
、y
いくつかの任意の値 -
z
初期値またはデフォルト値 -
P
パターン
2.構成
レシピは孤立していて自給自足であるという事実にもかかわらず、次のより高度な表現への漸進的な変換のために調整できることを忘れないでください。
seq.filter(_ == x).headOption != None // seq.filter(p).headOption seq.find(p) seq.find(_ == x) != None // option != None option.isDefined seq.find(_ == x).isDefined // seq.find(p).isDefined seq.exists(p) seq.exists(_ == x) // seq.exists(_ == x) seq.contains(x) seq.contains(x)
そのため、「交換処方アプリケーションモデル」( SICPに類似)に依存し、それを使用して複雑な表現を簡素化できます。
3.副作用
「サイド効果」は、主要な変換をリストする前に考慮すべき基本的な概念です。
実際、副作用とは、値を返すことに加えて、関数または式の外側で観察されるアクションのことです。例えば:
- 入出力操作
- 変数の変更(スコープ外で利用可能)
- オブジェクトの状態の変化(スコープ外で観察)
- 例外をスローします(スコープ内でも処理されません)。
上記のアクションのいずれかを含む関数または式は副作用があると言われ、そうでなければ「純粋」と呼ばれます。
なぜ副作用がそれほど重要なのですか? 彼らと実行の順序が重要だからです。 たとえば、2つの「純粋な」式(対応する値に関連付けられています):
val x = 1 + 2 val y = 2 + 3
それらには副作用(すなわち、式の外側で観察される効果)が含まれていないため、これらの式を任意の順序で計算できます-最初にx
、次にy
または最初にy
、次にx
これは得られた結果の正確さに影響しません(必要に応じて、結果の値をキャッシュすることもできます)。 次に、次の変更を検討します。
val x = { print("foo"); 1 + 2 } val y = { print("bar"); 2 + 3 }
これは別の話です-ターミナルでは「foobar」の代わりに「barfoo」を出力するため、実行順序を変更することはできません(これは明らかに意図したものではありません)。
そのため、副作用の存在は、コードに適用できる変換 (単純化と最適化の両方) の数を減らします。
同様の考慮事項は、関連するコレクション、式にも適用されます。 範囲外のどこかに特定のbuilder
があると想像してください:
seq.filter(x => { builder.append(x); x > 3 }).headOption
原則として、 seq.filter(p).headOption
呼び出しはseq.filter(p).headOption
簡略化されていますが、副作用seq.find(p)
ため、これを行うseq.find(p)
ん。
seq.find( x => {builder.append(x); x > 3 })
これらの式は最終値の点では同等ですが、副作用の点では同等ではありません。 最初の式はすべての要素を追加し、最後の式は述語に一致する最初の値を見つけるとすぐにすべての要素を破棄します。 したがって、このような単純化はできません。
自動簡素化を可能にするために何ができますか? 答えは、コード内のすべての副作用(原則としてコレクションがないものを含む)に関して従うべき黄金律です:
- 可能な限り副作用を避けてください。
- それ以外の場合、副作用をクリーンなコードから分離します。
したがって、 builder
aを(副作用があるAPIとともに)取り除くか、 builder
aの呼び出しを純粋な式から分離する必要があります。 このbuilder
は、削除できないサードパーティのオブジェクトであると想定します。そのため、呼び出しのみを分離できます。
seq.foreach(builder.append) seq.filter(_ > 3).headOption
これで、安全に変換を実行できます。
seq.foreach(builder.append) seq.find(x > 3)
清潔で美しい! 副作用の分離により、自動変換が可能になりました。 追加の利点は、明確な分離が存在するため、結果のコードを人が理解しやすくなることです。
副作用を分離することの最も明白で、同時に最も重要な利点は、他の可能な最適化に関係なく、コードの信頼性を向上させることです。 例について:初期式は、 Seq
現在の実装に応じて、さまざまな副作用を引き起こす可能性があります。 たとえば、 Vector
場合、すべての要素が追加され、 Stream
場合、述語との最初の一致が成功した後、すべての要素がスキップされます(ストリームは「遅延」であるため、要素は必要な場合にのみ計算されます)。 副作用の分離により、これらの不確実性を回避できます。
4.シーケンス
このセクションのアドバイスはSeq
相続人に適用されますが、一部の変換は他のタイプのコレクション(コレクションではなく)に有効です。たとえば、 Set
、 Option
、 Map
さらにはIterator
(これらはすべてモナドメソッドと同様のインターフェースを提供するため)
4.1作成
空のコレクションを明示的に作成する
// Seq[T]() // Seq.empty[T]
一部の不変のコレクションクラスには、 empty
メソッドのシングルトン実装がありempty
。 ただし、すべてのファクトリメソッドが作成されたコレクションの長さをチェックするわけではありません。 そのため、コンパイル段階でvoidを指定することにより、ヒープ上のスペース(インスタンスの再利用による)またはプロセッサーサイクル(実行時の次元チェックに費やすことができる)を節約できます。
以下にも適用可能: Set
、 Option
、 Map
、 Iterator
。
4.2長さ
配列の場合、 size
代わりにlength
を使用しsize
// array.size // array.length
size
とlength
は本質的に同義ですが、Scala 2.11ではArray.size
呼び出しは暗黙の変換によって行われ、各メソッド呼び出しの中間ラッパーオブジェクトを作成します。 もちろん、JVMのエスケープ分析を有効にしない限り、一時オブジェクトはガベージコレクターの負担になり、コードのパフォーマンスを低下させます(特にループ内)。
isEmptyを拒否しないでください
// !seq.isEmpty !seq.nonEmpty // seq.nonEmpty seq.isEmpty
単純なプロパティは、複合式よりも視覚的なノイズが少ないです。
以下にも適用可能: Set
、 Option
、 Map
、 Iterator
。
voidをチェックするときに長さを計算しないでください。
// seq.length > 0 seq.length != 0 seq.length == 0 // seq.nonEmpty seq.nonEmpty seq.isEmpty
一方では、単純なプロパティは複合式よりもはるかに簡単に認識されます。 一方、 LinearSeq
相続人( List
など)は、リストの長さを計算するために( IndexedSeq
O(1)
代わりにO(n)
時間を必要とする場合があるため、一般的に、長さの計算を避けることでコードを高速化できます、この値はあまり必要ありません。
無限ストリームの.length
呼び出しは決して終了しない可能性があるため、常に明示的にストリームの空性を確認してください。
適用対象: Set
、 Map
。
比較中にコレクションのフルサイズを計算しないでください。
// seq.length > n seq.length < n seq.length == n seq.length != n // seq.lengthCompare(n) > 0 seq.lengthCompare(n) < 0 seq.lengthCompare(n) == 0 seq.lengthCompare(n) != 0
コレクションのサイズの計算は、一部のタイプのコレクションでは非常に「高価な」計算になる可能性があるため、 LinearSeq
( Seq
値で非表示にできるO(length min n)
の比較時間をO(length)
からO(length min n)
にLinearSeq
できます。 さらに、無限のストリームを処理する場合、このアプローチは不可欠です。
emptyをチェックするためにexists
を使用しないでください。
// seq.exists(_ => true) seq.exists(const(true)) // seq.nonEmpty
もちろん、このようなトリックは完全に冗長です。
適用対象:セット、オプション、マップ、イテレーター。
4.3平等
配列の内容を比較するために==
に依存しないでください
// array1 == array2 // array1.sameElements(array2)
等価性テストは、配列のさまざまなインスタンスに対して常にfalse
を返します。
該当するもの: Iterator
異なるカテゴリのコレクションの同等性をテストしないでください
// seq == set // seq.toSet == set
等価性チェックを使用して、コレクションと異なるカテゴリ(例: List
とSet
)を比較できます。
このチェックの意味について再考してください(上記の例-シーケンス内の重複をどのように考慮するか)。
sameElements
を使用して通常のコレクションを比較しないでください
// seq1.sameElements(seq2) // seq1 == seq2
同等性テストは、同じカテゴリのコレクションを比較する方法です。 理論的には、これにより、潜在的なインスタンスチェックが原因でパフォーマンスが向上することがあります( eq
、通常ははるかに高速)。
明示的に対応を使用しないでください
// seq1.corresponds(seq2)(_ == _) // seq1 == seq2
同じことを行う組み込みメソッドが既にあります。 両方の式では、要素の順序が考慮されます。 繰り返しになりますが、生産性の向上の恩恵を受けることができます。
4.4索引付け
インデックスで最初のアイテムを取得しないでください
// seq(0) // seq.head
一部のコレクションクラスでは、更新されたアプローチが少し速くなる場合があります(たとえば、 List.apply
コードを確認してください)。 さらに、プロパティへのアクセスは、引数を使用してメソッドを呼び出すよりもはるかに簡単です(構文的にも意味的にも)。
インデックスで最後のアイテムを取得しないでください
// seq(seq.length - 1) // seq.last
最後の式はより明確で、コレクションの長さの不必要な計算を避けます(そして線形シーケンスの場合、これには多くの時間がかかります)。 さらに、一部のコレクションクラスは、インデックスアクセスよりも効率的に最後のアイテムを取得できます。
コレクション内のインデックスを明示的にチェックしないでください
// if (i < seq.length) Some(seq(i)) else None // seq.lift(i)
意味的には、2番目の式は同等ですが、より表現的には
headOptionをエミュレートしないでください
// if (seq.nonEmpty) Some(seq.head) else None seq.lift(0) // seq.headOption
簡略化された式はより簡潔です。
lastOption
エミュレートしない
// if (seq.nonEmpty) Some(seq.last) else None seq.lift(seq.length - 1) // seq.lastOption
最後の式は短くなっています(そして潜在的に高速です)。
indexOf
およびlastIndexOf
引数タイプに注意してください
// Seq(1, 2, 3).indexOf("1") // Seq(1, 2, 3).lastIndexOf("2") // // Seq(1, 2, 3).indexOf(1) Seq(1, 2, 3).lastIndexOf(2)
分散のindexOf
により、 indexOf
メソッドとlastIndexOf
メソッドはAny
型の引数を受け入れます。 実際には、これにより、コンパイル段階で検出できないバグを見つけにくくなります。 IDEのサポート検査が行われる場所です。
シーケンスインデックスの範囲を手動で作成しないでください
// Range(0, seq.length) // seq.indices
シーケンス内のすべてのインデックスの範囲を返す組み込みメソッドがあります。
コレクションをインデックスに手動で関連付けるためにzipを使用しないでください
// seq.zip(seq.indices) // seq.zipWithIndex
まず、最後の式が短くなります。 さらに、コレクションのサイズの隠された計算(線形シーケンスの場合は高価になる可能性がある)を避けることにより、パフォーマンスの向上が期待できます。
後者の式の追加の利点は、無限のコレクション(例: Stream
)でうまく機能することです。
IndexedSeqインスタンスを関数オブジェクトとして使用します。
// (seq: IndexedSeq[T]) Seq(1, 2, 3).map(seq(_)) // Seq(1, 2, 3).map(seq)
IndexedSeq[T]
インスタンスはFunction1[Int, T]
でもあるため、そのまま使用できます。
4.5存在
比較述語を使用して要素をチェックしないでください
// seq.exists(_ == x) // seq.contains(x)
2番目の式は意味的に同等ですが、より表現力豊かです。 これらの式がSet
に適用されると、セット検索はO(1)
なる傾向があるため、パフォーマンスが劇的に異なる可能性がありますO(1)
exists
場合exists
内部インデックスが使用されないため)。
以下にも適用可能: Set
、 Option
、 Iterator
。
contains
れる引数のタイプに注意してください
// Seq(1, 2, 3).contains("1") // // Seq(1, 2, 3).contains(1)
indexOf
やlastIndexOf
ように、タイプAny
引数を取るメソッドがcontains
ます。これにより、コンパイル段階では検出されないバグを見つけにくくなります。 それらに注意してください。
不等式述部を使用して、欠落している要素をチェックしないでください
// seq.forall(_ != x) // !seq.contains(x)
繰り返しますが、最後の式はよりクリーンで、おそらくより高速です(特にセットの場合)。
以下にも適用可能: Set
、 Option
、 Iterator
。
発生をカウントして存在を確認しない
// seq.count(p) > 0 seq.count(p) != 0 seq.count(p) == 0 // seq.exists(p) seq.exists(p) !seq.exists(p)
明らかに、条件に一致するアイテムがコレクション内にあるかどうかを知る必要がある場合、一致するアイテムの数をカウントすることは冗長です。 簡略化された式は、よりクリーンで高速に見えます。
- 述語
p
は純関数でなければなりません。 - 該当するもの:
Set
、Map
、Iterator
。
存在を確認するためにフィルタリングに頼らないでください
// seq.filter(p).nonEmpty seq.filter(p).isEmpty // seq.exists(p) !seq.exists(p)
filter
呼び出しは、ヒープのスペースを占有してGCをロードする中間コレクションを作成します。 さらに、前述の式はすべてのオカレンスを検出しますが、最初の式のみを検出する必要があります(コレクションの可能な内容によってはコードが遅くなる可能性があります)。 遅延コレクション( Stream
や、特にIterator
)の場合、潜在的なパフォーマンスの向上はそれほど重要ではありません。
- 述語
p
は純関数でなければなりません。 - 以下にも適用可能:
Set
、Option
、Map
、Iterator
。
存在を確認するために検索に頼らないでください
// seq.find(p).isDefined seq.find(p).isEmpty // seq.exists(p) !seq.exists(p)
検索はフィルタリングよりも間違いなく優れていますが、これは制限からはほど遠いです(少なくとも明確さの点では)。
以下にも適用可能: Set
、 Option
、 Map
、 Iterator
。
4.6フィルタリング
フィルター述語を拒否しないでください
// seq.filter(!p) // seq.filterNot(p)
最後の式は構文的に単純です(意味的に同等であるという事実にもかかわらず)。
以下にも適用可能: Set
、 Option
、 Map
、 Iterator
。
カウントするためにフィルタリングしないでください
// seq.filter(p).length // seq.count(p)
filter
呼び出しは、中間の(あまり必要ではない)コレクションを作成します。これは、ヒープのスペースを占有し、GCをロードします。
以下にも適用可能: Set
、 Option
、 Map
、 Iterator
。
最初の出現を見つけるためにフィルタリングを使用しないでください
// seq.filter(p).headOption // seq.find(p)
もちろん、 seq
遅延コレクション( Stream
など)でない場合、最初の要素のみが必要であるという事実にもかかわらず、フィルタリングはすべての発生を検出(および一時コレクションを作成)します。
以下にも適用可能: Set
、 Option
、 Map
、 Iterator
。
4.7並べ替え
プロパティで手動で並べ替えないでください
// seq.sortWith(_.property < _.property) // seq.sortBy(_.property)
これを行うには、より明確で表現力豊かな独自の方法があります。
IDで手動で並べ替えないでください
// seq.sortBy(identity) seq.sortWith(_ < _) // seq.sorted
そして、このための方法もあります。
ワンステップで逆ソート
// seq.sorted.reverse seq.sortBy(_.property).reverse seq.sortWith(f(_, _)).reverse // seq.sorted(Ordering[T].reverse) seq.sortBy(_.property)(Ordering[T].reverse) seq.sortWith(!f(_, _))
したがって、中間コレクションの作成を回避し、追加の変換を排除できます(ヒープスペースとプロセッササイクルを節約するため)。
ソートを使用して最小要素を見つけないでください
// seq.sorted.head seq.sortBy(_.property).head // seq.min seq.minBy(_.property)
後者のアプローチはより表現力豊かです。 さらに、追加のコレクションが作成されないという事実により、より高速に動作します。
最大の要素を見つけるためにソートを使用しないでください
// seq.sorted.last seq.sortBy(_.property).last // seq.max seq.maxBy(_.property)
説明は前のヒントと同じです。
4.8畳み込み
金額を手動で計算しないでください
// seq.reduce(_ + _) seq.fold(z)(_ + _) // seq.sum seq.sum + z
このアプローチの利点は、明快さと表現力です。
- その他の可能な方法:
reduceLeft
、reduceRight
、foldLeft
、foldRight
。 -
z
が0
等しい場合、2番目の変換は最初の変換に置き換えることができます。 - 該当するもの:
Set
、Iterator
。
作業を手動で計算しないでください
// seq.reduce(_ * _) seq.fold(z)(_ * _) // seq.product seq.product * z
理由は、前のケースと同じです。
-
z
が1
場合、2番目の変換は最初の変換に置き換えることができます。 - 該当するもの:
Set
、Iterator
。
最小アイテムを手動で検索しないでください
// seq.reduce(_ min _) seq.fold(z)(_ min _) // seq.min z min seq.min
理論的根拠は前のケースと同じです。
該当するもの: Set
、 Iterator
。
最大アイテムを手動で検索しないでください
// seq.reduce(_ max _) seq.fold(z)(_ max _) // seq.max z max seq.max
すべて前のケースと同じです。
該当するもの: Set
、 Iterator
。
forall
エミュレートしない
// seq.foldLeft(true)((x, y) => x && p(y)) !seq.map(p).contains(false) // seq.forall(p)
簡素化の目標は、明快さと表現力です。
- 述語
p
は純関数でなければなりません。 - 以下にも適用可能:
Set
、Option
(2行目)、Iterator
。
エミュレートしない
// seq.foldLeft(false)((x, y) => x || p(y)) seq.map(p).contains(true) // seq.exists(p)
すべての明快さと表現力で、最後の式はより高速に動作し(最初の適切な要素が見つかるとすぐに後続の要素の処理を停止します)、無限のシーケンスで機能します。
- 述語
p
は純関数でなければなりません。 - 以下にも適用可能:
Set
、Option
(2行目)、Iterator
。
map
エミュレートしません
// seq.foldLeft(Seq.empty)((acc, x) => acc :+ f(x)) seq.foldRight(Seq.empty)((x, acc) => f(x) +: acc) // seq.map(f)
これは、畳み込みによるマップ(マップ)の関数型プログラミング実装の「クラシック」です。 間違いなく有益であるが、使用する必要はない。 これを行うには、組み込みの表現力豊かなメソッドを使用します(これは、実装で単純なwhile
使用するため、高速です)。
以下にも適用可能: Set
、 Option
、 Iterator
。
filter
エミュレートしない
// seq.foldLeft(Seq.empty)((acc, x) => if (p(x)) acc :+ x else acc) seq.foldRight(Seq.empty)((x, acc) => if (p(x)) x +: acc else acc) // seq.filter(p)
理由は、前のケースと同じです。
以下にも適用可能: Set
、 Option
、 Iterator
。
reverse
エミュレートしない
// seq.foldLeft(Seq.empty)((acc, x) => x +: acc) seq.foldRight(Seq.empty)((x, acc) => acc :+ x) // seq.reverse
繰り返しになりますが、組み込みのメソッドはより高速でクリーンです。
以下にも適用可能: Set
、 Option
、 Iterator
。
4.9比較
Scalaでのパターンマッチングと部分関数のスタンドアロンのヒントを次に示します 。
パターンマッチングを持つ関数の代わりに部分関数を使用する
// seq.map { _ match { case P => ??? // x N } } // seq.map { case P => ??? // x N }
更新された式は同様の結果を与え、よりシンプルに見えます。
上記の変換は、 map
関数の引数だけでなく、任意の関数に適用できます。 このヒントはコレクションに限定されません。 ただし、ScalaコレクションライブラリAPIの高階関数の普遍性を考慮すると、この関数は便利です。
flatMap
collect
// seq.flatMap { case P => Seq(???) // x N case _ => Seq.empty } // seq.collect { case P => ??? // x N }
.
: Set
, Option
, Map
, Iterator
.
match
collect
,
// v match { case P => Seq(???) // x N case _ => Seq.empty } // Seq(v) collect { case P => ??? // x N }
, case- , , match
collect
. , case
.
Option
, .
: Set
, Option
, Iterator
.
collectFirst
// seq.collect{case P => ???}.headOption // seq.collectFirst{case P => ???}
, .
- .
- :
Set
,Map
,Iterator
.
4.10
filter
// seq.filter(p1).filter(p2) // seq.filter(x => p1(x) && p2(x))
( filter
), .
, ( ), : seq.view.filter(p1).filter(p2).force
.
-
p1
p2
. - :
Set
,Option
,Map
,Iterator
.
map
// seq.map(f).map(g) // seq.map(f.andThen(g))
, .
, view ( ), : seq.view.map(f).map(g).force
.
-
f
g
. - :
Set
,Option
,Map
,Iterator
.
// seq.sorted.filter(p) // seq.filter(p).sorted
— . , .
- ,
sortWith
sortBy
. -
p
.
map
// seq.reverse.map(f) // seq.reverseMap(f)
() , ( List
). , , , .
// seq.reverse.iterator // seq.reverseIterator
.
Set
// seq.toSet.toSeq // seq.distinct
( ), .
slice
// seq.drop(x).take(y) // seq.slice(x, x + y)
, . , .
: Set
, Map
, Iterator
.
splitAt
// val seq1 = seq.take(n) val seq2 = seq.drop(n) // val (seq1, seq2) = seq.splitAt(n)
( List
, Stream
), - , .
: Set
, Map
.
span
// val seq1 = seq.takeWhile(p) val seq2 = seq.dropWhile(p) // val (seq1, seq2) = seq.span(p)
, .
-
p
. - :
Set
,Map
,Iterator
.
partition
// val seq1 = seq.filter(p) val seq2 = seq.filterNot(p) // val (seq1, seq2) = seq.partition(p)
-,
-
p
. - :
Set
,Map
,Iterator
.
takeRight
// seq.reverse.take(n).reverse // seq.takeRight(n)
( , ).
flatten
// (seq: Seq[Seq[T]]) seq.reduce(_ ++ _) seq.fold(Seq.empty)(_ ++ _) seq.flatMap(identity) // seq.flatten
: .
: Set
, Option
, Iterator
.
flatMap
// (f: A => Seq[B]) seq.map(f).flatten // seq.flatMap(f)
- . , .
: Set
, Option
, Iterator
.
map
// seq.map(???) // // seq.foreach(???)
, map
. , .
: Set
, Option
, Map
, Iterator
.
unzip
// (seq: Seq[(A, B]]) seq.unzip._1 // seq.map(_._1)
, - .
- :
unzip3
. - :
Set
,Option
,Map
,Iterator
.
( ).
1) .
// seq.map(f).flatMap(g).filter(p).reduce(???) // seq.view.map(f).flatMap(g).filter(p).reduce(???)
reduce
, , : reduceLeft
, reduceRight
, fold
, foldLeft
, foldRight
, sum
, product
, min
, max
, head
, headOption
, last
, lastOption
, indexOf
, lastIndexOf
, find
, contains
, exists
, count
, length
, mkString
, ..
— , , - , GC. , ( map
, flatMap
, filter
, ++,
..) «» ( Stream
) , , .
(view) — , , :
- "" — .
- (
Stream
).
, view
.
2) , .
, - — force
( ):
// seq.map(f).flatMap(g).filter(p) // seq.view.map(f).flatMap(g).filter(p).force
— , , , withFilter
:
seq.withFilter(p).map(f)
"for comprehensions". , — , (, ). , ( ) ( veiw
force
)
, withFilter
- .
3) .
// seq.map(f).flatMap(g).filter(p).toList // seq.view.map(f).flatMap(g).filter(p).toList
force
-, .
« + ». breakOut
:
seq.map(f)(collection.breakOut): List[T]
, :
, , breakOut
.
.
- (
f
g
) (p
) ( , , ). - :
Set
,Map
.
// seq = seq :+ x seq = x +: seq seq1 = seq1 ++ seq2 seq1 = seq2 ++ seq1 // seq :+= x seq +:= x seq1 ++= seq2 seq1 ++:= seq2
Scala « », « » (“assignment operators”) — x <op>= y
x = x <op> y
, : <op>
(: +
, -
, .). , <op>
:
, - (.. , ). :
// list = x :: list list1 = list2 ::: list1 stream = x #:: stream stream1 = stream2 #::: stream1 // list ::= x list1 :::= list2 stream #::= x stream1 #:::= stream2
.
Set
, Map
, Iterator
( ).
// seq.foldLeft(Set.empty)(_ + _) seq.foldRight(List.empty)(_ :: _) // seq.toSet seq.toList
, , . , , .
: Set
, Option
, Iterator
.
toSeq
.
// (seq: TraversableOnce[T]) seq.toSeq // seq.toStream seq.toVector
- , Seq(...)
( , Vector ), toSeq
( Stream
, Iterator
view
) . TraversableOnce.toSeq
Stream
, , . , , .
:
val source = Source.fromFile("lines.txt") val lines = source.getLines.toSeq source.close() lines.foreach(println)
IOException
, , .
, toStream
, , toVector
toSeq
.
// (seq: Seq[String]) seq.reduce(_ + _) seq.reduce(_ + separator + _) seq.fold(prefix)(_ + _) seq.map(_.toString).reduce(_ + _) // seq: Seq[T] seq.foldLeft(new StringBuilder())(_ append _) // seq.mkString seq.mkString(prefix, separator, "")
, StringBuilder
.
- :
reduceLeft
,reduceRight
,foldLeft
,foldRight
. - :
Set
,Option
,Iterator
.
5. (Sets)
. , .
sameElements
// set1.sameElements(set2) // set1 == set2
( ), .
sameElements
, , .
, : , LinkedHashSet
.
: Map
.
Set -
// (set: Set[Int]) Seq(1, 2, 3).filter(set(_)) Seq(1, 2, 3).filter(set.contains) // Seq(1, 2, 3).filter(set)
Set[T]
Function1[T, Boolean]
, .
// set1.filter(set2.contains) set1.filter(set2) // set1.intersect(set2) // set1 & set2
, .
, , .
// set1.filterNot(set2.contains) set1.filterNot(set2) // set1.diff(set2) // set1 &~ set2
, , .
, , .
6. Options
Option
Scala , ( ..) , , - .
Option. , , Option
API.
6.1
Option None
// option == None option != None // option.isEmpty option.isDefined
, , , Option
.
, Option[T]
T
, scalac ( ), .
Option
Some
// option == Some(v) option != Some(v) // option.contains(v) !option.contains(v)
.
isInstanceOf
// option.isInstanceOf[Some[_]] // option.isDefined
.
// option match { case Some(_) => true case None => false } option match { case Some(_) => false case None => true } // option.isDefined option.isEmpty
, — . , .
: Seq
, Set
.
,
// !option.isEmpty !option.isDefined !option.nonEmpty // seq.isDefined seq.isEmpty seq.isEmpty
, — , .
, : isDefined
( option) nonEmpty
( ). , Option
.
6.2 Null
null
, Option
// if (v != null) Some(v) else None // Option(v)
.
null
// option.getOrElse(null) // option.orNull
, .
6.3
, , Option
.
, Option
, , « Option
— map
, flatMap
, filter
foreach
». , "check & get" ( ) , if
.
— , «» :
- ,
-
NoSuchElementException
MatchError
.
getOrElse
// if (option.isDefined) option.get else z option match { case Some(it) => it case None => z } // option.getOrElse(z)
orElse
// if (option1.isDefined) option1 else option2 option1 match { case Some(it) => Some(it) case None => option2 } // option1.orElse(option2)
exists
// option.isDefined && p(option.get) if (option.isDefined) p(option.get) else false option match { case Some(it) => p(it) case None => false } // option.exists(p)
forall
// option.isEmpty || (option.isDefined && p(option.get)) if (option.isDefined) p(option.get) else true option match { case Some(it) => p(it) case None => true } // option.forall(p)
contains
// option.isDefined && option.get == x if (option.isDefined) option.get == x else false option match { case Some(it) => it == x case None => false } // option.contains(x)
foreach
// if (option.isDefined) f(option.get) option match { case Some(it) => f(it) case None => } // option.foreach(f)
filter
// if (option.isDefined && p(option.get)) option else None option match { case Some(it) && p(it) => Some(it) case _ => None } // option.filter(p)
map
// if (option.isDefined) Some(f(option.get)) else None option match { case Some(it) => Some(f(it)) case None => None } // option.map(f)
flatMap
// (f: A => Option[B]) if (option.isDefined) f(option.get) else None option match { case Some(it) => f(it) case None => None } // option.flatMap(f)
6.4
map
getOrElse
fold
// option.map(f).getOrElse(z) // option.fold(z)(f)
( z
— ), . (- Scala), .
exists
// option.map(p).getOrElse(false) // option.exists(p)
( Option
). getOrElse
.
flatten
// (option: Option[Option[T]]) option.map(_.get) option.getOrElse(None) // option.flatten
.
Option
Seq
// option.map(Seq(_)).getOrElse(Seq.empty) option.getOrElse(Seq.empty) // option: Option[Seq[T]] // option.toSeq
, .
7.
, , .
// map.find(_._1 == k).map(_._2) // map.get(k)
, , - , Map
(, ) — . , .
get
,
// Before map.get(k).get // After map(k)
Option
, (raw) .
lift
get
// Before map.lift(k) // After map.get(k)
, ( ), . lift
, ( Map
PartialFunction
) .
get
getOrElse
// map.get(k).getOrElse(z) // map.getOrElse(k, z)
, . z
, .
Map -
// (map: Map[Int, T]) Seq(1, 2, 3).map(map(_)) // Seq(1, 2, 3).map(map)
Map[K, V] Function1[K, V], .
// map.map(_._1) map.map(_._1).toSet map.map(_._1).toIterator // map.keys map.keySet map.keysIterator
( ).
// map.map(_._2) map.map(_._2).toIterator // map.values map.valuesIterator
( ).
filterKeys
// map.filterKeys(p) // map.filter(p(_._1))
filterKeys
- . , filterKeys
. , , , , filterKeys(p).groupBy(???)
.
«» ( , ) – , - .
filterKeys
, , , - , . withKeyFilter
( withFilter
).
, filterKeys
( , ), .
, , ( ) , , :
type MapView[A, +B] = Map[A, B] implicit class MapExt[A, +B](val map: Map[A, B]) extends AnyVal { def withKeyFilter(p: A => Boolean): MapView[A, B] = map.filterKeys(p) }
MapView
, , view-. :
def get(k: T) = if (p(k)) map.get(k) else None
mapValues
// map.mapValues(f) // map.map(f(_._2))
, . :
type MapView[A, +B] = Map[A, B] implicit class MapExt[A, +B](val map: Map[A, B]) extends AnyVal { def withValueMapper[C](f: B => C): MapView[A, C] = map.mapValues(f) }
:
def get(k: T) = map.get(k).map(f)
// map.filterKeys(!seq.contains(_)) // map -- seq
, .
// map = map + x -> y map1 = map1 ++ map2 map = map - x map = map -- seq // map += x -> y map1 ++= map2 map -= x map --= seq
, , .
8.
Scala , .
:
- Scala for Project Euler — Scala.
- Ninety-nine — Scala, Java, Clojure Haskell ( ).
Scala.
, . , - , . .
翻訳者から
. ( ). firegurafiku , .