Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Split Array to Arrays by value Swift

Tags:

arrays

ios

swift

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] 
like image 519
OuSS Avatar asked May 09 '26 16:05

OuSS


2 Answers

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])]
like image 162
Shadow Of Avatar answered May 12 '26 04:05

Shadow Of


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.)

like image 22
dfrib Avatar answered May 12 '26 06:05

dfrib