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 .
To make the code examples clearer, I used the following notation:
seq
- an instance of a collection based on Seq
, like Seq(1, 2, 3)
set
is an instance of Set
, for example Set(1, 2, 3)
array
- an array such as Array(1, 2, 3)
option
- an instance of Option
, for example, Some(1)
map
is a Map
instance, similar to Map(1 -> "foo", 2 -> "bar")
???
- arbitrary expressionp
- predicate function of type T => Boolean
, for example _ > 2
n
is an integer valuei
- integer indexf
, g
- simple functions, A => B
x
, y
- some arbitrary valuesz
- initial or default valueP
- patternRemember, 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.
"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.
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).
// 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
.
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).
// !seq.isEmpty !seq.nonEmpty // seq.nonEmpty seq.isEmpty
Simple properties contain less visual noise than compound expressions.
Also applicable to: Set
, Option
, Map
, Iterator
.
// 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
.
// 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.
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.
==
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
.
// 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).
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).
// 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.
// 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.
// 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.
// if (i < seq.length) Some(seq(i)) else None // seq.lift(i)
Semantically, the second expression is equivalent, but more expressive.
// if (seq.nonEmpty) Some(seq.head) else None seq.lift(0) // seq.headOption
Simplified expression is more succinct.
lastOption
// if (seq.nonEmpty) Some(seq.last) else None seq.lift(seq.length - 1) // seq.lastOption
The last expression is shorter (and potentially faster).
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.
// Range(0, seq.length) // seq.indices
We have a built-in method that returns a range of all indexes in a sequence.
// 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
).
// (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.
// 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
.
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.
// seq.forall(_ != x) // !seq.contains(x)
Again, the last expression is cleaner and probably faster (especially for sets).
Also applicable to: Set
, Option
, Iterator
.
// 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.
p
must be a pure function.Set
, Map
, Iterator
. // 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
).
p
must be a pure function.Set
, Option
, Map
, Iterator
. // 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
.
// 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
.
// 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
.
// 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
.
// seq.sortWith(_.property < _.property) // seq.sortBy(_.property)
For this we have our own method, clearer and more expressive.
// seq.sortBy(identity) seq.sortWith(_ < _) // seq.sorted
And for this, too, there is a method.
// 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).
// 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.
// seq.sorted.last seq.sortBy(_.property).last // seq.max seq.maxBy(_.property)
The explanation is the same as the previous tip.
// seq.reduce(_ + _) seq.fold(z)(_ + _) // seq.sum seq.sum + z
The advantages of this approach are clarity and expressiveness.
reduceLeft
, reduceRight
, foldLeft
, foldRight
.z
equals 0
.Set
, Iterator
. // seq.reduce(_ * _) seq.fold(z)(_ * _) // seq.product seq.product * z
The reasons are the same as in the previous case.
z
is 1
.Set
, Iterator
. // 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
.
// seq.reduce(_ max _) seq.fold(z)(_ max _) // seq.max z max seq.max
All as in the previous case.
Also applicable to: Set
, Iterator
.
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.
p
must be a pure function.Set
, Option
(for the second row), Iterator
.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.
p
must be a pure function.Set
, Option
(for the second row), Iterator
.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
.
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
.
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
.
Here are some separate tips on Scala pattern matching and partial 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.
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
.
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 => ???}
, .
Set
, Map
, Iterator
.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
.. , .
sameElements
// set1.sameElements(set2) // set1 == set2
( ), .
sameElements
, , .
, : , LinkedHashSet
.
: Map
.
// (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
, , .
, , .
Option
Scala , ( ..) , , - .
Option. , , Option
API.
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
.
null
, Option
// if (v != null) Some(v) else None // Option(v)
.
null
// option.getOrElse(null) // option.orNull
, .
, , 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)
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
, .
, , .
// 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[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
, , .
Scala , .
See also:
Scala.
, . , - , . .
. ( ). firegurafiku , .
Source: https://habr.com/ru/post/333362/
All Articles