Swift中的过滤器与分区

时间:2018-10-18 21:58:02

标签: swift

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

我想知道使用过滤器和分区在时间复杂度以及空间复杂度方面的优势。通常最好使用哪个?

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 })
}

5 个答案:

答案 0 :(得分:1)

filter返回符合您条件的元素数组; partition对数组进行重新排序,以使所有符合条件的元素位于不符合条件的元素之前。例如:

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 }

换句话说,partition不返回新数组。

答案 1 :(得分:1)

我认为,最好的方法是创建自己的排序描述符。然后,您无需两次遍历数组。

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)

答案 2 :(得分:1)

这是一个简短的解决方案:

array.sorted { $1 == 0 * $0 }

它是 O(n log n)。它很短,但不及您的过滤方法 O(2 n)

更有效的解决方案是:

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是有效的,但是在这里不合适,因为它会交换belong in the second partition的元素和不交换的元素。这就与保持非零元素的原始顺序的要求相矛盾。


您通常还可以在Array上定义扩展名:

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")                        //[]

答案 3 :(得分:1)

请注意,partition仅返回索引,而不是像filter这样的数组。 partition根据条件重新索引数组,它将在满足条件的元素之前排列所有不满足条件的元素。
因此,partition将返回满足条件的元素的第一个索引。 您为partition使用变异成员而遇到的错误将通过以下方式解决。

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,则该语句应返回数组。使用此方法时,您可以这样声明,否则会得到警告。
let 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
}

它将更改源数组(即通过引用调用)
以下是partition的行为供您更好地理解。

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的{​​{3}}。
就个人而言,我建议对这种方法使用partition函数。 因为filter函数将创建2个数组,您需要将它们连接起来,而分区只会重新索引该数组。

答案 4 :(得分:0)

filter创建一个新数组,并保留原始数组。 partition(by:)适用于就地数组。您需要使数组可变,或者需要使副本可变。例如:

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())