【问题标题】:How to join array of optional integers to string?如何将可选整数数组连接到字符串?
【发布时间】:2017-05-20 06:01:48
【问题描述】:

给定 [Int?],需要从它构建字符串。

此代码 sn-p 有效

    let optionalInt1: Int? = 1
    let optionalInt2: Int? = nil

    let unwrappedStrings = [optionalInt1, optionalInt2].flatMap({ $0 }).map({ String($0) })
    let string = unwrappedStrings.joined(separator: ",")

但我不喜欢flatMap 后跟map。有没有更好的解决方案?

【问题讨论】:

    标签: arrays swift functional-programming optional


    【解决方案1】:

    这是另一种方法:

    [optionalInt1, optionalInt2].flatMap { $0 == nil ? nil : String($0!) }
    

    编辑:您可能不应该这样做。这些方法更好,避免!

    [optionalInt1, optionalInt2].flatMap {
        guard let num = $0 else { return nil }
        return String(num)
    }
    

    或:

    [optionalInt1, optionalInt2].flatMap { $0.map(String.init) }
    

    【讨论】:

    • 此解决方案也适用于数组的泛型类型不符合 CustomStringConvertible 或没有 description 属性的情况。
    • @YakivKovalskiy 在这种情况下,如果输入三元运算符的第二个条件目标,则很明显$0.some(...);一种特殊情况,我们确实可以安全地使用!,同时在语义上表明我们知道自己在做什么。
    • @YakivKovalskiy 强制展开不一定是坏事。它被过度使用和滥用了,但在这种情况下它是完全合理的
    • @Alexander 你可能是对的。在我看来,永远不应该在 Swift 代码中使用强制解包,但是这个话题太有争议了,不能在这里讨论。
    • flatMap 已替换为 compactMap
    【解决方案2】:

    您可以在应用于数组的flatMap 闭包中使用map method of Optional,利用前者将返回nil 而无需输入提供的闭包的事实,以防可选本身为nil

    let unwrappedStrings = [optionalInt1, optionalInt2]
        .flatMap { $0.map(String.init) }
    

    此外,如果您不将尾随闭包(flatMapmap)包装在括号中,并进一步利用初始化程序引用 String.init 将(在这种情况下)明确解决的事实到正确的String 初始化程序(如上所用),链式flatMapmap 不需要看起来“臃肿”,并且在这里也是一种完全有效的方法(链式flatMapmap 也成立语义价值)。

    let unwrappedStrings = [optionalInt1, optionalInt2]
        .flatMap{ $0 }.map(String.init) 
    

    【讨论】:

    • 但我在这里尽量避免使用mapflatMap 方法,因为循环遍历数组两次并不好。
    • @YakivKovalskiy 请注意,上面的第一个解决方案使用 可选类型map 操作(这是对可选元素的实例方法的单个调用),而不是数组,所以在那个数组中只会有一个传递。上面的第一种方法几乎等同于@Alexander's solution below。尽管如此,连锁的flatMapmap 仍然是O(n),我不明白这怎么可能“不能很好”,除非你正在与一些非常重型 HPC 应用程序。如果不是,请同样关注语义。
    • @YakivKovalsky 如果你想避免重复循环,请使用.lazy。例如。 array.map(fn1).map(fn2) 只会读取一次元素,直接连续应用每个函数,而不会将中间结果复制/存储到中间数组中。
    【解决方案3】:

    如果您不喜欢 flatMapmap 一起使用,您可以替换它

    [optionalInt1, optionalInt2].flatMap({ $0 }).map({ String($0) })
    

    有了这个

    [optionalInt1, optionalInt2].flatMap { $0?.description }
    

    总结

    let optionalInt1: Int? = 1
    let optionalInt2: Int? = nil
    
    let result = [optionalInt1, optionalInt2]
        .flatMap { $0?.description }
        .joined(separator: ",")
    

    更新

    自:

    • 正如 @Hamish 指出的那样,Swift 团队不鼓励直接访问 description
    • 并且由于双循环,OP 希望避免 flatMap + map 串联

    我提出另一种解决方案

    let result = [optionalInt1, optionalInt2].flatMap {
            guard let num = $0 else { return nil }
            return String(num)
        }.joined(separator: ",")
    

    【讨论】:

    • 不错的收获!我忘了.description 试图让事情与String(describing:)String() 一起工作
    • 请注意,Swift 团队不鼓励您直接访问 description 属性(概览的最后一段 in the docs)——尽管我承认它允许一个漂亮的调用。
    • @Hamish:我不知道。非常感谢您提供的信息。
    【解决方案4】:

    其他答案使用flatMap 来消除nil 元素,并需要调用joined() 来组合元素并添加逗号。

    使用joined() 添加逗号当然是第二遍 大批。这是一种使用reduce 一次性完成此操作的方法:

    let arr: [Int?] = [nil, nil, 1, 2, 3, nil, 4, nil, 5, nil]
    
    let result = arr.reduce("") { $1 == nil ? $0 : $0.isEmpty ? "\($1!)" : $0 + ",\($1!)" }
    
    print(result)
    

    输出:

    1,2,3,4,5
    

    说明

    本例使用reduce逐个元素构造结果字符串。

    reduce 是在序列上调用的方法。它接受一个初始值(本例中为"")和一个闭包,该闭包将序列中的下一个元素与部分结果组合起来以创建下一个部分结果。

    对序列中的每个元素调用闭包(即数组arr)。闭包首先检查元素是否为nil,如果是,则返回未修改的部分结果。如果元素不是nil,则检查部分结果是否仍然是空字符串。如果它为空,则这是结果中的第一个元素,因此它返回一个仅由该元素组成的字符串。如果部分结果不为空,它会将前面带有, 的新元素添加到部分结果中。


    使用reduce(into:):

    将其重写为使用 reduce(into:)append 会导致生成最终逗号分隔字符串时的实现速度是其他答案的两倍。

    let result = arr.reduce(into: "") { (string, elem) in
        guard let elem = elem else { return }
        if string.isEmpty {
            string = String(elem)
        } else {
            string.append(",\(elem)")
        }
    }
    

    【讨论】:

    • (我知道这是一个旧答案)这具有O(n^2) 时间复杂度(每个追加都会导致字符串重复),这不是很好。另外,由于代码比flatMap + joined(separator: " ") 更复杂,我强烈建议不要这样做。见github.com/amomchilov/Blog/blob/master/…
    • @Alexander-ReinstateMonica,在发布中构建了一个 10,000 元素数组,其中 10% nils 用了0.01 秒,我的答案和你最新的joined(separator: ",") 秒。重写以使用 reduce(into:)append 运行速度是原来的两倍。所以你对代码复杂性的抱怨是有道理的,但速度问题是没有根据的。
    • 我这些天的性能问题通常与时间无关(除了阻止网络呼叫,这些仍然是一个很大的禁忌),而更多的是关于电池寿命。我不会花太多时间在它上面,但如果它是微不足道的(例如在一系列调用之前放一个.lazy),我会提前做,只是为了让设备不需要使用它的更高时钟核心等。
    • @Alexander-ReinstateMonica,添加.lazy 使let result = arr.lazy.compactMap { $0.map(String.init) }.joined(separator: ",") 将其从0.01068 秒减至0.01469 秒。
    • @Alexander-ReinstateMonica,是的,这是一个发布版本。
    【解决方案5】:

    Swift3.1

    [1,2,3].flatMap({String(describing: $0)}).joined(分隔符: " ")

    // "1 2 3"(不带引号)

    【讨论】:

    • 这会产生Optional(1)nil 的字符串值,这几乎肯定不是有意的。即使它有意的,也可能值得重新考虑。
    【解决方案6】:

    完全避免 flatMap 和 map。但是创建一个本地变量。

    let optionalInt1: Int? = 1
    let optionalInt2: Int? = nil
    let optionalInts = [optionalInt1, optionalInt2]
    
    var strings = [String]()
    for optInt in optionalInts {
        if let integer = optInt {
            strings.append(String(integer))
        }
    }
    let string = strings.joined(separator: ",")
    

    【讨论】:

    • “完全避免 flatMap 和 map”不要那样做。
    • @Alexander-ReinstateMonica 我可以知道为什么吗?
    • 你的整个for 循环只是说flatMap(现在称为compactMap,对于这个变体)来说真的很长。为什么要重新发明轮子?
    猜你喜欢
    • 1970-01-01
    • 2023-04-02
    • 2013-02-26
    • 1970-01-01
    • 2013-10-03
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 2021-12-13
    相关资源
    最近更新 更多