skip to Main Content

I’m stuck in this problem.

Here is my Dictionary of arrays:

{"Image":["22301657205688/Chin2231657205705u3zK.jpeg","22301657205688/Chin2231657205707k6HN.jpeg","22301657205688/Chin2231657205708Ip57.jpeg","22301657205688/Forehead2231657205693CbX9.jpeg","22301657205688/L Cheek2231657205697g8d4.jpeg","22301657205688/Nose22316572057008AGT.jpeg","22301657205688/Nose2231657205702k9OU.jpeg"],"OutputScore":[3,9,9,3,1,3,9],"TotalScore":5.285714285714286}

I need to get the average number of OutputScore for the common Image name like Chin and Nose which are common in an array of Image. How can I filter the common name then compare it with the OutputScore indexes and get the average for the same names?

For Example There is 2 Nose Image name at index 5,6 and I need their average score from OutputScore value 3,9 at the same index.

Please help. Thanks.

3

Answers


  1. Let’s start with parsing the JSON, and extract the values:

    let jsonStr = """
    {
    "Image": [
        "22301657205688/Chin2231657205705u3zK.jpeg",
        "22301657205688/Chin2231657205707k6HN.jpeg",
        "22301657205688/Chin2231657205708Ip57.jpeg",
        "22301657205688/Forehead2231657205693CbX9.jpeg",
        "22301657205688/L Cheek2231657205697g8d4.jpeg",
        "22301657205688/Nose22316572057008AGT.jpeg",
        "22301657205688/Nose2231657205702k9OU.jpeg"
    ],
    "OutputScore": [
        3,
        9,
        9,
        3,
        1,
        3,
        9
    ],
    "TotalScore": 5.285714285714286
    }
    """
    
    let jsonDict = try! JSONSerialization.jsonObject(with: Data(jsonStr.utf8), options: []) as! [String: Any]
    let images = jsonDict["Image"] as! [String]
    let scores = jsonDict["OutputScore"] as! [Int]
    

    You need a method to extract the "name" from that partial URL. Here’s an attempt to do so. Your full needs aren’t clear enough, but it does the trick for your sample.

    func extractPart(from: String) -> String? {
        let regex = try! NSRegularExpression(pattern: "\d+\/([A-Za-z ]+)", options: [])
        guard let firstMatch = regex.firstMatch(in: from, options: [], range: NSRange(location: 0, length: from.utf16.count)) else { return nil }
        let partNSRange = firstMatch.range(at: 1)
        guard let partRange = Range(partNSRange, in: from) else { return nil }
        let part = from[partRange]
        return String(part)
    }
    

    We need to "link" images[0] & scores[0], images[1] & scores[1], … images[n] & scores[n]
    To do so, we can use zip():

    let zip = zip(images, scores)
    

    Now, let’s regroup the zip values which have the same part name:

    We can use Dictionary(grouping:by:) in order to group the values, transforming it into a Dictionary where keys are the part name, and values the zip couples:

    let partDict: [String : [(String, Int)]] = Dictionary(grouping: zip) { anCoupleElement in
        guard let name = extractPart(from: anCoupleElement.0) else {
            print("Couldn't extract part name from (anCoupleElement.0)")
            return "Unknown Key"
        }
        return name
    }
    print(partDict)
    

    We can use reduce(into:_:) in order to group the values, transforming it into a Dictionary where keys are the part name, and values the zip couples

    let reduced = zip.reduce(into: [String: [(String, Int)]]()) { partialResult, current in
        guard let name = extractPart(from: current.0) else {
            print("Couldn't extract part name from (current.0)")
            return
        }
        partialResult[name, default: []] += [current]
    }
    print(reduced)
    

    Then, we can calculate the average.
    I choose an iteration, since it’s not clear if you have the "Chin", and you need to search the dictionary for it or not. I used a for loop to show them all:

    for (aPartName, values) in partDict { //or for (aPartName, values) in reduced
        let average = Float(values.reduce(0) { $0 + $1.1 }) / Float(values.count)
        print("For: (aPartName), average: (average)")
        print("With values:")
        values.forEach {
            print("t($0.0) - ($0.1)")
        }
    }
    

    Final Output:

    For: Forehead, average: 3.0
    With values:
        22301657205688/Forehead2231657205693CbX9.jpeg - 3
    For: Nose, average: 6.0
    With values:
        22301657205688/Nose22316572057008AGT.jpeg - 3
        22301657205688/Nose2231657205702k9OU.jpeg - 9
    For: L Cheek, average: 1.0
    With values:
        22301657205688/L Cheek2231657205697g8d4.jpeg - 1
    For: Chin, average: 7.0
    With values:
        22301657205688/Chin2231657205705u3zK.jpeg - 3
        22301657205688/Chin2231657205707k6HN.jpeg - 9
        22301657205688/Chin2231657205708Ip57.jpeg - 9
    
    Login or Signup to reply.
  2. If I understood correctly, once you convert that response to an object you would need to process it. You could iterate all elements of the array "Image" and check if the element contains the feature you look for, if they do, add the value in that same index of OutputScore, finally average that.

    Assuming Images and OutputScore have the same size, and you have parsed them already, the code would look like:

    func getAverageFor(feature: String) -> Double {
        var elementsSum: Int = 0
        var elementsSeen: Int = 0
    
        for idx in 0 ..< ImageNames.count {
            if ImageNames[idx].contains(feature) {
                elementsSum += OutputScore[idx]
                elementsSeen += 1
            }
        }
    
        if elementsSeen > 0 {
            return Double(elementsSum) / Double(elementsSeen)
        }
    
        return 0    // I assume that non present features should be 0
    }
    
    Login or Signup to reply.
  3. I’m assuming that you will be converting your response to Model object. So, your model looks something like this

    struct ImageParts: Codable {
        let image: [String]
        let outputScore: [Int]
        let totalScore: Double
    
        enum CodingKeys: String, CodingKey {
            case image = "Image"
            case outputScore = "OutputScore"
            case totalScore = "TotalScore"
        }
    }
    

    Once you have your model object you can use below function to get the all averages mapped

    func getAvgerageMapped(from model: ImageParts, for parts: [String]) -> [[String: Double]]{
        return parts.map { key -> [String: Double] in
            var op = [Int]()
            model.image.enumerated().reduce(op) { partialResult, sequence in
                if sequence.element.contains(key) {
                    op.append(model.outputScore[sequence.offset])
                }
                return op
            }
            var avg = 0.0
            op.reduce(into: avg) { _, i in
                avg += Double(i)
            }
            avg = avg/Double(op.count)
            return [key:avg]
        }
    }
    
    let averages = getAvgerageMapped(from: model, for: ["Chin", "Forehead", "L Cheek", "Nose"])
    print(averages) //[["Chin": 7.0], ["Forehead": 3.0], ["L Cheek": 1.0], ["Nose": 6.0]]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search