将数组按值拆分为多个数组 Swift

3

我有一个包含数据的数组,数据格式为:[value]#[mode](mode为可选)

我想将这个数组按照 [mode] 分割成 [value] 数组,并保持数据在原来位置,因此对于相同的 mode,可能会产生多个数组:

例如:

let array = ["23.88", "24", "30",  "24.16#C", "25#C", "12#C", "24.44#O", "50#O" , "31", "40" , "44#C", "55#C"]  

// Result 

No mode  ---> [23.88,24,30] 
mode = C ---> [24.16,25,12]
mode = O ---> [24.44,50]
No mode  ---> [31,40] 
mode = C ---> [44,55]

我尝试了这个扩展程序,但不是我想要的内容。
extension SequenceType {
    func groupBy<U : Hashable>(@noescape keyFunc: Generator.Element -> U) -> [U:[Generator.Element]] {
        var dict: [U:[Generator.Element]] = [:]
        for el in self {
            let key = keyFunc(el)
            if case nil = dict[key]?.append(el) { dict[key] = [el] }
        }
        return dict
    }
}

它给我这样的结果:

No mode  ---> [23.88,24,30,31,40] 
mode = C ---> [24.16,25,12,44,55]
mode = O ---> [24.44,50] 

我尝试了扩展GroupBy,但不是我想要的。我更新了我的帖子。 - OuSS
Swift字典要求键必须是唯一的。如果您想使用字典生成结果,就像您将它们分解的那样,您需要将一个键映射到一个数组的数组。 - i_am_jorf
为什么24.44不在与23.88相同的数组中?在您期望的结果数组中。 - Shadow Of
我们需要你的 keyFunc 代码和扩展方法的实际调用。 - BaseZen
我可以为此创建一个解决方案,但可能要到今晚才行。 - Hayden Holligan
显示剩余2条评论
4个回答

2

我建议您使用元组数组(用于保存键)。组合这个数组,然后您可以轻松地将其重新映射到您需要的格式:

let array = ["23.88", "24", "30",  "24.16#C", "25#C", "12#C", "24.44#O", "50#O" , "31", "40" , "44#C", "55#C"]

let noModeTag = "#NoMode"

func group(array: [String]) -> [(String, [Double])]
{
    var result = [(String, [Double])]()

    func addNextElement(number: Double?, _ mode: String?) {
        guard let number = number, mode = mode else {fatalError("input format error")}
        if result.last?.0 == mode {
            var array = result.last!.1
            array.append(number)
            result[result.count - 1] = (mode, array)
        } else {
            result.append((mode, [number]))
        }
    }

    for element in array {
        if element.containsString("#") {
            let separated = element.componentsSeparatedByString("#")
            addNextElement(Double(separated.first ?? "_"), separated.last)
        } else {
            addNextElement(Double(element), noModeTag)
        }
    }
    return result
}

print(group(array))
//[("#NoMode", [23.88, 24.0, 30.0]), ("C", [24.16, 25.0, 12.0]), ("O", [24.44, 50.0]), ("#NoMode", [31.0, 40.0]), ("C", [44.0, 55.0])]

1

看一下这段代码:

func newArray(incomeArray:[String], index: Int) -> [String: [String]] {
    let modeArray = incomeArray[index].componentsSeparatedByString("#")
    let currentMode = modeArray.count > 1 ? modeArray.last : "No mode"
    var arr: [String] = []
    for i in index...incomeArray.count-1 {
        let modeArray = incomeArray[i].componentsSeparatedByString("#")
        let mode = modeArray.count > 1 ? modeArray.last : "No mode"
        if mode == currentMode || (mode == "" && currentMode == "") {
            arr.append(modeArray.first!)
        } else {
            break
        }
    }
    return ["Mode = " + currentMode!: arr];
}


let array: [String] = ["23.88", "24", "30",  "24.16#C", "25#C", "12", "24.44", "50" , "31#O", "40#O" , "44#C", "55#C"]

var index = 0
while index < array.count {
    let dict = newArray(array, index: index)
    print(dict)
    index += dict[Array(dict.keys).first!]!.count
}

result:

["Mode = No mode": ["23.88", "24", "30"]]
["Mode = C": ["24.16", "25"]]
["Mode = No mode": ["12", "24.44", "50"]]
["Mode = O": ["31", "40"]]
["Mode = C": ["44", "55"]]

这将给我一个包含所有#C模式的数组,但我想保持相同的值顺序。如果#C被分开,则需要2个#C数组。 - OuSS
所以,每次 #mode 改变时,您需要新的数组吗? - Igor
是的,那就是我想要的。 - OuSS
这是不完整的。你需要在数组上多次调用它,而且它并不能满足OuSS的要求。 - Hayden Holligan

1
你需要修改groupBy扩展,将其分组为一个2元组的数组而不是一个字典,其中第一个元组元素对应于非唯一“键”,第二个元组元素是self数组中可以归类到给定键的后续元素的数组。 修改后的SequenceType扩展
extension SequenceType {
    func groupBy<U : Comparable>(@noescape keyFunc: Generator.Element -> U) -> [(U,[Generator.Element])] {
        var tupArr: [(U,[Generator.Element])] = []
        for el in self {
            let key = keyFunc(el)
            if tupArr.last?.0 == key {
                tupArr[tupArr.endIndex-1].1.append(el)
            }
            else {
                tupArr.append((key,[el]))
            }
        }
        return tupArr
    }
}

请注意,现在扩展中的通用 U 只需要符合 Comparable 即可,因为我们只将 U 元素用作元组中的“虚拟”键。

调用扩展

通过这种修改,我们可以这样调用 groupBy(..) 方法:

let array = ["23.88", "24", "30",  "24.16#C", "25#C", "12", "24.44", "50" , "31#O", "40#O" , "44#C", "55#C"]

/* assuming we know the last character always describe the mode,
   given one is included (#) */
let groupedArray: [(String,[String])] = array.groupBy {
    guard $0.characters.contains("#") else { return "No mode" }
    return "mode = " + String($0.characters.last!)
}

print(groupedArray)
/* [("No mode", ["23.88", "24", "30"]), 
    ("mode = C", ["24.16#C", "25#C"]), 
    ("No mode", ["12", "24.44", "50"]), 
    ("mode = O", ["31#O", "40#O"]), 
    ("mode = C", ["44#C", "55#C"])] */

从分组的数组中删除原始模式标记(#X

如果您想要在结果数组中删除原始模式标记(#X),可以在调用 groupBy 后应用额外的 map 操作。

将具有字符串值的标记移除:

let groupedArrayClean = groupedArray.map { ($0.0, $0.1.map {
    String($0.characters.prefixUpTo($0.characters.indexOf("#") ?? $0.characters.endIndex))
    })
}

print(groupedArrayClean)
/* [("No mode", ["23.88", "24", "30"]), 
    ("mode = C", ["24.16", "25"]), 
    ("No mode", ["12", "24.44", "50"]), 
    ("mode = O", ["31", "40"]), 
    ("mode = C", ["44", "55"])] */

或者,使用结果值为 Double
let groupedArrayClean = groupedArray.map { ($0.0, $0.1.flatMap {
    Double(
        String(($0.characters.prefixUpTo($0.characters.indexOf("#") ?? $0.characters.endIndex))))
    })
}

print(groupedArrayClean)
/* [("No mode", [23.879999999999999, 24.0, 30.0]), 
    ("mode = C", [24.16, 25.0]), 
    ("No mode", [12.0, 24.440000000000001, 50.0]), 
    ("mode = O", [31.0, 40.0]), 
    ("mode = C", [44.0, 55.0])] */

或者:在单个链接调用中对模式标记进行分组和清理

或者,一次性使用groupBymap,不需要中间赋值:

let groupedArrayClean: [(String,[String])] = array.groupBy {
    guard $0.characters.contains("#") else { return "No mode" }
    return "mode = " + String($0.characters.last!)
    }
    .map { ($0.0, $0.1.map {
        String($0.characters
            .prefixUpTo($0.characters.indexOf("#") ?? $0.characters.endIndex))
        })
}

(类似地,对于生成的Double值情况也是如此。)

1
到目前为止,我认为这是最好的答案。它在不需要大量代码的情况下添加到了初始函数中,并且正好做到了它应该做的事情。 - Hayden Holligan
@HaydenHolligan 我同意。不过,当我开始写我的答案时,OP并没有提供任何函数。 - Shadow Of
@ShadowOf 说得好。你的代码也能实现同样的功能,但是无论是否是函数,都有比必要的更多的代码。 - Hayden Holligan
@OuSS 很高兴能帮忙! - dfrib
2
如果仅凭这个标准就可以对答案进行负分,那么很多人都有很多负分要扣了...我只是在使用移动设备,但您可以随意更新答案中您认为更合适的参数名称。 - dfrib
显示剩余3条评论

0
六年过去了,还是没有一种简单的方法来进行这种分段。至少我不知道是否有。今天我遇到了这个问题,以下是我如何解决它的方法(使用Swift 5+在2022年)。
实现是对Collection的扩展:
extension Collection {
    func segments<Eq: Equatable>(_ key: @escaping (Element) -> Eq) -> AnySequence<SubSequence> {
        AnySequence { () -> AnyIterator<SubSequence> in
            var idx = startIndex
            var prevKey: Eq?

            return AnyIterator {
                guard idx < endIndex else { return nil }
            
                let subSequence = self[idx ..< endIndex]
                    .prefix { element in
                        let elementKey = key(element)
                        defer {prevKey = elementKey}
                    
                        if let prevKey = prevKey {
                            return prevKey == elementKey
                        } else {
                            return true
                        }
                    }
                idx = index(idx, offsetBy: subSequence.count, limitedBy: endIndex) ?? endIndex
                return subSequence
            }
        }
    }
}

使用它只需要一行代码,就像这个测试案例一样:

    struct ElementOptionalKey: Equatable {
        let key: String?
        let value: Double
    }

    func testArraySplitterOptionalKey() throws {
        let array: [ElementOptionalKey] = [
            ElementOptionalKey(key: "1", value: 1.0),
            ElementOptionalKey(key: "1", value: 1.1),
            ElementOptionalKey(key: "2", value: 2.0),
            ElementOptionalKey(key: "2", value: 2.1),
            ElementOptionalKey(key: nil, value: 0.0),
            ElementOptionalKey(key: nil, value: 0.1),
            ElementOptionalKey(key: "1", value: 1.2),
            ElementOptionalKey(key: "1", value: 1.3),
            ElementOptionalKey(key: "1", value: 1.4),
            ElementOptionalKey(key: nil, value: 0.2),
            ElementOptionalKey(key: "3", value: 3.0)
        ]
    
        // HERE is the line :-)
        let result = array.segments { $0.key }
    
        XCTAssertEqual(Array(result), [
            [ElementOptionalKey(key: "1", value: 1.0), ElementOptionalKey(key: "1", value: 1.1)],
            [ElementOptionalKey(key: "2", value: 2.0), ElementOptionalKey(key: "2", value: 2.1)],
            [ElementOptionalKey(key: nil, value: 0.0), ElementOptionalKey(key: nil, value: 0.1)],
            [ElementOptionalKey(key: "1", value: 1.2), ElementOptionalKey(key: "1", value: 1.3), ElementOptionalKey(key: "1", value: 1.4)],
            [ElementOptionalKey(key: nil, value: 0.2)],
            [ElementOptionalKey(key: "3", value: 3.0)]
        ])
    }

我测试了空集合甚至带可选键的情况,就像你在原问题中所拥有的一样。运行良好。


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