简体   繁体   中英

Filter and sort swift array of dictionaries

I have the following array:

let parent = [ ["Step":"S 3", "Desc":"Do third step" ],
               ["Step":"S 1", "Desc":"Do first step" ],
               ["Step":"S 2", "Desc":"Do second step" ],
                ["Step":"P 1", "Desc":"Some other thing" ] ]

How do I filter and sort the array(using filter and sort functions) in the least possible steps so that I get the following output string or label --

1.Do first step

2.Do second step

3.Do third step

I'd suggest filtering, sorting, enumerating, mapping, and joining:

let results = parent
    .filter { $0["Step"]?.first == "S" }
    .sorted { $0["Step"]!.compare($1["Step"]!, options: .numeric) == .orderedAscending }
    .enumerated()
    .map { (index, value) in "\(index + 1). \(value["Desc"]!)" }
    .joined(separator: "\n")

A key consideration is the use of .numeric comparison, so that "S 10" shows up after "S 9", not between "S 1" and "S 2". You don't want to do simple string comparison if you're embedding a numeric value in your string.

I also threw in that enumeration because if you remove an item, you probably want to make sure that the numbers in your list don't skip over a value just because of the particular encoding inside your Step strings.


Unrelated, a dictionary is a poor model for something like this. I'd suggest a custom type:

struct Task {
    enum TaskType {
        case step
        case process    // or whatever "P" is supposed to stand for
    }

    let sequence: Int
    let type: TaskType
    let taskDescription: String
}

let parent = [Task(sequence: 3, type: .step, taskDescription: "Do third step"),
              Task(sequence: 1, type: .step, taskDescription: "Do first step"),
              Task(sequence: 2, type: .step, taskDescription: "Do second step"),
              Task(sequence: 3, type: .process, taskDescription: "Some other thing")]

let results = parent
    .filter { $0.type == .step }
    .sorted { $0.sequence < $1.sequence }
    .map { "\($0.sequence). \($0.taskDescription)" }
    .joined(separator: "\n")

Described answer:

First , you would need to filter the array to get only steps; Based on the posted parent array it seems that the valid step should contain a key as "Step" and a value formatted as "S #", so it could get filtered it as:

let filtered = parent.filter { (currentDict) -> Bool in
    // get the value for key "Step"
    guard let value = currentDict["Step"] else {
        return false
    }

    // check ifthe value matches the "S #"
    let trimmingBySpace = value.components(separatedBy: " ")
    if trimmingBySpace.count != 2 || trimmingBySpace[0] != "S" || Int(trimmingBySpace[1]) == nil {
        return false
    }

    return true
}

so far would get:

[["Step": "S 3", "Desc": "Do third step"],
 ["Step": "S 1", "Desc": "Do first step"],
 ["Step": "S 2", "Desc": "Do second step"]]

Second , you would sort the filtered array by the value of "Step" key:

let sorted = filtered.sorted { $0["Step"]! < $1["Step"]! }

and you should get:

[["Step": "S 1", "Desc": "Do first step"],
 ["Step": "S 2", "Desc": "Do second step"],
 ["Step": "S 3", "Desc": "Do third step"]]

Finally , you would map the sorted array to get the description values:

let descriptions = sorted.map { $0["Desc"] ?? "" }

descriptions should be:

["Do first step", "Do second step", "Do third step"]

All in one step:

let result = parent.filter { (currentDict) -> Bool in
    // get the value for key "Step"
    guard let value = currentDict["Step"] else {
        return false
    }

    // check ifthe value matches the "S #"
    let trimmingBySpace = value.components(separatedBy: " ")
    if trimmingBySpace.count != 2 || trimmingBySpace[0] != "S" || Int(trimmingBySpace[1]) == nil {
        return false
    }

    return true
    }.sorted {
    $0["Step"]! < $1["Step"]!
}.map {
    $0["Desc"] ?? ""
}

print(result) // ["Do first step", "Do second step", "Do third step"]
func sortStep(step1:[String:String], step2:[String:String]) -> Bool {
    guard let s1 = step1["Desc"], let s2 = step2["Desc"] else {
    return false
}
    return s1 < s2
}

let orderedStep = parent.sorted { sortStep(step1:$0, step2:$1) }
print("\(orderedStep)")

I've used forced unwrapping assuming that your structure will remain as mentioned. Feel free to add any checks as required.

parent.filter { $0["Step"]!.contains("S") }.sorted { $0["Step"]! < $1["Step"]!
}.map { print($0["Desc"]!) }
let sorted = parent.filter({ ($0["Step"]?.hasPrefix("S"))! }).sorted { $0["Step"]! < $1["Step"]!}
var prefix = 0
let strings = sorted.reduce("") { (partial, next) -> String in
   prefix += 1
   return partial + "\(prefix)." + next["Desc"]! + "\n"
}

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM