I have array thay contains data like this: [value]#[mode] (mode is optional)
I want to split this array to arrays of [value] by [mode] but keeping the same position for data so it can be many array for same mode:
ex:
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]
I tried this extension but is not what i want
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
}
}
it give me result like this:
No mode ---> [23.88,24,30,31,40]
mode = C ---> [24.16,25,12,44,55]
mode = O ---> [24.44,50]
I offer you to use array with tuples (to hold keys). Compose this array and then you easily can remap it to format you need:
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])]
Look at this code:
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"]]
You'll need to modify the groupBy
extension to group into an array of 2-tuples rather than an dictionary, where the first tuple element corresponds to a non-unique "key", and the 2nd tuple element is an array of subsequent elements in the self
array that can be categorized to the given key.
Modified SequenceType
extension
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
}
}
Note also that is now suffices that the generic U
in the extension conforms to Comparable
, as we only use the U
elements as "fake" keys in a tuple.
Call to extension
With this modification, we can call the groupBy(..)
method as
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"])] */
Removing original mode markings ( #X
) from grouped array
If you'd like to remove original mode markings ( #X
) in the resulting array, you can apply an additional map
operation following the call to groupBy
.
Removing markings with resulting values as String
:
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"])] */
Or, with resulting values as 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])] */
Alternatively: group and clean up mode markings in single chained call
Or, both groupBy
followed by map
at once, without an intermediate assignment:
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))
})
}
(Analogously for the resulting Double
values case.)
Six years later, there's still no simple way to get this kind of segmentation. At least I'm not aware of any. I came across this issue today and here's how I solved it (in 2022, using Swift 5+).
Implementation is a an extension to 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
}
}
}
}
Using it is a simple one-liner, like in this test case:
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)]
])
}
I tested it with empty collections and even with optional keys, like you have in the original question. Works well.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.