如何根据预定义的元素顺序对数组进行排序?

时间:2016-10-13 07:46:16

标签: arrays swift sorting

我想基于定义元素顺序的自定义数组对数组的元素进行排序。例如,假设以下数组:

let arr = ["second", "first", "second", "fourth", "third", "second"]

我尝试创建一个Array扩展,以便能够通过以下方式对此数组进行排序:

let sortedArr = arr.sortBy(orderArray: ["first","second","third","fourth"])
// desired output: ["first", "second", "second", "second", "third", "fourth": 

但是,扩展程序无法正常运行:

extension Array {
    public func sortBy<T: Comparable>(orderArray: [T]) -> [T]? {
        guard self.count > 0,
            self.first! as? T != nil else {
                return nil
        }

        let indices = self.map {orderArray.index(of: $0 as! T)! }

        return self.sorted { indices[$0] > indices[$1] } // This doesn’t work
    }
}

有什么想法吗?

2 个答案:

答案 0 :(得分:3)

您的代码存在的一个问题是self.sorted期望a 闭包比较数组元素,而不是索引。

这是一种可能的解决方案,也避免了不必要的 类型转换和解包(解释内联):

extension Array where Element: Equatable {
    public func sortBy(orderArray: [Element]) -> [Element]? {

        // Index of each element in `orderArray`:
        let targetIndices = self.flatMap { orderArray.index(of: $0) }
        // Verify that each array element occurs in `orderArray`:
        guard targetIndices.count == self.count else {
            return nil
        }
        // Sort array indices according to their index in `orderArray`:
        let sortedIndices = self.indices.sorted { targetIndices[$0] < targetIndices[$1] }
        // Rearrange elements accordingly:
        return sortedIndices.map { self[$0] }
    }
}

示例:

let arr = ["second", "first", "second", "fourth", "third", "second"]

if let sortedArr = arr.sortBy(orderArray: ["first","second","third","fourth"]) {
    print(sortedArr)
    // ["first", "second", "second", "second", "third", "fourth"]
}

移动orderArray中未包含的数组元素 在排序结果的末尾(但保留它们的相对顺序),稍微修改代码

extension Array where Element: Equatable {
    public func sortBy(orderArray: [Element]) -> [Element] {

        // Index of each element in `orderArray`:
        let targetIndices = self.enumerated().map {
            orderArray.index(of: $1) ?? orderArray.count + $0
        }
        // Sort array indices according to their index in `orderArray`:
        let sortedIndices = self.indices.sorted { targetIndices[$0] < targetIndices[$1] }
        // Rearrange elements accordingly:
        return sortedIndices.map { self[$0] }
    }
}

示例:

let arr = ["x", "second", "first", "y", "second", "fourth", "third", "second", "z"]
let sortedArr = arr.sortBy(orderArray: ["first","second","third","fourth"])
print(sortedArr)
// ["first", "second", "second", "second", "third", "fourth", "x", "y", "z"]

答案 1 :(得分:2)

另一种选择:从orderArray发出以构造一个排序数组

另一种选择(&#34;另一种方式&#34;与@MartinR:s answer相比)是从orderArray发出并简单地构建&#34;排序&#34;数组基于要排序的数组中orderArray中元素的出现次数。

子替代#1:允许orderArray不全面w.r.t.要排序的元素

在下面这个替代方案的示例实现中,要排序的数组中orderArray中没有的元素与原始数组中的元素保持相同的相对顺序,但是在部分的末尾的数组的排序(即,在orderArray中包含 的任何元素之后)。如果nil未完全覆盖要排序的数组中的所有元素,则另一个替代方法可能是orderArray返回。

extension Array where Element: Equatable {
    /* help method */
    private func countInstances(of element: Element) -> Int {
        return reduce(0) { $0 + ($1 == element ? 1 : 0) }
    }

    /* sort by ordered array method */
    func sortBy(orderArray: [Element]) -> [Element] {
        // construct sorted array based on elements in orderArray
        return orderArray
            .reduce([]) { $0 + Array(repeating: $1, count: countInstances(of: $1)) } + 
            filter { !orderArray.contains($0) }
    }
}

使用示例:

let arr1 = ["second", "first", "unsortable", "second", 
           "anotherUnsortable", "fourth", "third", "second"]
let arr2 = ["foo", "first", "bar"]
let orderArray = ["first", "second", "third", "fourth"]

print(arr1.sortBy(orderArray: orderArray)) 
    // ["first", "second", "second", "second", "third", "fourth", "unsortable", "anotherUnsortable"]

print(arr2.sortBy(orderArray: orderArray)) 
    // ["first", "foo", "bar"]

次要选择#2:对于非全面的orderArray,请返回nil

如果nil不全面,请回复orderArray w.r.t.要排序的数组成员,您可以相应地修改上面的sortBy方法:

extension Array where Element: Equatable {
    private func countInstances(of element: Element) -> Int {
        return reduce(0) { $0 + ($1 == element ? 1 : 0) }
    }

    func sortBy(orderArray: [Element]) -> [Element]? {
        guard reduce(true, { $0 && orderArray.contains($1) }) else { return nil }
        return orderArray
            .reduce([]) { $0 + Array(repeating: $1, count: countInstances(of: $1)) }
    }
}

使用示例:

let arr1 = ["second", "first", "second", 
            "fourth", "third", "second"]
let arr2 = ["foo", "baz", "bar"]
let orderArray = ["first", "second", "third", "fourth"]

print(arr1.sortBy(orderArray: orderArray)) 
    // Optional(["first", "second", "second", "second", "third", "fourth"])

print(arr2.sortBy(orderArray: orderArray)) 
    // nil