Swift中数组值的函数聚类
给定一个数组,如:Swift中数组值的函数聚类,swift,functional-programming,sequences,Swift,Functional Programming,Sequences,给定一个数组,如: [0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0] 我希望根据值的顺序差异(例如,abs(x-y)([Generator.Element],[Generator.Element]){ 设split=indexOf{!pred($0)}??endIndex 返回(数组(self[startIndex..[[Generator.Element]]{ guard self.count>0其他{return[]} 让(这个,那个)
[0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0]
我希望根据值的顺序差异(例如,abs(x-y)
,以及n=0.2
)将值聚集到子数组中,例如:
我想以声明的方式来做这件事——只是为了更好地了解更复杂的序列操作在函数上下文中是如何工作的(似乎大多数“functional Swift”演示/教程都非常基础)
提前谢谢
更新: 这是一条有点接近的单行线:
let times = [0, 0.5, 0.99, 1, 1.01, 1.5, 2, 2.5, 2.51, 3, 3.49, 3.5]
let result = times.map { t1 in
return times.filter { fabs($0 - t1) < 0.2 }
}
// [[0.0], [0.5], [0.99, 1.0, 1.01], [0.99, 1.0, 1.01], [0.99, 1.0, 1.01], [1.5], [2.0], [2.5, 2.51], [2.5, 2.51], [3.0], [3.49, 3.5], [3.49, 3.5]]
let times=[0,0.5,0.99,1,1.01,1.5,2,2.5,2.51,3,3.49,3.5]
让result=times.map{t1 in
返回时间.filter{fabs($0-t1)<0.2}
}
// [[0.0], [0.5], [0.99, 1.0, 1.01], [0.99, 1.0, 1.01], [0.99, 1.0, 1.01], [1.5], [2.0], [2.5, 2.51], [2.5, 2.51], [3.0], [3.49, 3.5], [3.49, 3.5]]
只需要消除重复项。我不知道有任何本机Swift方法可以满足您的需要。您可以通过一个简单的扩展来实现这一点:
extension Array {
func split(condition : (Element, Element) -> Bool) -> [[Element]] {
var returnArray = [[Element]]()
var currentSubArray = [Element]()
for (index, element) in self.enumerate() {
currentSubArray.append(element)
if index == self.count - 1 || condition(element, self[index+1]) {
returnArray.append(currentSubArray)
currentSubArray = []
}
}
return returnArray
}
}
用法示例:
let source = [0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0]
let n = 0.2
let target = source.split { abs($0 - $1) > n }
输出:
[[0.0], [0.5, 0.51], [1.0], [1.5], [1.99, 2.0, 2.1], [2.5], [3.0]]
[[0], [0.5, 0.51], [1], [1.5], [1.99, 2, 2.1], [2.5], [3]]
这与减少有关:
extension Array {
func split(condition : (Element, Element) -> Bool) -> [[Element]] {
return self.reduce([], combine:
{ (list : [[Element]], value : Element) in
if list.isEmpty {
return [[value]]
}
else if !condition(list.last!.last!, value) {
return list[0..<list.count - 1] + [list.last!+[value]]
}
else {
return list + [[value]]
}
}
)
}
}
let source = [0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0]
let n = 0.2
let target = source.split { abs($0 - $1) > n }
更新
如果您不介意在reduce中对阵列进行变异,您可以得到一个更短、更高效的解决方案:
extension Array {
func split(condition : (Element, Element) -> Bool) -> [[Element]] {
return self.reduce([], combine:
{ ( var list : [[Element]], value : Element) in
if list.isEmpty || condition(list.last!.last!, value) {
list += [[value]]
}
else {
list[list.count - 1] += [value]
}
return list
}
)
}
}
我绝对会像亚伦·布拉格那样做。国际海事组织认为,这是最好的快速方法。Swift实际上并不能很好地处理函数式编程。但为了探索你可能会如何,这里有一种方法我可能会攻击它 我完全希望这方面的表现会很糟糕。可能是O(n^2)。递归地构建数组,就像我在
groupWhile
中所做的那样,强制它在每一步复制整个数组
// Really Swift? We have to say that a subsequence has the same elements as its sequence?
extension CollectionType where SubSequence.Generator.Element == Generator.Element {
// Return the prefix for which pred is true, and the rest of the elements.
func span(pred: Generator.Element -> Bool) -> ([Generator.Element], [Generator.Element]) {
let split = indexOf{ !pred($0) } ?? endIndex
return (Array(self[startIndex..<split]), Array(self[split..<endIndex]))
}
// Start a new group each time pred is false.
func groupWhile(pred: Generator.Element -> Bool) -> [[Generator.Element]] {
guard self.count > 0 else { return [] }
let (this, that) = span(pred)
let next = that.first.map{[$0]} ?? []
let rest = Array(that.dropFirst())
return [this + next] + rest.groupWhile(pred)
}
}
extension CollectionType where
Generator.Element : FloatingPointType,
Generator.Element : AbsoluteValuable,
SubSequence.Generator.Element == Generator.Element {
//
// Here's the meat of it
//
func groupBySeqDistanceGreaterThan(n: Generator.Element) -> [[Generator.Element]] {
// I don't love this, but it is a simple way to deal with the last element.
let xs = self + [Generator.Element.infinity]
// Zip us with our own tail. This creates a bunch of pairs we can evaluate.
let pairs = Array(zip(xs, xs.dropFirst()))
// Insert breaks where the difference is too high in the pair
let groups = pairs.groupWhile { abs($0.1 - $0.0) < n }
// Collapse the pairs down to values
return groups.map { $0.map { $0.0 } }
}
}
//真的很快吗?我们不得不说,子序列与其序列具有相同的元素?
扩展集合类型,其中SubSequence.Generator.Element==Generator.Element{
//返回pred为true的前缀和其余元素。
func span(pred:Generator.Element->Bool)->([Generator.Element],[Generator.Element]){
设split=indexOf{!pred($0)}??endIndex
返回(数组(self[startIndex..[[Generator.Element]]{
guard self.count>0其他{return[]}
让(这个,那个)=跨度(pred)
让next=that.first.map{[$0]}???[]
让rest=Array(that.dropFirst())
返回[this+next]+rest.groupWhile(pred)
}
}
扩展集合类型,其中
Generator.Element:FloatingPointType,
生成器。元素:绝对有价值,
SubSequence.Generator.Element==Generator.Element{
//
//这是它的肉
//
func GroupBySeqDistanceGreater大于(n:Generator.Element)->[[Generator.Element]]{
//我不喜欢这个,但这是处理最后一个元素的简单方法。
设xs=self+[Generator.Element.infinity]
//用我们自己的尾巴给我们拉拉链。这就产生了一堆我们可以评估的配对。
让pairs=Array(zip(xs,xs.dropFirst())
//在对中差异太大的地方插入打断
设groups=pairs.groupWhile{abs($0.1-$0.0)
一个带有累加参数的简单折叠可以工作。顺便说一句,我不确定这是否正是您想要的,因为我不知道数组中的元素是否需要是后续的。在描述中您这样说,但是您的“示例答案”没有考虑它们是否是后续的。您应该改进问题描述
let a : [Double] = [0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0];
let diff : Double = 0.2;
let eps = 0.0000001
let b = a.sort().reduce(([],[])) { (ps : ([Double],[[Double]]), c : Double) -> ([Double],[[Double]]) in
if ps.0.count == 0 || abs(ps.0.first! - c) - diff <= eps { return (ps.0 + [c], ps.1) } else { return ([c], ps.1 + [ps.0]) }
}
let result = b.1 + [b.0];
print(result)
斯威夫特在函数式编程方面并不差 这里有一种方法可以避免if/else语句并保持分组条件隔离: (比IMHO接受的答案更清晰、更直接)
let value:[Double]=[0,0.5,0.51,1.0,1.5,1.99,2.0,2.1,2.5,3.0]
设inGroup={(x:Double,y:Double)返回abs(x-y)<0.2}
让间隔=zip(值,值.enumerated().dropFirst())
let start=interval.filter{!inGroup($0,$1.1)}.map{$0.1.0}
让ranges=zip([0]+开始,开始+[values.count])
让result=ranges.map{values[$0..是的,这很好。谢谢!我试着坚持(盲目地)为了避免循环的功能座右铭,但我真的不知道我该如何解决这个问题…我会看看是否有其他人响应,只是出于好奇。我做了很多象征性的音乐处理,所以我一直在做这种事情,非常有必要。只是探索,真的。再次感谢你提出的有趣的问题。我试着缝合一个使用map/filter/reduce/split的解决方案,但不能简单地实现。是的,也可以。请注意,要遵循更复杂的方法。我很想看看其他人想出了什么。它确实满足了您问题的要求。在Clojure或其他更复杂的语言中提出相同的解决方案也会很有趣ongly函数式语言。有趣的是,我的和Julian的性能大致相同,而Aaron的大约快一倍。当然,我的错了(!!无论如何,在这一点上…)所以这对我来说就足够了!外部和内部数组不断地被重新创建,以使其纯功能化。我认为您可以在仍然使用reduce的情况下欺骗和不这样做,并且性能会得到提高。任何时候使用reduce生成数组,您都可能得到O(n^2)在Swift中,因为每次调用.append()时,它都必须不断复制数组
。我确信我的解决方案也有同样糟糕的性能。Swift不是一种函数式语言,并且缺乏高效完成这项工作所需的列表构建原语。哈!有趣。是的,这相当棘手。不过,到目前为止,我认为你对Aaron的解决方案肯定是正确的。它是迄今为止最干净、最快的解决方案,而且具有相当大的优势顺便说一句,@Rob,你知道有没有办法过滤我上面版本中的重复内容?看起来可能需要另一个过滤器
let a : [Double] = [0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0];
let diff : Double = 0.2;
let eps = 0.0000001
let b = a.sort().reduce(([],[])) { (ps : ([Double],[[Double]]), c : Double) -> ([Double],[[Double]]) in
if ps.0.count == 0 || abs(ps.0.first! - c) - diff <= eps { return (ps.0 + [c], ps.1) } else { return ([c], ps.1 + [ps.0]) }
}
let result = b.1 + [b.0];
print(result)
[[0.0], [0.5, 0.51], [1.0], [1.5], [1.99, 2.0, 2.1], [2.5], [3.0]]
let values:[Double] = [0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0]
let inGroup = { (x:Double,y:Double) return abs(x-y) < 0.2 }
let intervals = zip(values,values.enumerated().dropFirst())
let starts = intervals.filter{ !inGroup($0,$1.1) }.map{$0.1.0}
let ranges = zip([0]+starts, starts+[values.count])
let result = ranges.map{values[$0..<$1]}
// result : [ [0.0], [0.5, 0.51], [1.0], [1.5], [1.99, 2.0, 2.1], [2.5], [3.0] ]
// how it works ...
//
// intervals: Contains pairs of consecutive values along with the index of second one
// [value[i],(index,value[i+1])]
//
// starts: Contains the index of second values that don't meet the grouping condition
// (i.e. start of next group)
// [index]
//
// ranges: Contains begining and end indexes for group ranges formed using start..<end
// [(Int,Int)]
//
// result: Groups of consecutive values meeting the inGroup condition
//