Home > Enterprise >  Convert an array of structs into array of dictionaries with values stated as strings
Convert an array of structs into array of dictionaries with values stated as strings

Time:07-06

I'm not sure if this is possible! But maybe the Stackoverflow community can help.

Description of work: I have created an array of structs [imageFileNames] to help me sort, filter and map a bunch of jpg files. Using the same array of structs, I want to populate a table view that's nested in a collection view. The collection view will use the key inputs as labels and the table view will use the values to populate the table view cells.

Example: enter image description here

Problem: In order to populate both the Collection Cell and Table View, I'd like to convert into an array of dictionaries but instead of having the dictionary values as structs, I'd like to convert them into strings. I use the part property in my struct to populate the keys in the dictionary. Below I displayed my struct build, array of structs and my ideal goal.

Can you all help me build a function that can manage the conversion?

Struct:

struct imageFileNames {
    var scene: String
    var object: String
    var objectNumber: String
    var part: String
    var frame: String
    
    init(scene:String, object:String, objectNumber: String, part:String, frame:String){
        self.scene = scene
        self.object = object
        self.objectNumber = objectNumber
        self.part = part
        self.frame = frame
    }
}

Current:

var notFinal: [imageFileNames] = 
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "kneeRight", frame: "00.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "01.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "02.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "03.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "04.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "05.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "mouthAngry", frame: "00.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "mouthAngry", frame: "01.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "mouthAngry", frame: "02.png"),
    CloneAnimation_20200907.imageFileNames(scene: "scene01", object: "character", objectNumber: "1", part: "mouthAngry", frame: "03.png"),
]

Goal:

var goalFinal: [String: [String]] = [
    "lefteyelid": [
        "scene01_character_1_lefteyelid_01",
        "scene01_character_1_lefteyelid_02",
        "scene01_character_1_lefteyelid_03",
        "scene01_character_1_lefteyelid_04"
    ],
     "mouthAngry": [
        "scene01_character_1_mouthAngry_00",
        "scene01_character_1_mouthAngry_01",
        "scene01_character_1_mouthAngry_02",
        "scene01_character_1_mouthAngry_03",
    ]
]

My Attempts: As you can see from my attempts, I'm pretty stuck on the matter.

Parsing out key value:

  1. I tried to use the let dict = notFinal.toDictionary {$0.part} method to parse out the key input and place corresponding structs into an array as values, however this method doesn't work because it only shows one of the values with the same part name.

Convert struct into string:

  1. I was able to create a function that takes the structure and combines all the properties into one string but is worthless without the initial part.
func convertStructIntoString (structHold: [imageFileNames])->[String] {
    var temp: [imageFileNames] = structHold
   
        let final = temp.map {$0.scene   "_"   $0.object   "_"   $0.objectNumber   "_"   $0.part   "_"   $0.frame}
    
    return final
}

CodePudding user response:

Before providing the answer, let me rename your class to ImageFileName to make the code more legible, so we get this (you may think about coming up with some other name as the struct doesn't really seem to be a file name):

struct ImageFileName {
    var scene: String
    var object: String
    var objectNumber: String
    var part: String
    var frame: String
}

It's probably a good idea to make your type names uppercase and the constructor you provided is not really needed for structs - it gets generated automatically.

Now the function that performs the conversion you're looking for would be:

func finalize(_ imageFileNames: [ImageFileName]) -> [String: [String]] {
    Dictionary(grouping: imageFileNames) { imageFileName in
        imageFileName.part
    }
    .mapValues { groupImageFileNames in
        groupImageFileNames.map { imageFileName in 
            "\(imageFileName.scene)_\(imageFileName.object)_\(imageFileName.part)_\(URL(string: imageFileName.frame)!.deletingPathExtension())"
        }
    }
}

or, the more concise version doing the same thing:

func finalize(_ imageFileNames: [ImageFileName]) -> [String: [String]] {
    Dictionary(grouping: imageFileNames, by: { $0.part })
        .mapValues { $0.map { "\($0.scene)_\($0.object)_\($0.part)_\(URL(string: $0.frame)!.deletingPathExtension())" } }
}

so you can do:

let notFinal: [ImageFileName] = [
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "kneeRight", frame: "00.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "01.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "02.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "03.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "04.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "lefteyelid", frame: "05.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "mouthAngry", frame: "00.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "mouthAngry", frame: "01.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "mouthAngry", frame: "02.png"),
    ImageFileName(scene: "scene01", object: "character", objectNumber: "1", part: "mouthAngry", frame: "03.png")
]

let goalFinal = finalize(notFinal)

EDITED: modified finalized function to add trailing frame index, as pointed out by @Jessy

CodePudding user response:

  1. Adopt CustomStringConvertible.

    import Foundation
    
    extension imageFileNames: CustomStringConvertible {
      var description: String {
        """
        \(scene)_\(object)_\(objectNumber)_\(part)_\
        \(URL(string: frame)!.deletingPathExtension())
        """
      }
    }
    
  2. Do what the other people are telling you:

    import struct Collections.OrderedDictionary
    
    extension OrderedDictionary where Key == String, Value == [String] {
      init(_ imageFileNamesSequence: some Sequence<imageFileNames>) {
        self = OrderedDictionary<_, _>(grouping: imageFileNamesSequence, by: \.part)
          .mapValues { $0.map(String.init) }
      }
    }
    
  • Related