如何获取一个CGPoint数组中每个值重复出现的次数?

3

我有一个CGPoint的数组,

pointArray = [(532.7, 150.0), (66.6, 150.0), (129.2, 150.0), (129.2, 150.0), (301.2, 150.0), (444.2, 150.0), (532.7, 150.0), (532.7, 150.0), (532.7, 150.0)]

如何获取每个点重复出现的次数?

@LeoDabus 让它成为可哈希的 :) - Alexander
@LeoDabus 当然可以。Martin已经为您完成了这项工作 :) - Alexander
@LeoDabus 我不明白你在问什么。 - Alexander
@Alexander 谢谢,我肯定会等待你的答案,不过希望它能早点到来 :) - Containment
@LeoDabus 不,我会使用像你在代码审查SE中链接的哈希函数。 - Alexander
显示剩余6条评论
3个回答

2

如@Alexander在评论中所说,你应该使用NSCountedSet,你可以像这样使用它:

let array = [
    CGPoint(x: 532.7, y: 150.0),
    CGPoint(x: 66.6, y: 150.0),
    CGPoint(x: 129.2, y: 150.0),
    CGPoint(x: 129.2, y: 150.0),
    CGPoint(x: 301.2, y: 150.0),
    CGPoint(x: 444.2, y: 150.0),
    CGPoint(x: 532.7, y: 150.0),
    CGPoint(x: 532.7, y: 150.0),
    CGPoint(x: 532.7, y: 150.0)
]

let countedSet = NSCountedSet(array: array)
countedSet.count(for: array.last!) //returns 4

如果您不想使用NSCountedSet,可以将每个点存储在字典中作为键,并将计数作为值。棘手的问题是CGPoint不符合Hashable,您可以像这样处理:

extension CGPoint:Hashable{
    public var hashValue: Int {
        let x = Double(self.x)
        let y = Double(self.y)

        return Int(((x + y)*(x + y + 1)/2) + y)
        //this hash function may not be the best for your data

    }
}

var map = [CGPoint:Int]()
for point in array {
    if let count = map[point] {
        map[point] = count + 1
    } else {
        map[point] = 1
    }
}

map[array.last!]//returns 4

我认为最好使用一个NSCountedSet

NSCountedSet也需要Hashable。幕后的技巧是将值转换为NSValue,我相信这样做。 - Sulthan
NSCountedSet有两个初始化方法: public convenience init(array: [Any])public convenience init(set: Set<AnyHashable>)你只需要将一个Set传递给init,并确保它符合Hashable协议 (我使用的是Swift 3.0.2和Xcode 8.2.1) - Rodrigo Ruiz Murguía

2
我认为使用CountedSet有点过头了。我会直接对它们进行计数:
let points = [CGPoint(x: 532.7, y: 150.0), CGPoint(x: 66.6, y: 150.0), CGPoint(x: 129.2, y: 150.0), CGPoint(x: 129.2, y: 150.0), CGPoint(x: 301.2, y: 150.0), CGPoint(x: 444.2, y: 150.0), CGPoint(x: 532.7, y: 150.0), CGPoint(x: 532.7, y: 150.0), CGPoint(x: 532.7, y: 150.0)]
var d : [NSValue:Int] = [:]
for p in points {
    let v = NSValue(cgPoint:p)
    if let ct = d[v] {
        d[v] = ct+1
    } else {
        d[v] = 1
    }
}
// how many times does `points[0]` appear in `points`?
d[NSValue(cgPoint:points[0])] // 4

在我看来,这是最好的方法。您还可以使CGPoint可哈希,返回其对应的NSValue hashValue。 - Leo Dabus
请注意,您可以将CGPoint强制转换为NSValue,而无需创建新对象。 - Leo Dabus
1
@LeoDabus 是的,我认为这是在Swift 3.0.1中全新的特性,而我仍然不习惯它。 - matt

0

我同意Matt的看法,认为已接受的解决方案似乎有些过度。我建议采用类似于他所采取的函数式方法:

let dictionary:[String:Int] = array.reduce([:]){
    var dict = $0.0, key = String(describing: $0.1)
    dict[key] = (dict[key] ?? 0 ) + 1
    return dict
}

使用这种方法,对于您的原始数组,我们得到以下结果:
let pointArray:[(CGFloat, CGFloat)] = [(532.7, 150.0), (66.6, 150.0), (129.2, 150.0), (129.2, 150.0), (301.2, 150.0), (444.2, 150.0), (532.7, 150.0), (532.7, 150.0), (532.7, 150.0)]

let dictionary:[String:Int] = pointArray.reduce([:]){
    var dict = $0.0, key = String(describing: $0.1)
    dict[key] = (dict[key] ?? 0 ) + 1
    return dict
}

print(dictionary)  //output is ["(129.2, 150.0)": 2, "(66.6, 150.0)": 1, "(532.7, 150.0)": 4, "(444.2, 150.0)": 1, "(301.2, 150.0)": 1]

请注意,使用String(describing:)作为字典键可以使其与元组一起使用,就像您的示例代码中一样,以及其他不一定是CGPointHashable实例的变化。这将为字典提供一个Hashable键,而无需编写自定义代码;然而,正如亚历山大在下面的评论中指出的那样,这并不是生成Hashable值的最有效方法,如果您正在处理成千上万个点或更多,或者在循环中频繁运行此操作,则使用String(describing:)与使用自定义函数从您的点元组生成哈希之间的差异可能是显着的。

@Alexander 一般来说是可以的。在这种情况下,由于他的(CGFloat, CGFloat)值数组不会发生碰撞,因此您无法使元组Hashable。使用CGPoint上的Hashable扩展或将CGPoint包装在NSValue中是可以替换其他情况的实现,但是这个答案提供了:1)算法的功能风格2)适用于OP示例输入的解决方案,这两者都没有被以前的答案涵盖。 - Daniel Hall
当然会导致冲突。您正在使用一个1个Int hashValue来表示CGPoint的2个Int。不存在任何函数可以进行1:1映射。您的算法很好,但是如果为CGPoint定义适当的哈希函数而不是使用String(describing:),它将更加优秀。 - Alexander
我说错了,我的意思是您不能使用1个Int hashValue表示2个64位值(Doubles)。您必须有碰撞,因此最好使用将它们在被散列的数据域中保持尽可能不频繁的散列算法。 String(describing:)输出一个字符串,该字符串对于元组中每个唯一的Float组合是唯一的。这是正确的。 并且不会与其他组合的String值的哈希发生冲突。那是不对的。可能的字符串比唯一哈希值多。 根据鸽笼原理,无法避免碰撞。 - Alexander
1
每个元组都需要在堆上进行动态分配,以容纳String缓冲区。对于任何非平凡数量的元组,在自己实现简单的哈希函数方面将会有明显的性能优势。特别是在Swift的主要目标之一——移动设备上。 - Alexander
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Daniel Hall
显示剩余3条评论

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