在reduce中使用逻辑运算符作为组合闭包

32

我尝试使用以下代码通过逻辑运算符OR (||) 来缩减一个 Bool 类型的数组,但是我遇到了一个错误:

func reduceBools(values: [Bool]) -> Bool {
    return values.reduce(false, combine: ||)
}

成员' || '的引用不明确

类比于整数,该代码可以完美运行。

func reduceInts(values: [Int]) -> Int {
    return values.reduce(0, combine: +)
}

我通过添加一个||函数(下面是代码)或使用{$0 || $1}闭包使其工作,但我不喜欢这些方法,我宁愿直接传递操作符。

func ||(lhs: Bool, rhs: Bool) -> Bool {
    return lhs || rhs
}

逻辑AND&&)运算符也是同样的情况。

我如何在不使用上述hack的情况下使其起作用?


3
这似乎是由于 ||&& 的 "自动闭包" 参数导致的一个错误/限制。请参考 https://dev59.com/MYjca4cB1Zd3GeqPrhYq 以及以下评论。 - Martin R
@MartinR 谢谢你提供的链接!我一直在想,也许我只是个愚蠢的人... - user3441734
@MartinR 看起来是这样的 :/ 有没有相关的 rdar 让我可以复制一下? - fpg1503
@fpg1503:据我所知没有。 - Martin R
我已经打开了一个错误报告 - fpg1503
6个回答

43

作为替代方案,您可以使用以下方法

// ||
func reduceBoolsOr(values: [Bool]) -> Bool {
    return values.contains(true)
}

// &&
func reduceBoolsAnd(values: [Bool]) -> Bool {
    return !values.contains(false)
}
注意,.reduce 会带来开销。如果你关心的是最终结果而不是在这种情况下查询||&&运算符的意外行为,那么也许上面的实用方法可以帮到你,即使它并没有真正地减少数组,但由于布尔类型的简单性质,产生了相同的结果。

2
我认为在这种情况下使用contains更好,因为你描述了想要获得的结果,而不是如何计算它。这甚至比通常的函数式方法更加声明性。 :) - NiñoScript

14

Swift 4.2+ / Xcode 10.0+

Swift的现代版本中有一个allSatisfy函数,用于检查所有元素是否符合某个规则。


在OP的情况下:

values.allSatisfy { $0 }

更新:

若要使其适用于OR,请执行以下操作:

!values.allSatisfy{!$0}

感谢Andy Weinstein


为了使其适用于“或”运算,请执行!values.allSatisfy{!$0} - Andy Weinstein

8
以下方法可行。
values.reduce(false) { $0 || $1 }

2
这是由于Swift的闭包语义引起的。它接受您的参数并对它们应用函数,省略参数名称。
protocol Numeric {
    ...
    public static func +(lhs: Self, rhs: Self) -> Self
    ...
}

在处理整数的示例中,您将(Int, Int)传递给闭包,并且Numeric协议中的+函数期望精确地将两个整数相加。
这就是为什么像下面的代码能够正常工作。
[1, 2, 3, 4].reduce(0, +)

因为你只输入了两个整数,并应用了一个只接受两个整数的函数。 如果你自己编写一个只需要两个参数的函数,它也可以正常工作。

func myOwnAwesomeFunc<T: Numeric>(a: T, b: T) -> T { in
    return 1 // production ready
}

[1, 2, 3, 4].reduce(0, myOwnAwesomeFunc) // prints 1

到目前为止,一切都很好。但为什么我们不能写

[true, false, true].reduce(false, ||) // yields Cannot invoke 'reduce' 
// with an argument list of type 
// '(Bool, (Bool, @autoclosure () throws -> Bool) throws -> Bool)'

这是因为此运算符需要一个布尔值和一个返回布尔值的闭包,而不是布尔值和闭包! 但如果这样,为什么我们不写 true || { false }() 呢? 这是由于 @autoclosure 会自动处理花括号。 主要问题是,为什么要这样实现,以至于我们不能使用 Swift 的精简闭包语法与布尔值?我不知道。

5
这样写是为了让 || 运算符可以短路,并且避免在 LHV 为 true 的情况下评估 RHV。 - jemmons
谢谢,这很有帮助。希望它能以更符合预期的方式工作。 - Alex V.

2

“成员||的歧义引用”意味着存在多个可能的候选项,编译器无法选择。在您的情况下,这些是:

public func ||<T : BooleanType, U : BooleanType>(lhs: T, @autoclosure rhs: () throws -> U) rethrows -> Bool

并且

public func ||<T : BooleanType>(lhs: T, @autoclosure rhs: () throws -> Bool) rethrows -> Bool

也许你使用{ $0 || $1 }的'黑科技'是这里最好的解决方案。


看起来第二个的原因是第一个由于一个 bug 不能被 @_transparent。最疯狂的事情是它们的实现是相同的。 - fpg1503

0
这是另一种方法,我修改了 reduceBools 函数以接受运算符作为参数 -
typealias LogicalOperator = ((Bool, @autoclosure () throws -> Bool) throws -> Bool)

func reduceBools(values: [Bool], combine: LogicalOperator) -> Bool {
    var started: Bool = false
    return values.reduce(into: true, { (result, value) in
        result = started ? try! combine(result, value) : value // obviously up to you how you'd handle the try/catch
        started = true
    })
}

let bools = [true, false, false, true]

let result1 = self.reduceBools(values: bools, combine: ||)
print(result1) // prints true

let result2 = self.reduceBools(values: bools, combine: &&)
print(result2) // prints false

或者它作为序列的扩展会更有用 -

extension Sequence where Element == Bool {

    func reduce(_ combine: LogicalOperator) -> Bool {
        var started: Bool = false
        return self.reduce(into: true, { (result, value) in
            result = started ? try! combine(result, value) : value
            started = true
        })
    }
}

print(bools.reduce(||)) // prints true

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接