├── sequences ├── creation.scala ├── filtering.scala ├── equality.scala ├── matching.scala ├── sorting.scala ├── existence.scala ├── length.scala ├── indexing.scala ├── reduction.scala └── rewriting.scala ├── side-effects.scala ├── composition.scala ├── options ├── null.scala ├── rewriting.scala ├── value.scala └── processing.scala ├── README.md ├── sets └── sets.scala └── maps └── maps.scala /sequences/creation.scala: -------------------------------------------------------------------------------- 1 | // Create empty collections explicitly 2 | 3 | // Before 4 | Seq[T]() 5 | 6 | // After 7 | Seq.empty[T] 8 | -------------------------------------------------------------------------------- /side-effects.scala: -------------------------------------------------------------------------------- 1 | val x = 1 + 2 2 | val y = 2 + 3 3 | 4 | val x = { print("foo"); 1 + 2 } 5 | val y = { print("bar"); 2 + 3 } 6 | 7 | seq filter { x => builder.append(x); x > 3 } headOption 8 | 9 | seq find { x => builder.append(x); x > 3 } 10 | 11 | seq.foreach(builder.append) 12 | seq.filter(_ > 3).headOption 13 | 14 | seq.foreach(builder.append) 15 | seq.find(x > 3) 16 | -------------------------------------------------------------------------------- /composition.scala: -------------------------------------------------------------------------------- 1 | seq.filter(_ == x).headOption != None 2 | 3 | // Via seq.filter(p).headOption -> seq.find(p) 4 | 5 | seq.find(_ == x) != None 6 | 7 | // Via option != None -> option.isDefined 8 | 9 | seq.find(_ == x).isDefined 10 | 11 | // Via seq.find(p).isDefined -> seq.exists(p) 12 | 13 | seq.exists(_ == x) 14 | 15 | // Via seq.exists(_ == x) -> seq.contains(x) 16 | 17 | seq.contains(x) 18 | -------------------------------------------------------------------------------- /sequences/filtering.scala: -------------------------------------------------------------------------------- 1 | // Don't negate filter predicate 2 | 3 | // Before 4 | seq.filter(!p) 5 | 6 | // After 7 | seq.filterNot(p) 8 | 9 | 10 | // Don't resort to filtering to count elements 11 | 12 | // Before 13 | seq.filter(p).length 14 | 15 | // After 16 | seq.count(p) 17 | 18 | 19 | // Don't use filtering to find first occurrence 20 | 21 | // Before 22 | seq.filter(p).headOption 23 | 24 | // After 25 | seq.find(p) 26 | -------------------------------------------------------------------------------- /options/null.scala: -------------------------------------------------------------------------------- 1 | // Don't compare value with "null" explicitly to construct an "Option" 2 | 3 | // Before 4 | if (v != null) Some(v) else None 5 | 6 | // After 7 | Option(v) 8 | 9 | 10 | // Don't use "Option(...)" with a constant 11 | 12 | // Before 13 | Option("constant") 14 | 15 | // After 16 | Some("constant") 17 | 18 | 19 | // Don't provide "null" as an explicit alternative 20 | 21 | // Before 22 | option.getOrElse(null) 23 | 24 | // After 25 | option.orNull 26 | -------------------------------------------------------------------------------- /sequences/equality.scala: -------------------------------------------------------------------------------- 1 | // Don't rely on "==" to compare array contents 2 | 3 | // Before 4 | array1 == array2 5 | 6 | // After 7 | array1.sameElements(array2) 8 | 9 | 10 | // Don't check equality between collections in different categories 11 | 12 | // Before 13 | seq == set 14 | 15 | // After 16 | seq.toSet == set 17 | 18 | 19 | // Don't use "sameElements" to compare ordinary collections 20 | 21 | // Before 22 | seq1.sameElements(seq2) 23 | 24 | // After 25 | seq1 == seq2 26 | 27 | 28 | // Don't check equality correspondence manually 29 | 30 | // Before 31 | seq1.corresponds(seq2)(_ == _) 32 | 33 | // After 34 | seq1 == seq2 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Collections README 2 | 3 | Code examples for my article [Scala Collections Tips and Tricks](https://pavelfatin.com/scala-collections-tips-and-tricks/) (simplifications and optimizations of typical Scala Collections API usages). 4 | 5 | 6 | Composition 7 | 8 | Side effects 9 | 10 | Sequences: 11 | 12 | * Creation 13 | * Length 14 | * Equality 15 | * Indexing 16 | * Existence 17 | * Filtering 18 | * Sorting 19 | * Reduction 20 | * Matching 21 | * Rewriting 22 | 23 | Sets: 24 | 25 | * Sets 26 | 27 | Options: 28 | 29 | * Value 30 | * Null 31 | * Processing 32 | * Rewriting 33 | 34 | Maps: 35 | 36 | * Maps 37 | 38 | 39 | Pavel Fatin, [https://pavelfatin.com](https://pavelfatin.com/) -------------------------------------------------------------------------------- /options/rewriting.scala: -------------------------------------------------------------------------------- 1 | // Convert "map" with "getOrElse" to "fold" 2 | 3 | // Before 4 | option.map(f).getOrElse(z) 5 | 6 | // After 7 | option.fold(z)(f) 8 | 9 | 10 | // Don't emulate "exists" 11 | 12 | // Before 13 | option.map(p).getOrElse(false) 14 | 15 | // After 16 | option.exists(p) 17 | 18 | 19 | // Don't emulate "flatten" 20 | 21 | // Before (option: Option[Option[T]]) 22 | option.map(_.get) 23 | option.getOrElse(None) 24 | 25 | // After 26 | option.flatten 27 | 28 | 29 | // Don't convert option to sequence manually 30 | 31 | // Before 32 | option.map(Seq(_)).getOrElse(Seq.empty) 33 | option.getOrElse(Seq.empty) // option: Option[Seq[T]] 34 | 35 | // After 36 | option.toSeq 37 | -------------------------------------------------------------------------------- /sets/sets.scala: -------------------------------------------------------------------------------- 1 | // Don't use "sameElements" to compare unordered collections 2 | 3 | // Before 4 | set1.sameElements(set2) 5 | 6 | // After 7 | set1 == set2 8 | 9 | 10 | // Use "Set" instance as a function value 11 | 12 | // Before (set: Set[Int]) 13 | Seq(1, 2, 3).filter(set(_)) 14 | Seq(1, 2, 3).filter(set.contains) 15 | 16 | // After 17 | Seq(1, 2, 3).filter(set) 18 | 19 | 20 | // Don't compute set intersection manually 21 | 22 | // Before 23 | set1.filter(set2) 24 | 25 | // After 26 | set1.intersect(set2) // or set1 & set2 27 | 28 | 29 | // Don't compute set difference manually 30 | 31 | // Before 32 | set1.filterNot(set2) 33 | 34 | // After 35 | set1.diff(set2) // or set1 &~ set2 36 | -------------------------------------------------------------------------------- /sequences/matching.scala: -------------------------------------------------------------------------------- 1 | // Use partial function instead of function with pattern matching 2 | 3 | // Before 4 | seq.map { 5 | _ match { 6 | case P => ??? // x N 7 | } 8 | } 9 | 10 | // After 11 | seq.map { 12 | case P => ??? // x N 13 | } 14 | 15 | 16 | // Convert "flatMap" with partial function to "collect" 17 | 18 | // Before 19 | seq.flatMap { 20 | case P => Seq(???) // x N 21 | case _ => Seq.empty 22 | } 23 | 24 | // After 25 | seq.collect { 26 | case P => ??? // x N 27 | } 28 | 29 | 30 | // Convert "match" to "collect" when the result is a collection 31 | 32 | // Before 33 | v match { 34 | case P => Seq(???) // x N 35 | case _ => Seq.empty 36 | } 37 | 38 | // After 39 | Seq(v) collect { 40 | case P => ??? // x N 41 | } 42 | 43 | 44 | // Don't emulate "collectFirst" 45 | 46 | // Before 47 | seq.collect{case P => ???}.headOption 48 | 49 | // After 50 | seq.collectFirst{case P => ???} 51 | 52 | -------------------------------------------------------------------------------- /sequences/sorting.scala: -------------------------------------------------------------------------------- 1 | // Don't sort by a property manually 2 | 3 | // Before 4 | seq.sortWith(_.property < _.property) 5 | 6 | // After 7 | seq.sortBy(_.property) 8 | 9 | 10 | // Don't sort by identity manually 11 | 12 | // Before 13 | seq.sortBy(identity) 14 | seq.sortWith(_ < _) 15 | 16 | // After 17 | seq.sorted 18 | 19 | 20 | // Perform reverse sorting in one step 21 | 22 | // Before 23 | seq.sorted.reverse 24 | seq.sortBy(_.property).reverse 25 | seq.sortWith(f(_, _)).reverse 26 | 27 | // After 28 | seq.sorted(Ordering[T].reverse) 29 | seq.sortBy(_.property)(Ordering[T].reverse) 30 | seq.sortWith(!f(_, _)) 31 | 32 | 33 | // Don't use sorting to find the smallest element 34 | 35 | // Before 36 | seq.sorted.head 37 | seq.sortBy(_.property).head 38 | 39 | // After 40 | seq.min 41 | seq.minBy(_.property) 42 | 43 | 44 | // Don't use sorting to find the largest element 45 | 46 | // Before 47 | seq.sorted.last 48 | seq.sortBy(_.property).last 49 | 50 | // After 51 | seq.max 52 | seq.maxBy(_.property) 53 | -------------------------------------------------------------------------------- /options/value.scala: -------------------------------------------------------------------------------- 1 | // Don't compare option values with "None" 2 | 3 | // Before 4 | option == None 5 | option != None 6 | 7 | // After 8 | option.isEmpty 9 | option.isDefined 10 | 11 | 12 | // Don't compare option values with "Some" 13 | 14 | // Before 15 | option == Some(v) 16 | option != Some(v) 17 | 18 | // After 19 | option.contains(v) 20 | !option.contains(v) 21 | 22 | 23 | // Don't rely on instance type to check value existence 24 | 25 | // Before 26 | option.isInstanceOf[Some[_]] 27 | 28 | // After 29 | option.isDefined 30 | 31 | 32 | // Don't resort to pattern matching to check value existence 33 | 34 | // Before 35 | option match { 36 | case Some(_) => true 37 | case None => false 38 | } 39 | 40 | option match { 41 | case Some(_) => false 42 | case None => true 43 | } 44 | 45 | // After 46 | option.isDefined 47 | option.isEmpty 48 | 49 | 50 | // Don't negate value existence-related properties 51 | 52 | // Before 53 | !option.isEmpty 54 | !option.isDefined 55 | !option.nonEmpty 56 | 57 | // After 58 | seq.isDefined 59 | seq.isEmpty 60 | seq.isEmpty 61 | -------------------------------------------------------------------------------- /sequences/existence.scala: -------------------------------------------------------------------------------- 1 | // Don't use equality predicate to check element presence 2 | 3 | // Before 4 | seq.exists(_ == x) 5 | 6 | // After 7 | seq.contains(x) 8 | 9 | 10 | // Be careful with "contains" argument type 11 | 12 | // Before 13 | Seq(1, 2, 3).contains("1") // compilable 14 | 15 | // After 16 | Seq(1, 2, 3).contains(1) 17 | 18 | 19 | // Don't use inequality predicate to check element absence 20 | 21 | // Before 22 | seq.forall(_ != x) 23 | 24 | // After 25 | !seq.contains(x) 26 | 27 | 28 | // Don't count occurrences to check existence 29 | 30 | // Before 31 | seq.count(p) > 0 32 | seq.count(p) != 0 33 | seq.count(p) == 0 34 | 35 | // After 36 | seq.exists(p) 37 | seq.exists(p) 38 | !seq.exists(p) 39 | 40 | 41 | // Don't resort to filtering to check existence 42 | 43 | // Before 44 | seq.filter(p).nonEmpty 45 | seq.filter(p).isEmpty 46 | 47 | // After 48 | seq.exists(p) 49 | !seq.exists(p) 50 | 51 | 52 | // Don't resort to search to check existence 53 | 54 | // Before 55 | seq.find(p).isDefined 56 | seq.find(p).isEmpty 57 | 58 | // After 59 | seq.exists(p) 60 | !seq.exists(p) 61 | -------------------------------------------------------------------------------- /sequences/length.scala: -------------------------------------------------------------------------------- 1 | // Prefer "length" to "size" for arrays 2 | 3 | // Before 4 | array.size 5 | 6 | // After 7 | array.length 8 | 9 | 10 | // Don't negate emptiness-related properties 11 | 12 | // Before 13 | !seq.isEmpty 14 | !seq.nonEmpty 15 | 16 | // After 17 | seq.nonEmpty 18 | seq.isEmpty 19 | 20 | 21 | // Don't compute length for emptiness check 22 | 23 | // Before 24 | seq.length > 0 25 | seq.length != 0 26 | seq.length == 0 27 | 28 | // After 29 | seq.nonEmpty 30 | seq.nonEmpty 31 | seq.isEmpty 32 | 33 | 34 | // Don't compute length for emptiness check 35 | 36 | // Before 37 | seq.length > 0 38 | seq.length != 0 39 | seq.length == 0 40 | 41 | // After 42 | seq.nonEmpty 43 | seq.nonEmpty 44 | seq.isEmpty 45 | 46 | 47 | // Don't compute full length for length matching 48 | 49 | // Before 50 | seq.length > n 51 | seq.length < n 52 | seq.length == n 53 | seq.length != n 54 | 55 | // After 56 | seq.lengthCompare(n) > 0 57 | seq.lengthCompare(n) < 0 58 | seq.lengthCompare(n) == 0 59 | seq.lengthCompare(n) != 0 60 | 61 | 62 | // Don't use "exists" to for empiness check 63 | 64 | // Before 65 | seq.exists(_ => true) 66 | seq.exists(const(true)) 67 | 68 | // After 69 | seq.nonEmpty 70 | -------------------------------------------------------------------------------- /sequences/indexing.scala: -------------------------------------------------------------------------------- 1 | // Don't retrieve first element by index 2 | 3 | // Before 4 | seq(0) 5 | 6 | // After 7 | seq.head 8 | 9 | 10 | // Don't retrieve last element by index 11 | 12 | // Before 13 | seq(seq.length - 1) 14 | 15 | // After 16 | seq.last 17 | 18 | 19 | // Don't check index bounds explicitly 20 | 21 | // Before 22 | if (i < seq.length) Some(seq(i)) else None 23 | 24 | // After 25 | seq.lift(i) 26 | 27 | 28 | // Don't emulate "headOption" 29 | 30 | // Before 31 | if (seq.nonEmpty) Some(seq.head) else None 32 | seq.lift(0) 33 | 34 | // After 35 | seq.headOption 36 | 37 | 38 | // Don't emulate "lastOption" 39 | 40 | // Before 41 | if (seq.nonEmpty) Some(seq.last) else None 42 | seq.lift(seq.length - 1) 43 | 44 | // After 45 | seq.lastOption 46 | 47 | 48 | // Be careful with "indexOf" and "lastIndexOf" argument types 49 | 50 | // Before 51 | Seq(1, 2, 3).indexOf("1") // compilable 52 | Seq(1, 2, 3).lastIndexOf("2") // compilable 53 | 54 | // After 55 | Seq(1, 2, 3).indexOf(1) 56 | Seq(1, 2, 3).lastIndexOf(2) 57 | 58 | 59 | // Don't construct indices range manually 60 | 61 | // Before 62 | Range(0, seq.length) 63 | 64 | // After 65 | seq.indices 66 | 67 | 68 | // Don't zip collection with its indices manually 69 | 70 | // Before 71 | seq.zip(seq.indices) 72 | 73 | // After 74 | seq.zipWithIndex 75 | 76 | 77 | // Use "IndexedSeq" instance as a function value 78 | 79 | // Before (seq: IndexedSeq[T]) 80 | Seq(1, 2, 3).map(seq(_)) 81 | 82 | // After 83 | Seq(1, 2, 3).map(seq) 84 | -------------------------------------------------------------------------------- /sequences/reduction.scala: -------------------------------------------------------------------------------- 1 | // Don't calculate sum manually 2 | 3 | // Before 4 | seq.reduce(_ + _) 5 | seq.fold(z)(_ + _) 6 | 7 | // After 8 | seq.sum 9 | seq.sum + z 10 | 11 | 12 | // Don't calculate product manually 13 | 14 | // Before 15 | seq.reduce(_ * _) 16 | seq.fold(z)(_ * _) 17 | 18 | // After 19 | seq.product 20 | seq.product * z 21 | 22 | 23 | // Don't search for the smallest element manually 24 | 25 | // Before 26 | seq.reduce(_ min _) 27 | seq.fold(z)(_ min _) 28 | 29 | // After 30 | seq.min 31 | z min seq.min 32 | 33 | 34 | // Don't search for the largest element manually 35 | 36 | // Before 37 | seq.reduce(_ max _) 38 | seq.fold(z)(_ max _) 39 | 40 | // After 41 | seq.max 42 | z max seq.max 43 | 44 | 45 | // Don't emulate "forall" 46 | 47 | // Before 48 | seq.foldLeft(true)((x, y) => x && p(y)) 49 | !seq.map(p).contains(false) 50 | 51 | // After 52 | seq.forall(p) 53 | 54 | 55 | // Don't emulate "exists" 56 | 57 | // Before 58 | seq.foldLeft(false)((x, y) => x || p(y)) 59 | seq.map(p).contains(true) 60 | 61 | // After 62 | seq.exists(p) 63 | 64 | 65 | // Don't emulate "map" 66 | 67 | // Before 68 | seq.foldLeft(Seq.empty)((acc, x) => acc :+ f(x)) 69 | seq.foldRight(Seq.empty)((x, acc) => f(x) +: acc) 70 | 71 | // After 72 | seq.map(f) 73 | 74 | 75 | // Don't emulate "filter" 76 | 77 | // Before 78 | seq.foldLeft(Seq.empty)((acc, x) => if (p(x)) acc :+ x else acc) 79 | seq.foldRight(Seq.empty)((x, acc) => if (p(x)) x +: acc else acc) 80 | 81 | // After 82 | seq.filter(p) 83 | 84 | 85 | // Don't emulate "reverse" 86 | 87 | // Before 88 | seq.foldLeft(Seq.empty)((acc, x) => x +: acc) 89 | seq.foldRight(Seq.empty)((x, acc) => acc :+ x) 90 | 91 | // After 92 | seq.reverse 93 | -------------------------------------------------------------------------------- /options/processing.scala: -------------------------------------------------------------------------------- 1 | // Don't emulate "getOrElse" 2 | 3 | // Before 4 | if (option.isDefined) option.get else z 5 | 6 | option match { 7 | case Some(it) => it 8 | case None => z 9 | } 10 | 11 | // After 12 | option.getOrElse(z) 13 | 14 | 15 | // Don't emulate "orElse" 16 | if (option1.isDefined) option1 else option2 17 | 18 | option1 match { 19 | case Some(it) => Some(it) 20 | case None => option2 21 | } 22 | 23 | // After 24 | option1.orElse(option2) 25 | 26 | 27 | // Don't emulate "exists" 28 | 29 | // Before 30 | option.isDefined && p(option.get) 31 | 32 | if (option.isDefined) p(option.get) else false 33 | 34 | option match { 35 | case Some(it) => p(it) 36 | case None => false 37 | } 38 | 39 | // After 40 | option.exists(p) 41 | 42 | 43 | // Don't emulate "forall" 44 | 45 | // Before 46 | option.isEmpty || (option.isDefined && p(option.get)) 47 | 48 | if (option.isDefined) p(option.get) else true 49 | 50 | option match { 51 | case Some(it) => p(it) 52 | case None => true 53 | } 54 | 55 | // After 56 | option.forall(p) 57 | 58 | 59 | // Don't emulate "contains" 60 | 61 | // Before 62 | option.isDefined && option.get == x 63 | 64 | if (option.isDefined) option.get == x else false 65 | 66 | option match { 67 | case Some(it) => it == x 68 | case None => false 69 | } 70 | 71 | // After 72 | option.contains(x) 73 | 74 | 75 | // Don't emulate "foreach" 76 | 77 | // Before 78 | if (option.isDefined) f(option.get) 79 | 80 | option match { 81 | case Some(it) => f(it) 82 | case None => 83 | } 84 | 85 | // After 86 | option.foreach(f) 87 | 88 | 89 | // Don't emulate "filter" 90 | 91 | // Before 92 | if (option.isDefined && p(option.get)) option else None 93 | 94 | option match { 95 | case Some(it) && p(it) => Some(it) 96 | case _ => None 97 | } 98 | 99 | // After 100 | option.filter(p) 101 | 102 | 103 | // Don't emulate "map" 104 | 105 | // Before 106 | if (option.isDefined) Some(f(option.get)) else None 107 | 108 | option match { 109 | case Some(it) => Some(f(it)) 110 | case None => None 111 | } 112 | 113 | // After 114 | option.map(f) 115 | 116 | 117 | // Don't emulate "flatMap" 118 | 119 | // Before (f: A => Option[B]) 120 | if (option.isDefined) f(option.get) else None 121 | 122 | option match { 123 | case Some(it) => f(it) 124 | case None => None 125 | } 126 | 127 | // After 128 | option.flatMap(f) 129 | -------------------------------------------------------------------------------- /maps/maps.scala: -------------------------------------------------------------------------------- 1 | // Don't search for a value manually 2 | 3 | // Before 4 | map.find(_._1 == k).map(_._2) 5 | 6 | // After 7 | map.get(k) 8 | 9 | 10 | // Don't use "get" when a raw value is needed 11 | 12 | // Before 13 | map.get(k).get 14 | 15 | // After 16 | map(k) 17 | 18 | 19 | // Don't use "lift" instead of "get" 20 | 21 | // Before 22 | map.lift(k) 23 | 24 | // After 25 | map.get(k) 26 | 27 | 28 | // Don't call "get" with "getOrElse" separately 29 | 30 | // Before 31 | map.get(k).getOrElse(z) 32 | 33 | // After 34 | map.getOrElse(k, z) 35 | 36 | 37 | // Use "Map" instance as a function value 38 | 39 | // Before (map: Map[Int, T]) 40 | Seq(1, 2, 3).map(map(_)) 41 | 42 | // After 43 | Seq(1, 2, 3).map(map) 44 | 45 | 46 | // Don't extract keys manually 47 | 48 | // Before 49 | map.map(_._1) 50 | map.map(_._1).toSet 51 | map.map(_._1).toIterator 52 | 53 | // After 54 | map.keys 55 | map.keySet 56 | map.keysIterator 57 | 58 | 59 | // Don't extract values manually 60 | 61 | // Before 62 | map.map(_._2) 63 | map.map(_._2).toIterator 64 | 65 | // After 66 | map.values 67 | map.valuesIterator 68 | 69 | 70 | // Be careful with "filterKeys" 71 | 72 | // Before 73 | map.filterKeys(p) 74 | 75 | // After 76 | map.filter(p(_._1)) 77 | 78 | 79 | // Unambiguous synonym 80 | 81 | type MapView[A, +B] = Map[A, B] 82 | 83 | implicit class MapExt[A, +B](val map: Map[A, B]) extends AnyVal { 84 | def withKeyFilter(p: A => Boolean): MapView[A, B] = 85 | map.filterKeys(p) 86 | } 87 | 88 | 89 | // Helper method 90 | 91 | def get(k: T) = if (p(k)) map.get(k) else None 92 | 93 | 94 | // Be careful with "mapValues" 95 | 96 | // Before 97 | map.mapValues(f) 98 | 99 | // After 100 | map.map(f(_._2)) 101 | 102 | 103 | // Unambiguous synonym 104 | 105 | type MapView[A, +B] = Map[A, B] 106 | 107 | implicit class MapExt[A, +B](val map: Map[A, B]) extends AnyVal { 108 | def withValueMapper[C](f: B => C): MapView[A, C] = 109 | map.mapValues(f) 110 | } 111 | 112 | 113 | // Helper method 114 | 115 | def get(k: T) = map.get(k).map(f) 116 | 117 | 118 | // Don't filter out keys manually 119 | 120 | // Before 121 | map.filterKeys(!seq.contains(_)) 122 | 123 | // After 124 | map -- seq 125 | 126 | 127 | // Use assignment operators to reassign a map 128 | 129 | // Before 130 | map = map + x -> y 131 | map1 = map1 ++ map2 132 | map = map - x 133 | map = map -- seq 134 | 135 | // After 136 | map += x -> y 137 | map1 ++= map2 138 | map -= x 139 | map --= seq 140 | -------------------------------------------------------------------------------- /sequences/rewriting.scala: -------------------------------------------------------------------------------- 1 | // Merge consecutive "filter" calls 2 | 3 | // Before 4 | seq.filter(p1).filter(p2) 5 | 6 | // After 7 | seq.filter(x => p1(x) && p2(x)) 8 | 9 | 10 | // Merge consecutive "map" calls 11 | 12 | // Before 13 | seq.map(f).map(g) 14 | 15 | // After 16 | seq.map(f.andThen(g)) 17 | 18 | 19 | // Do sorting after filtering 20 | 21 | // Before 22 | seq.sorted.filter(p) 23 | 24 | // After 25 | seq.filter(p).sorted 26 | 27 | 28 | // Don't reverse collection explicitly before calling "map" 29 | 30 | // Before 31 | seq.reverse.map(f) 32 | 33 | // After 34 | seq.reverseMap(f) 35 | 36 | 37 | // Don't reverse collection explicitly to acquire reverse iterator 38 | 39 | // Before 40 | seq.reverse.iterator 41 | 42 | // After 43 | seq.reverseIterator 44 | 45 | 46 | // Don't convert collection to a set to find distinct elements 47 | 48 | // Before 49 | seq.toSet.toSeq 50 | 51 | // After 52 | seq.distinct 53 | 54 | 55 | // Don't emulate "slice" 56 | 57 | // Before 58 | seq.drop(x).take(y) 59 | 60 | // After 61 | seq.slice(x, x + y) 62 | 63 | 64 | // Don't emulate "splitAt" 65 | 66 | // Before 67 | val seq1 = seq.take(n) 68 | val seq2 = seq.drop(n) 69 | 70 | // After 71 | val (seq1, seq2) = seq.splitAt(n) 72 | 73 | 74 | // Don't emulate "span" 75 | 76 | // Before 77 | val seq1 = seq.takeWhile(p) 78 | val seq2 = seq.dropWhile(p) 79 | 80 | // After 81 | val (seq1, seq2) = seq.span(p) 82 | 83 | 84 | // Don't emulate "partition" 85 | 86 | // Before 87 | val seq1 = seq.filter(p) 88 | val seq2 = seq.filterNot(p) 89 | 90 | // After 91 | val (seq1, seq2) = seq.partition(p) 92 | 93 | 94 | // Don't emulate "takeRight" 95 | 96 | // Before 97 | seq.reverse.take(n).reverse 98 | 99 | // After 100 | seq.takeRight(n) 101 | 102 | 103 | // Don't emulate "flatten" 104 | 105 | // Before (seq: Seq[Seq[T]]) 106 | seq.reduce(_ ++ _) 107 | seq.fold(Seq.empty)(_ ++ _) 108 | seq.flatMap(identity) 109 | 110 | // After 111 | seq.flatten 112 | 113 | 114 | // Don't emulate "flatMap" 115 | 116 | // Before (f: A => Seq[B]) 117 | seq.map(f).flatten 118 | 119 | // After 120 | seq.flatMap(f) 121 | 122 | 123 | // Don't use "map" when result is ignored 124 | 125 | // Before 126 | seq.map(???) // the result is ignored 127 | 128 | // After 129 | seq.foreach(???) 130 | 131 | 132 | // Don't use "unzip" to extract a single component 133 | 134 | // Before (seq: Seq[(A, B]]) 135 | seq.unzip._1 136 | 137 | // After 138 | seq.map(_._1) 139 | 140 | 141 | // Don't create temporary collections 142 | 143 | // Transformation reduces collection to a single value. 144 | 145 | // Before 146 | seq.map(f).flatMap(g).filter(p).reduce(???) 147 | 148 | // After 149 | seq.view.map(f).flatMap(g).filter(p).reduce(???) 150 | 151 | 152 | // Transformation produces a collection of the same class. 153 | 154 | // Before 155 | seq.map(f).flatMap(g).filter(p) 156 | 157 | // After 158 | seq.view.map(f).flatMap(g).filter(p).force 159 | 160 | 161 | // Transformation creates a collection of different class. 162 | 163 | // Before 164 | seq.map(f).flatMap(g).filter(p).toList 165 | 166 | // After 167 | seq.view.map(f).flatMap(g).filter(p).toList 168 | 169 | // Using "breakOut" 170 | 171 | seq.map(f).toList(collection.breakOut): List[T] 172 | 173 | 174 | // Use assignment operators to reassign a sequence 175 | 176 | // Before 177 | seq = seq :+ x 178 | seq = x +: seq 179 | seq1 = seq1 ++ seq2 180 | seq1 = seq2 ++ seq1 181 | 182 | // After 183 | seq :+= x 184 | seq +:= x 185 | seq1 ++= seq2 186 | seq1 ++:= seq2 187 | 188 | 189 | // Don't convert collection type manually 190 | 191 | // Before 192 | seq.foldLeft(Set.empty)(_ + _) 193 | seq.foldRight(List.empty)(_ :: _) 194 | 195 | // After 196 | seq.toSet 197 | seq.toList 198 | 199 | // Be careful with "toSeq" on non-strict collections 200 | 201 | // Before (seq: TraversableOnce[T]) 202 | seq.toSeq 203 | 204 | // After 205 | seq.toStream 206 | seq.toVector 207 | 208 | // Don't convert to "String" manually 209 | 210 | // Before (seq: Seq[String]) 211 | seq.reduce(_ + _) 212 | seq.reduce(_ + separator + _) 213 | seq.fold(prefix)(_ + _) 214 | seq.map(_.toString).reduce(_ + _) // seq: Seq[T] 215 | seq.foldLeft(new StringBuilder())(_ append _) 216 | 217 | // After 218 | seq.mkString 219 | seq.mkString(prefix, separator, "") 220 | --------------------------------------------------------------------------------