如何在Swift中对数组进行数字和字母排序

4

假设有一个数组:

var array = ["5C", "4D", "2H", "13S", "4C", "5H"]

如何将这个数组排序,使得新的数组按照最后一个字符的字母顺序排列,之前的数字按照数值大小排序,例如:

["4C", "5C", "4D", "2H", "5H", "13S"]

我对编程语法只有很基本的了解。我已经搜索过如何使用 .sorted 函数和 .ascendingOrder 来进行数字排序,但是我找不到同时可以按照字母和数字排序的解决方案。

4个回答

3

你需要编写自己的比较器,这在Swift中非常方便。

如果最后一个字符相同,则按数字顺序对字符串进行排序,否则按最后一个字符排序。

let array = ["5C", "4D", "2H", "13S", "4C", "5H"]

let sortedArray = array.sorted { (str1, str2) -> Bool in
    if str1.suffix(1) == str2.suffix(1) {
        return str1.dropLast().localizedStandardCompare(str2.dropLast()) == .orderedAscending
    } else {
        return str1.suffix(1) < str2.suffix(1)
    }
}

// ["4C", "5C", "4D", "2H", "5H", "13S"]

你说得对,我没有仔细看 OP 所需的结果。您的答案对于 OP 的用例是正确的(至少只要 alpha 后缀总是一个字母,并且需要按 Unicode 顺序进行比较。如果最后一个字符始终为 alpha,且应该进行不区分大小写的比较,则您的代码将需要稍微调整一下,但您的答案将返回小数据集的所需结果)。 - Duncan C
这个可行!看到所有的评论,我非常感激所有的答案。我意识到还有很多东西需要学习。谢谢! - Ehu9

3

编辑:

我的回答展示了如何使用 sorted() 对字符串数组进行“数字”排序。但这并不完全符合提问者的要求。

对于提问者:你应该接受 vadian 的答案,他的是第一个正确的答案。

然而,在我的答案中我花了一些时间解释了 Swift 闭包语法,因此我还是会保留这个答案。


你可以使用数组方法 sorted(),它需要一个闭包作为参数,比较对象对并返回 true 如果第一个元素应该排在前面。

然后,你可以使用 NSString 方法 compare(options:) 进行“数字”字符串比较,其中数字序列在字符串内被视为数字。

下面是一个可以工作的代码片段,将对你的数组进行排序:

var array = ["5C", "4D", "2H", "13S", "4C", "5H"]

let sorted = array.sorted (by: { (first: String, second: String) -> Bool in
    return first.compare(second, options: .numeric) == .orderedAscending
})

sorted()函数是一个高阶函数,也就是一个接收另一个函数作为参数的函数。对于一组字符串来说,该函数需要接收两个字符串并返回一个布尔值。它实际上接收的是一个闭包而不是一个函数,其中闭包是一个“匿名函数”(没有名称的函数)。

根据vadian的代码修改我的片段,代码如下:

var array = ["5C", "4D", "2H", "13S", "4C", "5H"]

let sorted = array.sorted (by: { (first: String, second: String) -> Bool in
    if first.suffix(1) == second.suffix(1) {
        return first.dropLast.compare(second, options: .numeric) == .orderedAscending

    } else {
        return first.suffix(1) < second.suffix(1)
    }
})

您可以使用几个快捷方式重写上面的代码:

使用 "尾随闭包",您可以跳过包含闭包作为参数的(),只需在函数名称后用花括号提供闭包即可。

您可以省略闭包的参数和返回类型声明,并跳过返回语句:

let sorted = array.sorted { $0.compare($1, options: .numeric) == .orderedAscending }

对于像vadian的那样能够给出正确答案的更复杂的代码,我建议不要使用这种位置参数。使用像firstsecond这样的本地变量可以使代码更易于阅读。

我建议仔细研究苹果的Swift iBooks中关于闭包的章节,直到您理解闭包可以表达的各种方式及其不同的快捷语法为止。起初很困惑,而使用闭包是使用Swift的基础。


只需添加一个速记版本。清洁的代码是美丽的代码。let sorted = array.sorted { $0.compare($1, options: .numeric) == .orderedAscending } 我认为这是正确的... 暂时没有代码。 - xTwisteDx
2
结果是 ["2H", "4C", "4D", "5C", "5H", "13S"],但这不是 OP 寻找的。 - vadian
这是一个很好的答案,如果这些字符串没有更高层次的含义,那么这是最好的答案。然而,考虑到它们的结构和特殊处理,我怀疑它们确实有意义。在这种情况下,最好解析出值并给它们取好名字。 - Alexander
结果是["2H", "4C", "4D", "5C", "5H", "13S"]。 - Eduardo Oliveros
感谢您的回答。虽然结果不是我在寻找的,但这填补了我很多疑问的空白。我会查看Swift iBooks。谢谢Duncan! - Ehu9
显示剩余2条评论

0
欢迎来到StackOverflow!
这些数字代表什么?我会创建一个struct来模拟这个“东西”(暂且称其为Thing),并编写一个函数,可以将String解析成Thing,如下所示:
struct Thing: Equatable { // FIXME: Name me something descriptive
    let number: Int // FIXME: Name me something descriptive
    let letter: Character // FIXME: Name me something descriptive

    static func parse(from string: String) -> Thing? {
        let numberSegment = string.prefix(while: { $0.isNumber })
        guard !numberSegment.isEmpty,
            let number = Int(numberSegment) else { return nil }

        let letterSegement = string.drop(while: { $0.isNumber })
        guard letterSegement.count == 1,
            let letter = letterSegement.first else { return nil }

        return Thing(number: number, letter: letter)
    }
}

然后,您可以遵守Comparable,通过定义比较运算符<来定义您想要排序的方式:

extension Thing: Comparable {
    static func < (lhs: Thing, rhs: Thing) -> Bool {
        return (lhs.letter, lhs.number) < (rhs.letter, rhs.number)
    }
}

从那里开始,只需要将所有字符串解析为Thing,并对它们进行排序:

let array = ["5C", "4D", "2H", "13S", "4C", "5H"]

let things = array.map { Thing.parse(from: $0)! }
print("Before sorting:")
things.forEach { print("\t\($0)") }

let sortedThings = things.sorted()
print("\nAfter sorting:")
sortedThings.forEach { print("\t\($0)") }

输出:

Before sorting:
    Thing(number: 5, letter: "C")
    Thing(number: 4, letter: "D")
    Thing(number: 2, letter: "H")
    Thing(number: 13, letter: "S")
    Thing(number: 4, letter: "C")
    Thing(number: 5, letter: "H")

After sorting:
    Thing(number: 4, letter: "C")
    Thing(number: 5, letter: "C")
    Thing(number: 4, letter: "D")
    Thing(number: 2, letter: "H")
    Thing(number: 5, letter: "H")
    Thing(number: 13, letter: "S")

这也不是预期的结果。后缀应该先排序,然后是前缀。 - vadian
@vadian 一行代码修改,已修复 :) - Alexander
哇,非常感谢您详细的回复。这里的数字代表一副牌,其中字母代表花色,数字代表等级(11、12、13分别是J、Q、K)。我在尝试构建一个纸牌游戏并对手牌进行排序。2-3个字符代码代表特定卡牌的图像名称,因此我可以将其建模到UI中。我对结构体的功能有些困惑,所以我将进一步阅读它,也许我的代码会更简洁实用。谢谢您,这给了我一些关于如何建模我的牌堆的思路。 - Ehu9
@Ehu9 哦,我现在明白了。结构体是一种将相关数据和操作打包在一起的方式。这是在Swift中创建自己的数据类型的一种方式。这样,您可以说一个函数需要一个Card(而不仅仅是一个通用的String),因此Swift编译器可以强制执行您不能提供像“Hello World!”这样的无意义字符串。是的,您应该创建一个struct Card,其中包含rank: Ranksuit: Suite,其中RankSuite都是枚举类型,列举了13个等级(Ace,2-10,J,Q,K)和4个套房。请参见https://dev59.com/KGAf5IYBdhLWcg3w9Wmu - Alexander

0
欢迎来到StackOverflow!
这是我的解决方案,希望它能为您工作。我只是先整理数字,然后与字母进行比较以创建一个新数组:
var array = ["5C", "4D", "2H", "13S", "4C", "5H"]
    array = array.sorted { $0.numbersValues < $1.numbersValues }
    let str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    var newArrray: [String] = [] 
    for letter in str {
            for value in array {
                if value.lettersValues.hasPrefix(String(letter)) {
                       newArrray.append(value)
                }
            }
    }

不要忘记在你的项目中包含这些辅助方法

    extension String {

        var lettersValues: String {
            return self.components(separatedBy: CharacterSet.decimalDigits).joined()
        }

        var numbersValues: String {
            return self.filter { "0"..."9" ~= $0 }
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接