从一个CGPoint数组中获取最大值和最小值

3
我们有一个CGPoint数组:
let points = [(1234.0, 1053.0), (1241.0, 1111.0), (1152.0, 1043.0)]

我要做的是在数组中获取具有最高x值和最高y值的CGPoint。我将使用这些点来创建CGRect:
extension CGRect {
    init(p1: CGPoint, p2: CGPoint) {
        self.init(x: min(p1.x, p2.x),
                  y: min(p1.y, p2.y),
                  width: abs(p1.x - p2.x),
                  height: abs(p1.y - p2.y))
    }
}

我知道有一种方法可以通过以下方式获取数组中的最大值和最小值:

points.min()
points.max()

但是这些似乎不起作用,因为它是一个CGPoint数组。从数组中获取这些值是否可能?


CGPoint不可比较,因为pointA < pointB的含义并不明确。您是想创建一个包含所有points的最小矩形吗? - Alexander
@Alexander-ReinstateMonica 我正在尝试根据y和x坐标的最高和最低值创建一个基于CGRect的形状,所以是的,基本上就是一个形状。 - SwiftyJD
3个回答

4
你可以通过映射值来找到x和y坐标的最小值和最大值,就像下面这样。如果你不确定points数组是否包含任何数据,使用守卫语句来避免强制解包:
let xArray = points.map(\.x)
let yArray = points.map(\.y)
guard let minX = xArray.min(),
      let maxX = xArray.max(),
      let minY = yArray.min(),
      let maxY = yArray.max() else { return }

从那里开始:
let minPoint = CGPoint(x: minX, y: minY)
let maxPoint = CGPoint(x: maxX, y: maxY)

然后你可以修改你的扩展函数,因为你已经知道哪些值是最小值和最大值。
extension CGRect {
    init(minPoint: CGPoint, maxPoint: CGPoint) {
        self.init(x: minPoint.x,
                  y: minPoint.y,
                  width: maxPoint.x - minPoint.x,
                  height: maxPoint.y - minPoint.y)
    }
}

如下评论中Leo Dabus所建议的,您可以在可失败的初始化器扩展中一次性完成所有操作。
extension CGRect {
    init?(points: [CGPoint]) {
        let xArray = points.map(\.x)
        let yArray = points.map(\.y)
        if  let minX = xArray.min(),
            let maxX = xArray.max(),
            let minY = yArray.min(),
            let maxY = yArray.max() {

            self.init(x: minX,
                      y: minY,
                      width: maxX - minX,
                      height: maxY - minY)
        } else {
            return nil
        }
    }
}

1
没有必要使用闭包 map(\.x)map(\.y) 并保留对它们的引用,而不是两次映射它们。您还可以将它们移动到初始化方法内部并使其可失败。 - Leo Dabus
1
谢谢@LeoDabus。根据你的建议,我更新了我的答案。 - RealUglyDuck

3
你可以使用max(by:)函数。
let minXPoint = points.min(by: {$0.x < $1.x}) //(1152.0, 1043.0)
let maxYPoint = points.max(by: {$0.y < $1.y}) //(1241.0, 1111.0)

我真的很喜欢这个答案。 - HalR
对于问题中提到的points,它是有效的。但是如果具有最小X值的点与具有最小Y值的点不同怎么办?那么它将返回具有最小X值的点,但该点可能没有最小Y值。 - RealUglyDuck

0

你不能仅通过单个对min()/max()的调用来完成这个。

根据y和x坐标的最高点和最低点

具有最小x值的点可能与具有最小y值的点不同。因此,任何只返回其中一个点的方法(如min()/max())都是不够的。

你需要分别调用两个函数,例如:

let minX = points.min(by: { $0.x < $1.x })!.x
let minY = points.min(by: { $0.y < $1.y })!.y
let maxX = points.min(by: { $0.x > $1.x })!.x
let maxY = points.min(by: { $0.y > $1.y })!.y

或者你可以尝试使用reduce一次性完成所有操作。
let initialAccumulator = (
    minX: CGFloat.max, minY: CGFloat.max, maxX: CGFloat.min, maxY: CGFloat.min)
let (minX, minY, maxX, maxY) = points.reduce(initialAccumulator) { accumulator, point in
    return (
        minX: min(accumulator.minX, point.x),
        minY: min(accumulator.minY, point.y),
        maxX: max(accumulator.maxX, point.x),
        maxY: max(accumulator.maxY, point.y)
    )
}

我可能会这样做:


extension CGRect {
    static func minimalRect(containing points: [CGPoint]) -> CGRect? {
        if points.isEmpty { return nil }

        let minX = points.min(by: { $0.x < $1.x })!.x
        let minY = points.min(by: { $0.y < $1.y })!.y
        let maxX = points.min(by: { $0.x > $1.x })!.x
        let maxY = points.min(by: { $0.y > $1.y })!.y

        return CGRect(
            x: minX,
            y: minY,
            width: (minX - maxX).magnitude,
            height: (minY - maxY).magnitude
        )
    }
}


let points = [(1234.0, 1053.0), (1241.0, 1111.0), (1152.0, 1043.0)].map(CGPoint.init)
let r = CGRect.minimalRect(containing: points)
print(r ?? .zero) // => (1152.0, 1043.0, 89.0, 68.0)

为什么不使用一个可失败的初始化器而不是一个静态方法? - Leo Dabus
我在这两个之间反复权衡,但在这种情况下,我没有使用初始化器,因为我想要在名称中使用单词"rect"。相比较而言,我更喜欢调用CGRect.minimalRect(containing: points),而不是CGRect(minimalRectContaining: points)或者CGRect(minimalContaining: Points) - Alexander

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