Swift中的过滤器与分区

Swift中的过滤器与分区,swift,Swift,我试图在给定数组中找到非零元素,并将零移到后面。应用过滤器的方法,它是有效的。应用分区方法给了我数组是不可变的问题 我想知道在时间复杂度和空间复杂度方面使用过滤器和分区的优点。通常最好使用哪一种 var inputArray = [1,4,0,0,5,1,0] 过滤方法 func NonZeroArrayWithFilter(array:[Int]) -> [Int] { return array.filter({$0 > 0}) + array.filter({$0 ==

我试图在给定数组中找到非零元素,并将零移到后面。应用过滤器的方法,它是有效的。应用分区方法给了我数组是不可变的问题

我想知道在时间复杂度和空间复杂度方面使用过滤器和分区的优点。通常最好使用哪一种

var inputArray = [1,4,0,0,5,1,0] 
过滤方法

func NonZeroArrayWithFilter(array:[Int]) -> [Int] {
  return array.filter({$0 > 0}) + array.filter({$0 == 0})
}
func NonZeroArrayWithPartition(array:[Int]) -> [Int] {
  return array.partition(by: { $0 > 0 }) + array.partition(by: { $0 == 0 })
}
分区方法

func NonZeroArrayWithFilter(array:[Int]) -> [Int] {
  return array.filter({$0 > 0}) + array.filter({$0 == 0})
}
func NonZeroArrayWithPartition(array:[Int]) -> [Int] {
  return array.partition(by: { $0 > 0 }) + array.partition(by: { $0 == 0 })
}

filter
生成一个新数组,并保持原始数组的完整性<代码>分区(按:)在适当的阵列上工作。要么需要使数组可变,要么需要制作可变副本。例如:

extension MutableCollection where Self.Element: ExpressibleByIntegerLiteral & Equatable {
    mutating func moveZerosToEnd() -> Self {
        var copy = self
        _ = copy.partition(by: { $0 == 0 })
        return copy
    }
}

var inputArray = [1,4,0,0,5,1,0] 

print(inputArray.moveZerosToEnd())

filter
返回与您的条件匹配的元素数组<代码>分区对数组重新排序,使所有符合条件的元素位于不符合条件的元素之前。例如:

var array = [1, 5, 2, 6]
array.filter { $0 < 4 } // returns [1, 2]

// reorders `array` to [1, 2, 5, 6], and returns 2:
// all elements before array[2] are smaller than 4
array.partition { $0 < 4 }
var数组=[1,5,2,6]
array.filter{$0<4}//返回[1,2]
//将'array'重新排序为[1,2,5,6],并返回2:
//数组[2]之前的所有元素都小于4
array.partition{$0<4}

换句话说,
分区
不会返回新数组。

在我看来,最好的方法是创建自己的排序描述符。这样就不需要两次“遍历”数组

let array = [1,2,0,6,0,2,5]
func nonZeroSortDescriptor(lhs: Int, rhs: Int -> Bool {
    return rhs == 0
}
array.sorted(by: nonZeroSortDescriptor)
//1,2,6,2,5,0,0
UPD

另外,@Alexander是对的,这种排序的复杂性将是
O(2N+1)
,因此这里优化了排序方法:

func NonZeroArrayWithFilterFast(array:[Int]) -> [Int] {
    var zerosCount = 0
    return array.filter({
        if $0 == 0 {
            zerosCount += 1
        }
        return $0 > 0
    }) + [Int](repeating: 0, count: zerosCount)
}
NonZeroArrayWithFilterFast(array: array)

该算法的复杂度为O(N)

以下是一个简短的解决方案:

array.sorted { $1 == 0 * $0 }
它是O(n logn)。它很短,但不如过滤器方法的O(2n)快

更有效的解决办法是:

func zeroTail(_ array:[Int]) -> [Int] {
    guard !array.isEmpty else { return array }

    var tempo = Array(repeating: 0, count: array.count)
    var index = 0
    array.forEach { element in
        if element != 0 {
            tempo[index] = element
            index += 1
        }
    }
    return tempo
}

zeroTail([0,1,2,0,6,0,2,5,0])  //[1, 2, 6, 2, 5, 0, 0, 0, 0]
它只遍历数组一次:O(n)

partition
是有效的,但它不适合在这里使用,因为它可以将这些元素与其他元素交换。这与保持非零元素的原始顺序的要求相矛盾


通常,您还可以在数组上定义扩展:

extension Array where Element: Comparable {
    func withTail(_ value: Element) -> [Element] {
        guard !self.isEmpty else { return self }

        var tempo: [Element] = Array(repeating: value, count: self.count)
        var index: Int = 0
        self.forEach { element in
            if element != value {
                tempo[index] = element
                index += 1
            }
        }
        return tempo
    }
}
下面是一些用例:

[0, 1, 2, 0, 6, 0, 2, 5, 0].withTail(0)            //[1, 2, 6, 2, 5, 0, 0, 0, 0]
["c", "a", "c", "b", "c", "d", "c"].withTail("c")  //["a", "b", "d", "c", "c", "c", "c"]
[1.2, 2.3, 4.5, 1.2].withTail(6.0)                 //[1.2, 2.3, 4.5, 1.2] 
[Character]().withTail("a")                        //[]

请注意,
partition
只返回索引,而不像
filter
那样返回数组。
partition
正在根据条件重新索引数组,它将在满足元素之前排列所有不满足元素。
因此,
partition
将返回满足元素的第一个索引。 使用
分区
的mutating member时出现的错误将如下解决

func NonZeroArrayWithPartition(array: [Int]) -> [Int] {
    var array = array
//    return array.partition(by: { $0 > 0 }) + array.partition(by: { $0 == 0 }) // your statement
    _ = array.partition(by: {$0 == 0})
    return array
}
您的语句将被注释,因为它将传递
Array.Index
(即数组的索引),而如果
int
,则dunction应返回Array。使用此方法时,您可以这样声明,否则会收到警告。
让inputArray=[1,4,0,0,5,1,0]

对于同样的事情,这里有另一种方法

var inputArray = [1,4,0,0,5,1,0]
...
NonZeroArrayWithPartition(array: &inputArray)
...
func NonZeroArrayWithPartition(array:inout [Int]) -> [Int] {
    _ = array.partition(by: {$0 == 0})
    return array
}
它将更改源数组(即通过引用调用)
下面是一些语句,您可以更好地理解
分区的行为

var inputArray = [1,4,0,0,5,1,0]
print(inputArray)
print(NonZeroArrayWithFilter(array: inputArray))
print(inputArray)
print(NonZeroArrayWithPartition(array: &inputArray))
print(inputArray)
输出如下所示

[1,4,0,0,5,1,0]
[1, 4, 5, 1, 0, 0, 0]
[1, 4, 0, 0, 5, 1, 0]
[1, 4, 1, 5, 0, 0, 0]
[1,4,1,5,0,0,0]

下面是分区的

就我个人而言,我建议使用
partition
函数来实现这种方法。
因为filter函数将创建2个数组,您需要将它们连接起来,而分区将只是重新为数组编制索引。

您的示例将破坏输入数组:
[0,1,2,0,6,0,0,2,5,0]
->
[5,1,2,2,6,0,0,0]
@PavloBoiko不需要。@PavloBoiko OP没有声明非零元素之间应该保持顺序,事实上,斯威夫特的分类不能保证是稳定的,所以这不一定行得通。另外,它的性能比仅使用分区更差。
nonZeroSortDescriptor
不应该抛出。分区方法中的更新看起来非常熟悉。数组是不可变的,返回值不是数组,它是重新排序的集合中满足条件的第一个元素的索引。不清楚预期的输出是什么。您没有声明应该保留非零之间的顺序,事实上,您自己的第二种方法并没有保留它。这是不是一个要求?我从来没有机会对任何事情使用
partition
。它通常用于什么?@matt它通过对数组元素重新排序来变异数组,那些符合条件的元素被放置在结果数组的末尾。它返回一个整数,它是结果数组中与closure
belongsinssecondpartition
中传递的条件匹配的第一个元素的索引。我知道它的作用。我可以阅读文档。我在问你为什么在现实生活中会有这样的机会。@matt可能的用例可能是:1)速度:根据条件快速分割(虚拟)阵列,而不必使用过滤器进行两次O(n)。分区的返回值将是分隔符。2) 内存效率:过滤而不创建新数组。@matt我最近使用了
分区
。分区在这里不起作用。请尝试使用
[0,1,2,0,6,0,2,5,0]
是否使用我的条件?我已经更新了分区方法的条件。我不使用你的条件。