📜 ⬆️ ⬇️

Scala Collection: Secrets and Tricks

I present to your attention the translation of the article by Pavel Fatin Scala Collections Tips and Tricks . Pavel works at JetBrains and is developing a Scala plugin for IntelliJ IDEA. Further, the story goes on behalf of the author.


In this article, you will find the simplifications and optimizations characteristic of the daily use of the Scala API collections .


Some tips are based on the intricacies of library collection implementations, but most recipes are reasonable transformations that are often overlooked in practice.


This list is inspired by my attempts to develop practical inspections for the Scala collections , for the IntelliJ Scala plugin . We are now implementing these inspections, so using the Scala plugin in IDEA you automatically benefit from static code analysis.


However, these recipes are valuable in their own right. They can help you deepen your understanding of the standard library of Scala collections and make your code faster and more expressive.


Update:
If you are experiencing adventure,
You can learn how to help develop IntelliJ plug-in for Scala and try your hand at implementation by selecting the appropriate inspection .


Content:


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.  

All code samples are available in the GitHub repository .


1. Legend


To make the code examples clearer, I used the following notation:



2. Composition


Remember, despite the fact that recipes are isolated and self-sufficient, they can be put together for the subsequent gradual transformation into more advanced expressions:


 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) 

So, we can rely on a “replacement recipe application model” (similar to SICP ), and use it to simplify complex expressions.


3. Side effects


"Side effect" (Side effect) is a basic concept that should be considered before we list the main transformations.


In fact, a side effect is any action that is observed outside of a function or expression besides returning a value, for example:



Functions or expressions containing any of the above actions are said to have side effects, otherwise they are called “clean.”


Why are side effects so important? Because with them the order of execution matters. For example, two "pure" expressions (associated with the corresponding values):


 val x = 1 + 2 val y = 2 + 3 

Since they do not contain side effects (i.e., effects observed outside expressions), we can calculate these expressions in an arbitrary order — first x and then y or first y , and then x — this will not affect the correctness of the results ( we can even cache the resulting values ​​if we want one). Now consider the following modification:


 val x = { print("foo"); 1 + 2 } val y = { print("bar"); 2 + 3 } 

And this is another story - we cannot change the order of execution, because in our terminal there will be printed "barfoo" instead of "foobar" (and this is clearly not what we wanted).


So, the presence of side effects reduces the number of possible transformations (both simplifications and optimizations) that we can apply to the code.


Similar reasoning applies to related collections, expressions. Imagine that somewhere outside the scope, we have a certain builder :


 seq.filter(x => { builder.append(x); x > 3 }).headOption 

In principle, the seq.filter(p).headOption simplified to seq.find(p) , however, the presence of a side effect seq.find(p) us from doing this:


 seq.find( x => {builder.append(x); x > 3 }) 

Although these expressions are equivalent from the standpoint of the final value, they are not equivalent regarding side effects. The first expression will add all elements, and the last will drop all elements as soon as it finds the first value that matches the predicate. Therefore, such a simplification can not be done.


What can be done to make automatic simplification possible? The answer is the golden rule , which should be followed in relation to all side effects in our code (including the one where there are no collections in principle):



Therefore, we need to either get rid of builder a (along with its API, which has side effects), or separate the call of builder a from the pure expression. Suppose that this builder is a kind of third-party object, which we cannot get rid of, so we can only isolate the call:


 seq.foreach(builder.append) seq.filter(_ > 3).headOption 

Now we can safely complete the conversion:


 seq.foreach(builder.append) seq.find(x > 3) 

Clean and beautiful! Isolation of side effects made automatic conversion possible. An additional benefit is that due to the presence of a clear separation, it is easier for a person to understand the resulting code.


The least obvious and the most important advantage of isolating side effects will be improving the reliability of our code, regardless of other possible optimizations. Regarding the example: the initial expression can produce various side effects depending on the current implementation of Seq . For Vector , for example, it will add all elements, for Stream it will skip all elements after the first successful match with the predicate (because the streams are “lazy” - the elements are calculated only when necessary). Separating the side effects allows us to avoid these uncertainties.


4. Sequences (Sequences)


Although the tips in this section apply to the Seq heirs, some conversions are valid for other types of collections (and not collections), for example: Set , Option , Map and even Iterator (because they all provide similar interfaces with monadic methods).


4.1 Creating


Explicitly create empty collections


 //  Seq[T]() //  Seq.empty[T] 

Some immutable collection classes have a singleton implementation of the empty method. However, not all factory methods check the length of the created collections. So, by designating emptiness at compile time, you can save either space on the heap (by reusing the instance), or processor ticks (which could be spent on dimension checks at runtime).
Also applicable to: Set , Option , Map , Iterator .


4.2 Lengths


For arrays, use length instead of size


 //  array.size //  array.length 

Although size and length are essentially synonyms, in Scala 2.11, Array.size calls Array.size still executed via implicit conversion, thus creating intermediate wrapper objects for each method call. If you, of course, do not enable escaping analysis for the JVM, temporary objects will become a burden for the garbage collector and will degrade the performance of the code (especially inside loops).


Do not deny isEmpty


 //  !seq.isEmpty !seq.nonEmpty //  seq.nonEmpty seq.isEmpty 

Simple properties contain less visual noise than compound expressions.
Also applicable to: Set , Option , Map , Iterator .


Do not calculate the length when checking for emptiness.


 //  seq.length > 0 seq.length != 0 seq.length == 0 //  seq.nonEmpty seq.nonEmpty seq.isEmpty 

On the one hand, a simple property is perceived much easier than a compound expression. On the other hand, heirs of LinearSeq (such as List ) may need O(n) time to calculate the length of the list (instead of O(1) for IndexedSeq ), so we can speed up our code by avoiding the length calculation when we, in general, This value is not very necessary.
Also .length in mind that the .length challenge for endless streams may never end, so always check the stream for emptiness explicitly.
Also applicable to: Set , Map .


During the comparison, do not calculate the full size of the collection.


 //  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 

Since the calculation of the size of the collection can be quite “expensive” calculation for some types of collections, we can reduce the comparison time from O(length) to O(length min n) for the heirs of LinearSeq (which can be hidden under Seq -like values). In addition, this approach is indispensable when dealing with endless streams.


Do not use exists to check for emptiness.


 //  seq.exists(_ => true) seq.exists(const(true)) //  seq.nonEmpty 

Of course, such a trick would be completely superfluous.
Also applicable to: Set, Option, Map, Iterator.


4.3 Equality


Do not rely on == to compare the contents of arrays


 //  array1 == array2 //  array1.sameElements(array2) 

The equality test will always return false for different instances of arrays.
Also applicable to: Iterator .


Do not check for equality of the collection of different categories.


 //  seq == set //  seq.toSet == set 

Equality checks can be used to compare collections and different categories (for example List and Set ).
I ask you to think twice about the meaning of this test (regarding the example above - how to treat duplicates in sequence).


Do not use the sameElements to compare ordinary collections.


 //  seq1.sameElements(seq2) //  seq1 == seq2 

Equality testing is the way to compare collections of the same category. In theory, this can improve performance due to the presence of possible underlying instance checks ( eq , usually much faster).


Do not use the correspond explicitly.


 //  seq1.corresponds(seq2)(_ == _) //  seq1 == seq2 

We already have a built-in method that does the same. Both expressions take into account the order of the elements. And again, we can benefit from increased productivity.


4.4 Indexing


Do not get the first item by index


 //  seq(0) //  seq.head 

For some classes of collections, the updated approach may be a little faster (see the List.apply code, for example). Moreover, accessing a property is much easier (both syntactically and semantically) than calling a method with an argument.


Don't get the last item by index


 //  seq(seq.length - 1) //  seq.last 

The last expression will be clearer and will avoid unnecessary calculations of the length of the collection (and for linear sequences it may take a lot of time). Moreover, some collection classes can retrieve the last element more efficiently than index access.


Do not check whether the index is within the boundaries of the collection explicitly.


 //  if (i < seq.length) Some(seq(i)) else None //  seq.lift(i) 

Semantically, the second expression is equivalent, but more expressive.


Do not emulate headOption


 //  if (seq.nonEmpty) Some(seq.head) else None seq.lift(0) //  seq.headOption 

Simplified expression is more succinct.


Do not emulate lastOption


 //  if (seq.nonEmpty) Some(seq.last) else None seq.lift(seq.length - 1) //  seq.lastOption 

The last expression is shorter (and potentially faster).


Be careful with the argument types for indexOf and 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) 

Due to the indexOf variation , the indexOf and lastIndexOf methods accept arguments of type Any . In practice, this can lead to hard-to-find bugs that cannot be detected at the compilation stage. This is where your IDE's subsidiary inspections will be.


Do not create a range of sequence indexes manually.


 //  Range(0, seq.length) //  seq.indices 

We have a built-in method that returns a range of all indexes in a sequence.


Do not use zip to link the collection with indexes manually.


 //  seq.zip(seq.indices) //  seq.zipWithIndex 

First, the last expression is shorter. In addition, we can expect some performance gains due to the fact that we avoid the hidden calculation of the size of the collection (which can be expensive in the case of linear sequences).
An additional advantage of the last expression is that it works well with potentially infinite collections (for example, Stream ).


Use an IndexedSeq instance as a function object:


 //  (seq: IndexedSeq[T]) Seq(1, 2, 3).map(seq(_)) //  Seq(1, 2, 3).map(seq) 

Since an instance of IndexedSeq[T] also Function1[Int, T] , you can use it as such.


4.5 Existence


Do not use comparison predicate to check for element


 //  seq.exists(_ == x) //  seq.contains(x) 

The second expression is semantically equivalent, more expressive. When these expressions are applied to Set , performance can be dramatically different, because searching for sets tends to O(1) (due to internal indexing, which is not used when calling exists ).
Also applicable to: Set , Option , Iterator .


Be careful with the argument type contains


 //  Seq(1, 2, 3).contains("1") //  //  Seq(1, 2, 3).contains(1) 

As well as the indexOf and lastIndexOf methods of the contains accepts arguments of type Any , which can lead to hard-to-find bugs that are not detected at the compilation stage. Be careful with them.


Do not use inequality predicate to check for missing element.


 //  seq.forall(_ != x) //  !seq.contains(x) 

Again, the last expression is cleaner and probably faster (especially for sets).
Also applicable to: Set , Option , Iterator .


Do not count occurrences to verify existence


 //  seq.count(p) > 0 seq.count(p) != 0 seq.count(p) == 0 //  seq.exists(p) seq.exists(p) !seq.exists(p) 

Obviously, when we need to know whether a conditionally matching element is in a collection, counting the number of satisfying elements will be redundant. Simplified expression looks cleaner and faster.



Do not use filtering to check for existence.


 //  seq.filter(p).nonEmpty seq.filter(p).isEmpty //  seq.exists(p) !seq.exists(p) 

The filter call creates an intermediate collection that takes up space on the heap and loads the GC. In addition, the preceding expressions find all entries, while you only need to find the first (which can slow down the code depending on the possible contents of the collection). Potential performance gains are less significant for lazy collections (such as Stream and, in particular, Iterator ).



Do not resort to search to verify the existence of


 //  seq.find(p).isDefined seq.find(p).isEmpty //  seq.exists(p) !seq.exists(p) 

The search is definitely better than filtering, but this is far from the limit (at least in terms of clarity).
Also applicable to: Set , Option , Map , Iterator .


4.6 Filtering


Do not deny the predicate filter


 //  seq.filter(!p) //  seq.filterNot(p) 

The last expression is syntactically simpler (despite the fact that semantically they are equivalent).
Also applicable to: Set , Option , Map , Iterator .


Do not filter to count


 //  seq.filter(p).length //  seq.count(p) 

The filter call creates an intermediate (and not very necessary) collection that will take up space on the heap and load the GC.
Also applicable to: Set , Option , Map , Iterator .


Do not use filtering to find the first entry.


 //  seq.filter(p).headOption //  seq.find(p) 

Of course, if seq not a lazy collection (like, for example, Stream ), filtering will find all entries (and create a temporary collection), with only the first element required.
Also applicable to: Set , Option , Map , Iterator .


4.7 Sorting


Do not manually sort by property


 //  seq.sortWith(_.property < _.property) //  seq.sortBy(_.property) 

For this we have our own method, clearer and more expressive.


Do not manually sort by identity


 //  seq.sortBy(identity) seq.sortWith(_ < _) //  seq.sorted 

And for this, too, there is a method.


Reverse Sort in One Step


 //  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(_, _)) 

Thus, we can avoid creating an intermediate collection and eliminate additional conversions (to save heap space and processor cycles).


Do not use sorting to find the minimum item.


 //  seq.sorted.head seq.sortBy(_.property).head //  seq.min seq.minBy(_.property) 

The latter approach is more expressive. In addition, due to the fact that an additional collection is not created, it will work faster.


Do not use sorting to find the maximum element.


 //  seq.sorted.last seq.sortBy(_.property).last //  seq.max seq.maxBy(_.property) 

The explanation is the same as the previous tip.


4.8 Convolution


Do not calculate the amount manually


 //  seq.reduce(_ + _) seq.fold(z)(_ + _) //  seq.sum seq.sum + z 

The advantages of this approach are clarity and expressiveness.



Do not calculate the product manually


 //  seq.reduce(_ * _) seq.fold(z)(_ * _) //  seq.product seq.product * z 

The reasons are the same as in the previous case.



Do not search for the minimal element manually.


 //  seq.reduce(_ min _) seq.fold(z)(_ min _) //  seq.min z min seq.min 

The rationale is the same as in the previous case.
Also applicable to: Set , Iterator .


Do not search for the maximum item manually.


 //  seq.reduce(_ max _) seq.fold(z)(_ max _) //  seq.max z max seq.max 

All as in the previous case.
Also applicable to: Set , Iterator .


Do not emulate forall


 //  seq.foldLeft(true)((x, y) => x && p(y)) !seq.map(p).contains(false) //  seq.forall(p) 

The purpose of simplification is clarity and expressiveness.



Don't emulate exists


 //  seq.foldLeft(false)((x, y) => x || p(y)) seq.map(p).contains(true) //  seq.exists(p) 

With all the clarity and expressiveness, the last expression can work faster (it stops the subsequent processing of elements as soon as it finds the first suitable element), which can work for infinite sequences.



Do not emulate map


 //  seq.foldLeft(Seq.empty)((acc, x) => acc :+ f(x)) seq.foldRight(Seq.empty)((x, acc) => f(x) +: acc) //  seq.map(f) 

This is a “classical” implementation of mapping (map) through convolution in functional programming. Undoubtedly, it is instructive, but there is no need to use it. To do this, we have a built-in and expressive method (which is also faster, since it uses a simple while in its implementation).
Also applicable to: Set , Option , Iterator .


Do not emulate 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) 

The reasons are the same as in the previous case.
Also applicable to: Set , Option , Iterator .


Do not emulate reverse


 //  seq.foldLeft(Seq.empty)((acc, x) => x +: acc) seq.foldRight(Seq.empty)((x, acc) => acc :+ x) //  seq.reverse 

And again, the built-in method is faster and cleaner.
Also applicable to: Set , Option , Iterator .


4.9 Comparison


Here are some separate tips on Scala pattern matching and partial functions .


Use partial functions instead of pattern matching functions.


 //  seq.map { _ match { case P => ??? // x N } } //  seq.map { case P => ??? // x N } 

The updated expression gives a similar result and looks simpler.


The transformations described above can be applied to any functions, and not just to the arguments of the map function. This tip applies not only to collections. However, in view of the omnipresence of higher-order functions in the API of the standard library of Scala collections, it will be very useful.


Convert flatMap with partial collect function


 //  seq.flatMap { case P => Seq(???) // x N case _ => Seq.empty } //  seq.collect { case P => ??? // x N } 

The updated expression gives a similar result and looks much simpler.
Also applicable to: Set , Option , Map , Iterator .


Convert match to collect when the result is a collection.


 //  v match { case P => Seq(???) // x N case _ => Seq.empty } //  Seq(v) collect { case P => ??? // x N } 

Given that all case statements create collections, you can simplify the expression by replacing match with a call to collect . , case .
Option , .
: Set , Option , Iterator .


collectFirst


 //  seq.collect{case P => ???}.headOption //  seq.collectFirst{case P => ???} 

, .



4.10


filter


 //  seq.filter(p1).filter(p2) //  seq.filter(x => p1(x) && p2(x)) 

( filter ), .
, ( ), : seq.view.filter(p1).filter(p2).force .



map


 //  seq.map(f).map(g) //  seq.map(f.andThen(g)) 

, .


, view ( ), : seq.view.map(f).map(g).force .




 //  seq.sorted.filter(p) //  seq.filter(p).sorted 

— . , .



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) 

, .



partition


 //  val seq1 = seq.filter(p) val seq2 = seq.filterNot(p) //  val (seq1, seq2) = seq.partition(p) 

-,



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) 

, - .




( ).


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) — , , :



, 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 .


.




 //  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 .



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 .
— , «» :



.


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 , .


See also:



Scala.


, . , - , . .



. ( ). firegurafiku , .


')

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


All Articles