Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Collection


May 13, 2021 Scala


Table of contents


Collection

The basic data structure

Scala provides some nice collections.

Refer to Effective Scala's views on how to use the collection.

List List

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

Set Set

The set is not repeated

scala> Set(1, 1, 2)
res0: scala.collection.immutable.Set[Int] = Set(1, 2)

Tuple for tuples

A u-group is a simple collection of logic that combines elements without using classes.

scala> val hostPort = ("localhost", 80)
hostPort: (String, Int) = (localhost, 80)

Unlike sample classes, the metagroup cannot get a field by name, but instead reads the object using the position undersrage, which is based on 1, not 0.

scala> hostPort._1
res0: String = localhost

scala> hostPort._2
res1: Int = 80

The group can be well combined with pattern matching.

hostPort match {
  case ("localhost", port) => ...
  case (host, port) => ...
}

When you create a two-element group, you can use a special syntax: ->

scala> 1 -> 2
res0: (Int, Int) = (1,2)

Refer to Effective Scala's view of http://twitter.github.com/effectivescala/#Functional (http://twitter.github.com/effectivescala/#Functional programming-Destructuring bindings) ("dismantling" a yuangroup).

Map map

It can hold basic data types.

Map(1 -> 2)
Map("foo" -> "bar")

This may seem like a special syntax, but don't forget that ->

The Map() method also uses a list of variable parameters learned from the first lesson: Map(1 -> "one", 2 -> "two") to Map((1, "one"), (2, "two")) the first argument is the key of the map and the second parameter is the value of the map.

The value of the map can be a map or even a function.

Map(1 -> Map("foo" -> "bar"))
Map("timesTwo" -> { timesTwo(_) })

Option Option

Option is a container that represents a possible inclusion of values.

Option's basic interface is this:

trait Option[T] {
  def isDefined: Boolean
  def get: T
  def getOrElse(t: T): T
}

Option itself is generic and has two sub-classes: Some.T. or None

Let's look at an example of using Option:

Map.get uses Option as its return value, indicating that the method may not return the value you requested.

scala> val numbers = Map("one" -> 1, "two" -> 2)
numbers: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 1, two -> 2)

scala> numbers.get("two")
res0: Option[Int] = Some(2)

scala> numbers.get("three")
res1: Option[Int] = None

Now that our data seems to be stuck in Option, how do we get it?

Intuitively, it is possible to use conditional judgment on the isDefined method.

// We want to multiply the number by two, otherwise return 0.
val result = if (res1.isDefined) {
  res1.get * 2
} else {
  0
}

We recommend using getOrElse or pattern matching to handle this result.

getOrElse makes it easy for you to define a default value.

val result = res1.getOrElse(0) * 2

Pattern matching works naturally with Option.

val result = res1 match {
  case Some(n) => n * 2
  case None => 0
}

Refer to Effective Scala's comments on http://twitter.github.com/effectivescala/#Functional options (programming-options).

Function combinations

List(1, 2, 3) map squared applies the squared function to each element in the list and returns a List(1, 4, 9) W e call this operation map combination. ( If you want a better definition, you might like the description of the combiner on Stackoverflow.) They are often used in standard data structures.

map

Map applies a function to each element in the list, returning a list of elements that were applied.

scala> numbers.map((i: Int) => i * 2)
res0: List[Int] = List(2, 4, 6, 8)

or pass in a section of the application function

scala> def timesTwo(i: Int): Int = i * 2
timesTwo: (i: Int)Int

scala> numbers.map(timesTwo _)
res0: List[Int] = List(2, 4, 6, 8)

Foreach

Foreach is much like map, but does not return a value. Foreach is only used for functions [side-effects] . . side-effects.

scala> numbers.foreach((i: Int) => i * 2)

Nothing returned.

You can try to store the return value, but it will be a Unit type (i.e. void)

scala> val doubled = numbers.foreach((i: Int) => i * 2)
doubled: Unit = ()

filter

Filter removes any elements that calculate the result of false for the incoming function. A function that returns a Boolean value is often referred to as a [或判定函数] .

scala> numbers.filter((i: Int) => i % 2 == 0)
res0: List[Int] = List(2, 4)
scala> def isEven(i: Int): Boolean = i % 2 == 0
isEven: (i: Int)Boolean

scala> numbers.filter(isEven _)
res2: List[Int] = List(2, 4)

Zip

Zip aggregates the contents of two lists into a pair list.

scala> List(1, 2, 3).zip(List("a", "b", "c"))
res0: List[(Int, String)] = List((1,a), (2,b), (3,c))

partition

partition splits the list using the given predicate function.

scala> val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> numbers.partition(_ % 2 == 0)
res0: (List[Int], List[Int]) = (List(2, 4, 6, 8, 10),List(1, 3, 5, 7, 9))

find

Find returns the first element in the collection that matches the predicate function.

scala> numbers.find((i: Int) => i > 5)
res0: Option[Int] = Some(6)

drop & dropWhile

Drop deletes the first i element

scala> numbers.drop(5)
res0: List[Int] = List(6, 7, 8, 9, 10)

DropWhile deletes the element until the first element that matches the predicate function is found. For example, if we use the dropWhile odd function on the numbers list, 1 will be discarded (but 3 will not be discarded because it is "protected" by 2).

scala> numbers.dropWhile(_ % 2 != 0)
res0: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10)

foldLeft

scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n)
res0: Int = 55

0 is the initial value (remember that the numbers are of the List(Int) type), and m is an adder.

Observe the operation directly:

scala> numbers.foldLeft(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); m + n }
m: 0 n: 1
m: 1 n: 2
m: 3 n: 3
m: 6 n: 4
m: 10 n: 5
m: 15 n: 6
m: 21 n: 7
m: 28 n: 8
m: 36 n: 9
m: 45 n: 10
res0: Int = 55

foldRight

Like foldLeft, it just runs the opposite way.

scala> numbers.foldRight(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); m + n }
m: 10 n: 0
m: 9 n: 10
m: 8 n: 19
m: 7 n: 27
m: 6 n: 34
m: 5 n: 40
m: 4 n: 45
m: 3 n: 49
m: 2 n: 52
m: 1 n: 54
res0: Int = 55

flatten

Flatten flatten flattens the nested structure into a hierarchical collection.

scala> List(List(1, 2), List(3, 4)).flatten
res0: List[Int] = List(1, 2, 3, 4)

flatMap

FlatMap is a common combination that combines mapping with mapping and flattening. FlatMap requires a function that handles nested lists and then strings the results together.

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

scala> nestedNumbers.flatMap(x => x.map(_ * 2))
res0: List[Int] = List(2, 4, 6, 8)

Think of it as a quick action to "map first and flatten later":

scala> nestedNumbers.map((x: List[Int]) => x.map(_ * 2)).flatten
res1: List[Int] = List(2, 4, 6, 8)

This example calls map first, and then flatten immediately, which is the characteristic of "combinors" and the nature of these functions.

Refer to Effective Scala's comments on http://twitter.github.com/effectivescala/#Functional flatMap

Extended function combinations

Now we've learned some functions on the collection.

We'll try to write our own function combinations.

Interestingly, each function combination shown above can be implemented using the fold method. Let's look at some examples.

def ourMap(numbers: List[Int], fn: Int => Int): List[Int] = {
  numbers.foldRight(List[Int]()) { (x: Int, xs: List[Int]) =>
    fn(x) :: xs
  }
}

scala> ourMap(numbers, timesTwo(_))
res0: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

Why List[Int]()?Scala not smart enough to understand that your goal is to accumulate results in an empty list of Int types.

Map?

All of the function combinations shown can be used on map. Map can be thought of as a list of binary groups, so you write a function that deals with a binary group of keys and values.

scala> val extensions = Map("steve" -> 100, "bob" -> 101, "joe" -> 201)
extensions: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101), (joe,201))

Now filter out entries with a telephone extension number below 200.

scala> extensions.filter((namePhone: (String, Int)) => namePhone._2 < 200)
res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))

Because parameters are metagroups, you must use the location acquirer to read their keys and values.

Fortunately, we can actually use pattern matching to extract keys and values more gracefully.

scala> extensions.filter({case (name, extension) => extension < 200})
res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))