I am writing a simple Diceware password generator to experiment with RxSwift. I am struggling with using flatMap
and reduce
in separate steps.
我正在编写一个简单的Diceware密码生成器来试验RxSwift。我正在努力使用flatMap并在单独的步骤中减少。
Current code
目前的代码
I have an observable wordCount
that is bound to the UIStepper
value and generate a new password with a given number of words.
我有一个可观察的wordCount绑定到UIStepper值并生成一个具有给定数量的单词的新密码。
let rawPassword = wordCount
.asObservable()
.map { wordCount in
self.rollDice(numberOfDice: wordCount)
.map { numbers in wordMap[numbers]! }
}
rollDice
returns an Observable<String>
(for example: ["62345", "23423", "14231", ...]
) and is then mapped to words.
rollDice返回一个Observable
rawPassword
is an Observable<Observable<String>>
rawPassword是一个Observable
In this example, it would be: [["spec", "breed", "plins", "wiry", "chile", "cecil"]]
.
在这个例子中,它将是:[[“spec”,“breed”,“plins”,“wiry”,“chile”,“cecil”]]。
I then have a reducedPassword
which flatMap
and reduce
to a String
:
然后我有一个reducePassword,其中flatMap和reduce减少为一个字符串:
let reducedPassword = rawPassword
.flatMap { raw in
raw.reduce("") { prev, value in
let separator = "-"
return prev == "" ? value : "\(prev)\(separator)\(value)"
}
}
This works and I end up with the string: spec-breed-plins-wiry-chile-cecil
.
这有效,我最终得到了字符串:spec-breed-plins-wiry-chile-cecil。
The problem
问题
Now I want to change the word separator from the UI. I just want to re-apply the reduce on my rawPassword
when the text from the UITextField
is updated.
现在我想从UI更改单词分隔符。我只想在更新UITextField的文本时重新应用我的rawPassword上的reduce。
I am trying to use combineLatest
to combine the Observable<String>
for the separator with my Observable<Observable<String>>
rawPassword
, like this:
我试图使用combineLatest将分隔符的Observable
let reducedPassword = Observable.combineLatest(rawPassword, separator.asObservable()) { raw, sep in
raw.reduce("") { prev, value in
return prev == "" ? value : "\(prev)\(sep)\(value)"
}
}
But the reduce
never fires and clicking the stepper does nothing. I have tried to flatMap
in a separate step but then, with combineLatest
I only end up with the last word. Is combineLatest
the right approach at all?
但是减少永远不会发生,点击步进器什么都不做。我试图在一个单独的步骤中进行flatMap但是然后,使用combineLatest,我最终只得到了最后一个单词。结合起来是最正确的方法吗?
1 个解决方案
#1
2
The Problem
The problem is that reducedPassword
(in your combineLatest
case) is really Observable<Observable<String>>
. It helps to type annotate, especially when learning RxSwift
to ensure things are actually what you expect them to be.
问题是reducedPassword(在你的combineLatest案例中)实际上是Observable
let reducedPassword: Observable<Observable<String>> = Observable.combineLatest(rawPassword, separator.asObservable()) { // ...
The Fix
So, to get your modified reducedPassword
to work, you need to take elements coming through (Observable<String>
) and replace the current Observable<Observable<String>>
with it. So use switchLatest()
(which is like doing flatMap { $0 }
):
因此,要使修改后的reducedPassword生效,您需要获取通过的元素(Observable
let reducedPassword = Observable.combineLatest(rawPassword, separator.asObservable()) {
raw, sep in
raw.reduce("") { prev, value in
prev == "" ? value : "\(prev)\(sep)\(value)"
}
}
.switchLatest() // equivalent to: .flatMap { $0 }
All Together
I put all of your code together, along with the fix, into something that can be run as-is with no UI:
我把所有代码和修复程序放在一起,可以按原样运行,没有UI:
let disposeBag = DisposeBag()
let wordCount = BehaviorSubject<UInt>(value: 2)
let separator = BehaviorSubject<String>(value: "-")
let wordMap = ["0" : "zero", "1" : "one", "2" : "two", "3" : "three", "4" : "four"]
func rollDice(numberOfDice: UInt) -> Observable<String> {
let maxNumber = wordMap.count
return Observable.from(0..<numberOfDice)
.map { _ in Int(arc4random()) % maxNumber }
.map { String($0) }
}
let rawPassword = wordCount
.asObservable()
.map { wordCount in
rollDice(numberOfDice: wordCount)
.map { numbers in wordMap[numbers]! }
}
let reducedPassword = Observable.combineLatest(rawPassword, separator.asObservable()) {
raw, sep in
raw.reduce("") { prev, value in
prev == "" ? value : "\(prev)\(sep)\(value)"
}
}
.switchLatest() // equivalent to: .flatMap { $0 }
reducedPassword
.subscribe(onNext: { print($0) })
separator.onNext("||||")
wordCount.onNext(4)
separator.onNext("___")
Outputs:
输出:
zero-four
zero||||two
two||||four||||zero||||one
three___two___two___four零四零||||二二||||四||||零||||一三___两个_two___四
Notes
-
I'm not sure why
wordMap
is[String : String]
instead of just[String]
. It seems strange to index a map with something likewordMap["123"]
instead ofwordMap[123]
.我不确定为什么wordMap是[String:String]而不仅仅是[String]。使用wordMap [“123”]而不是wordMap [123]来索引地图似乎很奇怪。
-
It would be easier to implement and understand if you were to not use
Observable<Observable<String>>
, but ratherObservable<[String]>
. This is why you had the problem, and even when you understand it, it's still more complex than it needs to be.如果你不使用Observable
>,而是使用Observable <[String]>,那么实现和理解会更容易。这就是你遇到问题的原因,即使你理解它,它仍然比它需要的更复杂。 -
Remember, type annotate your variables and closures to make sure you're doing what you expect. You'll catch these issues earlier.
请记住,键入注释您的变量和闭包,以确保您正在做您期望的。你会早点发现这些问题。
Improved Solution
Here's another way you could write it, taking into account the notes I've outlined above.
考虑到我上面概述的注释,这是你可以写的另一种方式。
let disposeBag = DisposeBag()
let wordCount = BehaviorSubject<UInt>(value: 2)
let separator = BehaviorSubject<String>(value: "-")
let wordMap = ["zero", "one", "two", "three", "four"]
func rollDice(numberOfDice: UInt) -> [Int] {
let maxNumber = wordMap.count
return (0..<numberOfDice).map { _ in
Int(arc4random()) % maxNumber
}
}
let words: Observable<[String]> = wordCount
.asObservable()
.map { (count: UInt) -> [String] in
return rollDice(numberOfDice: count)
.map { roll in wordMap[roll] }
}
let password: Observable<String> = Observable.combineLatest(words, separator.asObservable()) {
(words: [String], separator: String) -> String in
words.reduce("") { prev, value in
prev == "" ? value : "\(prev)\(separator)\(value)"
}
}
password.subscribe(onNext: { print($0) })
separator.onNext("||||")
wordCount.onNext(4)
separator.onNext("___")
#1
2
The Problem
The problem is that reducedPassword
(in your combineLatest
case) is really Observable<Observable<String>>
. It helps to type annotate, especially when learning RxSwift
to ensure things are actually what you expect them to be.
问题是reducedPassword(在你的combineLatest案例中)实际上是Observable
let reducedPassword: Observable<Observable<String>> = Observable.combineLatest(rawPassword, separator.asObservable()) { // ...
The Fix
So, to get your modified reducedPassword
to work, you need to take elements coming through (Observable<String>
) and replace the current Observable<Observable<String>>
with it. So use switchLatest()
(which is like doing flatMap { $0 }
):
因此,要使修改后的reducedPassword生效,您需要获取通过的元素(Observable
let reducedPassword = Observable.combineLatest(rawPassword, separator.asObservable()) {
raw, sep in
raw.reduce("") { prev, value in
prev == "" ? value : "\(prev)\(sep)\(value)"
}
}
.switchLatest() // equivalent to: .flatMap { $0 }
All Together
I put all of your code together, along with the fix, into something that can be run as-is with no UI:
我把所有代码和修复程序放在一起,可以按原样运行,没有UI:
let disposeBag = DisposeBag()
let wordCount = BehaviorSubject<UInt>(value: 2)
let separator = BehaviorSubject<String>(value: "-")
let wordMap = ["0" : "zero", "1" : "one", "2" : "two", "3" : "three", "4" : "four"]
func rollDice(numberOfDice: UInt) -> Observable<String> {
let maxNumber = wordMap.count
return Observable.from(0..<numberOfDice)
.map { _ in Int(arc4random()) % maxNumber }
.map { String($0) }
}
let rawPassword = wordCount
.asObservable()
.map { wordCount in
rollDice(numberOfDice: wordCount)
.map { numbers in wordMap[numbers]! }
}
let reducedPassword = Observable.combineLatest(rawPassword, separator.asObservable()) {
raw, sep in
raw.reduce("") { prev, value in
prev == "" ? value : "\(prev)\(sep)\(value)"
}
}
.switchLatest() // equivalent to: .flatMap { $0 }
reducedPassword
.subscribe(onNext: { print($0) })
separator.onNext("||||")
wordCount.onNext(4)
separator.onNext("___")
Outputs:
输出:
zero-four
zero||||two
two||||four||||zero||||one
three___two___two___four零四零||||二二||||四||||零||||一三___两个_two___四
Notes
-
I'm not sure why
wordMap
is[String : String]
instead of just[String]
. It seems strange to index a map with something likewordMap["123"]
instead ofwordMap[123]
.我不确定为什么wordMap是[String:String]而不仅仅是[String]。使用wordMap [“123”]而不是wordMap [123]来索引地图似乎很奇怪。
-
It would be easier to implement and understand if you were to not use
Observable<Observable<String>>
, but ratherObservable<[String]>
. This is why you had the problem, and even when you understand it, it's still more complex than it needs to be.如果你不使用Observable
>,而是使用Observable <[String]>,那么实现和理解会更容易。这就是你遇到问题的原因,即使你理解它,它仍然比它需要的更复杂。 -
Remember, type annotate your variables and closures to make sure you're doing what you expect. You'll catch these issues earlier.
请记住,键入注释您的变量和闭包,以确保您正在做您期望的。你会早点发现这些问题。
Improved Solution
Here's another way you could write it, taking into account the notes I've outlined above.
考虑到我上面概述的注释,这是你可以写的另一种方式。
let disposeBag = DisposeBag()
let wordCount = BehaviorSubject<UInt>(value: 2)
let separator = BehaviorSubject<String>(value: "-")
let wordMap = ["zero", "one", "two", "three", "four"]
func rollDice(numberOfDice: UInt) -> [Int] {
let maxNumber = wordMap.count
return (0..<numberOfDice).map { _ in
Int(arc4random()) % maxNumber
}
}
let words: Observable<[String]> = wordCount
.asObservable()
.map { (count: UInt) -> [String] in
return rollDice(numberOfDice: count)
.map { roll in wordMap[roll] }
}
let password: Observable<String> = Observable.combineLatest(words, separator.asObservable()) {
(words: [String], separator: String) -> String in
words.reduce("") { prev, value in
prev == "" ? value : "\(prev)\(separator)\(value)"
}
}
password.subscribe(onNext: { print($0) })
separator.onNext("||||")
wordCount.onNext(4)
separator.onNext("___")