操作列表
压缩和展平是两种常见的操作,用于操作
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] 将
left
与right
进行压缩,结果是一个Pair
的List
,将left
中的每个元素与其相应的right
中的元素配对。 - [2] 您还可以使用范围来对
zip()
进行调用。 - [3] 范围
10..100
比right
大得多,但在其中一个序列用尽时,压缩过程会停止。
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
(即 List
的 List
),并将其展平为包含单个元素的 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")
}
我们希望得到一个作者的 List
。map()
生成了一个作者的 List
的 List
,这并不是很方便。flatten()
将其接受并生成一个简单的 List
。flatMap()
在单个步骤中生成相同的结果。
在此示例中,我们使用 map()
和 flatMap()
将 enum
Suit
和 Rank
结合在一起,生成一副 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()
,以生成一个
Card
的 List
,用于 deck
。
练习和解答可以在 www.AtomicKotlin.com 找到。