Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

操作列表

压缩展平是两种常见的操作,用于操作 List

压缩

zip() 通过模仿夹克上的拉链的行为,将两个 List 结合在一起,将相邻的 List 元素成对配对:

// ManipulatingLists/Zipper.kt
import atomictest.eq

fun main() {
  val left = listOf("a", "b", "c", "d")
  val right = listOf("q", "r", "s", "t")

  left.zip(right) eq                 // [1]
    "[(a, q), (b, r), (c, s), (d, t)]"

  left.zip(0..4) eq                  // [2]
    "[(a, 0), (b, 1), (c, 2), (d, 3)]"

  (10..100).zip(right) eq            // [3]
    "[(10, q), (11, r), (12, s), (13, t)]"
}
  • [1]leftright 进行压缩,结果是一个 PairList,将 left 中的每个元素与其相应的 right 中的元素配对。
  • [2] 您还可以使用范围来对 zip() 进行调用。
  • [3] 范围 10..100right 大得多,但在其中一个序列用尽时,压缩过程会停止。

zip() 也可以对其创建的每个 Pair 执行操作:

// ManipulatingLists/ZipAndTransform.kt
package manipulatinglists
import atomictest.eq

data class Person(
  val name: String,
  val id: Int
)

fun main() {
  val names = listOf("Bob", "Jill", "Jim")
  val ids = listOf(1731, 9274, 8378)
  names.zip(ids) { name, id ->
    Person(name, id)
  } eq "[Person(name=Bob, id=1731), " +
    "Person(name=Jill, id=9274), " +
    "Person(name=Jim, id=8378)]"
}

names.zip(ids) { ... } 生成了一个名称 - id Pair 序列,并将 lambda 应用于每个 Pair。结果是初始化的 Person 对象的 List

要从单个 List 中合并两个相邻的元素,使用 zipWithNext()

// ManipulatingLists/ZippingWithNext.kt
import atomictest.eq

fun main() {
  val list = listOf('a', 'b', 'c', 'd')

  list.zipWithNext() eq listOf(
    Pair('a', 'b'),
    Pair('b', 'c'),
    Pair('c', 'd'))

  list.zipWithNext { a, b -> "$a$b" } eq
    "[ab, bc, cd]"
}

zipWithNext() 的第二次调用在压缩之后执行了附加操作。

展平

flatten() 接受一个包含元素本身是 List 的元素的 List(即 ListList),并将其展平为包含单个元素的 List

// ManipulatingLists/Flatten.kt
import atomictest.eq

fun main() {
  val list = listOf(
    listOf(1, 2),
    listOf(4, 5),
    listOf(7, 8),
  )
  list.flatten() eq "[1, 2, 4, 5, 7, 8]"
}

flatten() 帮助我们理解另一个在集合上执行的重要操作:flatMap()。让我们产生一个范围的所有可能的 Pair

// ManipulatingLists/FlattenAndFlatMap.kt
import atomictest.eq

fun main() {
  val intRange = 1..3

  intRange.map { a ->          // [1]
    intRange.map { b -> a to b }
  } eq "[" +
    "[(1, 1), (1, 2), (1, 3)], " +
    "[(2,

 1), (2, 2), (2, 3)], " +
    "[(3, 1), (3, 2), (3, 3)]" +
    "]"

  intRange.map { a ->          // [2]
    intRange.map { b -> a to b }
  }.flatten() eq "[" +
    "(1, 1), (1, 2), (1, 3), " +
    "(2, 1), (2, 2), (2, 3), " +
    "(3, 1), (3, 2), (3, 3)" +
    "]"

  intRange.flatMap { a ->      // [3]
    intRange.map { b -> a to b }
  } eq "[" +
    "(1, 1), (1, 2), (1, 3), " +
    "(2, 1), (2, 2), (2, 3), " +
    "(3, 1), (3, 2), (3, 3)" +
    "]"
}

每种情况下的 lambda 都是相同的:将每个 intRange 元素与每个 intRange 元素结合,以生成所有可能的 a to b Pair。但是在 [1] 中,map() 有助于保留额外的信息,这里我们已经生成了三个 List,每个都对应于 intRange 中的一个元素。在某些情况下,这些额外的信息是必不可少的,但在这里我们不需要它 - 我们只需要一个单一的扁平 List,其中包含所有组合,没有额外的结构。

有两个选择。[2] 展示了将 flatten() 函数应用于移除这个额外结构并将结果展平为单个 List,这是可接受的方法。但是,这是一个非常常见的任务,Kotlin 提供了一个称为 flatMap() 的组合操作,它通过单个调用执行 map()flatten()[3] 展示了 flatMap() 的使用。在大多数支持函数式编程的语言中,您会发现 flatMap()

下面是 flatMap() 的另一个示例:

// ManipulatingLists/WhyFlatMap.kt
package manipulatinglists
import atomictest.eq

class Book(
  val title: String,
  val authors: List<String>
)

fun main() {
  val books = listOf(
    Book("1984", listOf("George Orwell")),
    Book("Ulysses", listOf("James Joyce"))
  )
  books.map { it.authors }.flatten() eq
    listOf("George Orwell", "James Joyce")
  books.flatMap { it.authors } eq
    listOf("George Orwell", "James Joyce")
}

我们希望得到一个作者的 Listmap() 生成了一个作者的 ListList,这并不是很方便。flatten() 将其接受并生成一个简单的 ListflatMap() 在单个步骤中生成相同的结果。

在此示例中,我们使用 map()flatMap()enum SuitRank 结合在一起,生成一副 Card

// ManipulatingLists/PlayingCards.kt
package manipulatinglists
import kotlin.random.Random
import atomictest.*

enum class Suit {
  Spade, Club, Heart, Diamond
}

enum class Rank(val faceValue: Int) {
  Ace(1), Two(2), Three(3), Four(4), Five(5),
  Six(6), Seven(7), Eight(8), Nine(9),
  Ten(10), Jack(10), Queen(10), King(10)
}

class Card(val rank: Rank, val suit: Suit) {
  override fun toString() =
    "$rank of ${suit}s"
}

val deck: List<Card> =
  Suit.values().flatMap { suit ->
    Rank.values().map { rank ->
      Card(rank, suit)
    }
  }

fun main() {
  val rand = Random(26)
  repeat(7) {
    trace("'${deck.random(rand)}'")
  }
  trace eq """
    'Jack of Hearts' 'Four of Hearts'
    'Five of Clubs' 'Seven of Clubs'
    'Jack of Diamonds' 'Ten of Spades'
    'Seven of Spades'
  """
}

在初始化 deck 中,内部的 Rank.values().map 生成了四个 List,一个对应于每个 Suit,因此我们在外部循环上使用了 flatMap(),以生成一个

CardList,用于 deck

练习和解答可以在 www.AtomicKotlin.com 找到。