如何获取CGPoint数组的每个值重复的次数
Posted
技术标签:
【中文标题】如何获取CGPoint数组的每个值重复的次数【英文标题】:how to get the number of times each value of a CGPoint array is repeated 【发布时间】:2017-06-11 13:56:37 【问题描述】:我有一个CGPoint
s 数组,
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)]
如何获取每个点重复的次数?
【问题讨论】:
看看NSCountedSet
@LeoDabus 所以让它成为 Hashable :)
@LeoDabus 当然是。 Martin 已经为您完成了 :)
@LeoDabus 看来你现在需要它了。将 CGPoints 转换为字符串以用作字典中的键实际上是一种非常糟糕的散列算法。只需将其换成设计更好的算法,就像马丁发布的那样,并使用与您目前相同的算法
@LeoDabus 在大多数情况下什么可以正常工作?
【参考方案1】:
正如@Alexander 在 cmets 中所说,您应该使用 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
。
NSCountedSet
有两个初始化器:public convenience init(array: [Any])
和 public convenience init(set: Set<AnyHashable>)
你只需要 Hashable
将 Set
传递给 init
(我使用 Swift 3.0.2 和 Xcode 8.2。 1)【参考方案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 Hashable 返回其对应的 NSValue hashValue 请注意,您可以只从 CGPoint 转换为 NSValue 而不是创建新对象 @LeoDabus 是的,我认为这在 Swift 3.0.1 中是全新的,但我仍然不习惯。【参考方案3】:我同意 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:)
作为字典键允许它与示例代码中的元组以及其他不一定是 CGPoint
或 Hashable
实例的变体一起使用。这将为您提供字典的Hashable
键,而无需编写自定义代码; 然而,正如 Alexander 在下面的 cmets 中指出的那样,生成 Hashable
值并不是最高效的方式,如果您在数万个或更多点上进行操作,或者在循环,使用 String(describing:)
与自定义函数从点元组生成哈希之间的区别可能很大
【讨论】:
使用String(describing:)
作为散列技术是不必要的昂贵且容易发生冲突
@Alexander 一般来说,是的。在这种情况下,使用他的(CGFloat, CGFloat)
值数组,它不应该发生冲突,并且您不能创建元组Hashable
。在CGPoint
上使用Hashable
扩展名,或在NSValue
中包装CGPoint
s 是可以在其他情况下替代的实现,但此答案提供:1)算法的功能样式2)有效的解决方案使用 OP 的示例输入,以前的答案都没有涵盖这些输入
当然会造成碰撞。你用 1 个 Int
hashValue
代表 CGPoint
的 2 个 Int
s。不存在任何可以映射这些 1:1 的函数。您的算法很好,但是使用为CGPoint
定义的适当哈希函数而不是使用String(describing:)
会更好
@Alexander CGPoint
不包含 Int
s 并且 OP 示例代码根本没有使用 CGPoint
;它使用 Float
s / Double
s 的元组。无法在元组上创建Hashable
扩展,String(describing:)
输出一个String
,它对于元组中Float
s 的每个唯一组合都是唯一的,并且不会与String
的哈希冲突其他组合的值。如果您查看我的示例代码中的打印输出,您将看到 String
键。除非String
有一个糟糕的哈希实现(它没有),否则这些都不会相互冲突
每个元组都需要在堆上动态分配以包含字符串的缓冲区。对于任何非平凡数量的元组,自己实现一个简单的散列函数都会带来令人信服的性能优势。尤其是在移动设备上,这是 Swift 的主要目标之一。以上是关于如何获取CGPoint数组的每个值重复的次数的主要内容,如果未能解决你的问题,请参考以下文章