Swift: transformar una matriz de en una matriz de hash , ]]

Tengo 2 types de struct, Header y Session , ambos conformes al protocolo TimelineItem .
Tengo una Array compuesta por TimelineItem así:
[Header1, SessionA, SessionB, Header2, SessionC, SessionD]

Mi necesidad es agrupar Session bajo Header relacionado así:
[ [Header1: [SessionA, SessionB], [Header2: [SessionC, SessionD] ]

Intenté usar el método de filter para recuperar solo la estructura de Header y el método de split para recuperar una matriz de matriz de Session . Estos funcionan bien pero no puedo gestionar cómo reconciliar ambos para build mi object final [[Header: [Session]]] .

Aquí está mi código de ejemplo:

 enum TimelineItemType: String { case Header = "header" case Session = "session" } protocol TimelineItem { var id: Int { get } var type: TimelineItemType { get } var startDate: NSDate { get } } 

La estructura del Header

 struct Header: TimelineItem, Decodable, Hashable, Equatable { let id: Int let type: TimelineItemType = .Header let startDate: NSDate let text: String init?(json: JSON) { guard let id: Int = "id" <~~ json, let type: TimelineItemType = "type" <~~ json, let startDate: NSDate = "startDate" <~~ json, let text: String = "text" <~~ json where type == .Header else { return nil } self.id = id self.startDate = startDate self.text = text } var hashValue: Int { // As id is unique, we can use it for hash purpose return id } } 

La estructura de Session

 struct Session: TimelineItem, Decodable, Equatable { let id: Int let type: TimelineItemType = .Session let startDate: NSDate let name: String let syllabus: String let speaker: Speaker let language: String let room: String let duration: Int init?(json: JSON) { guard let id: Int = "id" <~~ json, let type: TimelineItemType = "type" <~~ json, let startDate: NSDate = Decoder.decodeDateISO8601("startDate")(json), let name: String = "name" <~~ json, let speaker: Speaker = "speaker" <~~ json, let syllabus: String = "syllabus" <~~ json, let language: String = "language" <~~ json, let room: String = "room" <~~ json, let duration: Int = "duration" <~~ json where type == .Session else { return nil } self.id = id self.startDate = startDate self.name = name self.speaker = speaker self.syllabus = syllabus self.language = language self.room = room self.duration = duration } } 

Y, finalmente, el código que traté de dividir mi matriz:

 func timelineFromItems(timelineItems: [TimelineItem]) -> [[Header: [Session]]]? { let slicedSessions = timelineItems.split { $0 is Header } let sessions = Array(slicedSessions) let headers = timelineItems.filter { $0.type == .Header } var timeline = [[Header: [Session]]]() // HOW TO FILL THE TIMELINE ?? } 

¿CÓMO LLENAR EL TIEMPO?

Reduzco este ejemplo eliminando toda la información no necesaria

 protocol P {} struct A: P, Hashable { var i:Int var hashValue: Int { return i } } func ==(lhs: A, rhs: A)->Bool { return lhs.i == rhs.i } struct B: P { var i:Int } // your current data let arr:[P] = [A(i: 1),B(i: 1),B(i: 2), A(i: 2), B(i: 3), B(i: 4), B(i: 5)] 

function que transforma sus datos al formatting requerido

 func foo(arr: [P])->[[A:[B]]]? { var dict:[A:[B]] = [:] var arrb:[B] = [] let arrk:[A] = arr.filter { $0 is A }.map { $0 as! A } guard var key = arr[0] as? A else { return nil } arr.forEach { (p) in if let a = p as? A { dict[key] = arrb arrb = [] key = a } if let b = p as? B { arrb.append(b) } } dict[key] = arrb var arrr:[[A:[B]]] = [] arrk.forEach { (a) in if let arrb = dict[a] { arrr.append([a:arrb]) } } return arrr } 

ahora la matriz resultante se ajusta a sus requisitos (espero que :-))

 if let result = foo(arr) { print(result) // [[A(i: 1): [B(i: 1), B(i: 2)]], [A(i: 2): [B(i: 3), B(i: 4), B(i: 5)]]] } 

otra testing de datos

 let arr:[P] = [A(i: 1),B(i: 1),B(i: 2), A(i: 2), A(i: 3), B(i: 3)] 

te dio

 [[A(i: 1): [B(i: 1), B(i: 2)]], [A(i: 2): []], [A(i: 3): [B(i: 3)]]] 

entonces, funciona aunque no B sigue A

No veo ningún obstáculo en llenar la línea de time, así es como lo haría:

 func timelineFromItems(timelineItems: [TimelineItem]) -> [[Header: [Session]]]? { let slicedSessions = timelineItems.split { $0 is Header } let sessions = Array(slicedSessions) let headers = timelineItems.filter { $0.type == .Header } var timeline = [[Header: [Session]]]() if sessions.count == headers.count { // Check to be sure you have as much header as sessions for (index, value) in headers.enumerate() { let dictionary = [value: sessions.getElement(index)] timeline.append(dictionary) } } return timeline } 

De hecho, puede ir con algo tan simple como:

 func deserialize(input: [TimelineItem]) -> [Header: [Session]] { var result = [Header: [Session]]() var latestHeader: Header! = nil input.forEach() { if let header = $0 as? Header { latestHeader = header result[header] = [] } else if let session = $0 as? Session { result[latestHeader]!.append(session) } } return result } 

UPD

No es tan simple, pero sigue siendo conciso (arriba produce el dictionary con [Session] como elementos, mientras que como @ user3441734 señaló que necesita una matriz de dictionarys de un solo elemento):

 func deserialize(input: [TimelineItem]) -> [[Header: [Session]]] { var result = [[Header: [Session]]]() var latestHeader: Header? = nil input.forEach() { if let header = $0 as? Header { latestHeader = header result.append([header: []]) } else if let session = $0 as? Session, let header = latestHeader { var headerDict = result.popLast()! headerDict[header]!.append(session) result.append(headerDict) } } return result } 

Gracias a todos ustedes, logré satisfacer mi necesidad con el siguiente código:

“ `

 typealias Section = [header: Header, sessions: [TimelineItem]] private static func groupBySection(timelineItems: [TimelineItem]) -> [Section]? { let slicedSessions = timelineItems.split { $0.type == .Header } let headers = timelineItems.filter { $0.type == .Header } var timeline = [Section]() for (index, value) in headers.enumerate() { guard index < slicedSessions.count else { break } let sessionIndex = slicedSessions.startIndex.advancedBy(index) let arraySessions = Array(slicedSessions[sessionIndex]) let header = value as! Header let section = (header: header, sessions: arraySessions) timeline.append(section) } return timeline.count > 0 ? timeline : nil } 

“ `