Search code examples
arraysswiftrecursionrecursive-datastructures

How to recursively create a Modal with an array of data in Swift


I need to recursively add these comments in the correct order.

Currently i get an array of comments that are unsorted. These comments can be the top level or can be a child of the top level.

I was using this recursive method to add them but i can’t seem to get it right.

The result should be an array of CommentModal’s [CommentModal]

For reference: The postID is the topLevel comment that the child belongs to. Every child comment will have a postID so that they know where they belong to. If the child has a child, the top level child’s commentID will be the child’s postID.

Data and Data Models

struct Comment {
    var postID: String
    var commentID: String
    var date: Double
}

class CommentModal {
    var mainComment: Comment
    var childComment: [CommentModal] = []
    
    init(mainComment: Comment) {
        self.mainComment = mainComment
    }
}

let data: [Comment] = [
    Comment(postID: "RootPostID", commentID: "116", date: 1),
    Comment(postID: "RootPostID", commentID: "117", date: 2),
    Comment(postID: "RootPostID", commentID: "118", date: 3),
    Comment(postID: "116",      commentID: "216", date: 4),
    Comment(postID: "117",      commentID: "217", date: 5),
    Comment(postID: "118",      commentID: "218", date: 6),
    Comment(postID: "216",      commentID: "316", date: 7),
    Comment(postID: "216",      commentID: "317", date: 8),
]

Initialized

private func index(comments: [Comment]) {
        discardableCachedComments = comments
        commentModalArray = addChildren(from: comments)
    }

    private func addChildren(from comments: [Comment]) -> [CommentModal] {
        var result: [CommentModal] = []
        
        for comment in comments {
            let children = discardableCachedComments.filter { $0.postID == comment.commentID }
            discardableCachedComments.removeAll { $0.postID == comment.commentID }
            
            let commentModal = CommentModal(mainComment: comment)
            
            if children.count >= 1 {
                commentModal.childComment = addChildren(from: children)
            }
            discardableCachedComments.removeAll { $0.commentID == comment.commentID }
            result.append(commentModal)
        }
        
        return result
    }

Desired Output

Using the data above i want to see the result be:

An array of 3 Top Level CommentModals. Each of those Top Level CommentModals will have a childComment which is an array of CommentModal. For one of those childComment we will also see it have two values in childComment.

If you see the data you will see how the postID and commentID are assembled so that they are added correctly in its respective modal.


Solution

  • I've changed a couple of names to make things semantically a little easier to understand, but this should show the gist of it. I've also changed CommentModal into a struct, since that made initialization easier, but you could change it back.

    This should be copy/pastable into a Playground:

    struct Comment : Codable {
        var parentID: String
        var id: String
        var date: Double
    }
    
    struct CommentModel : Codable {
        var comment: Comment
        var children: [CommentModel] = []
    }
    
    let data: [Comment] = [
        Comment(parentID: "RootPostID", id: "116", date: 1),
        Comment(parentID: "RootPostID", id: "117", date: 2),
        Comment(parentID: "RootPostID", id: "118", date: 3),
        Comment(parentID: "116",      id: "216", date: 4),
        Comment(parentID: "117",      id: "217", date: 5),
        Comment(parentID: "118",      id: "218", date: 6),
        Comment(parentID: "216",      id: "316", date: 7),
        Comment(parentID: "216",      id: "317", date: 8),
    ]
    
    func createCommentModels(rootKey: String, input: [Comment]) -> [CommentModel] {
        return input
            .filter { $0.parentID == rootKey }
            .map { comment in
                return CommentModel(comment: comment,
                                    children: createCommentModels(rootKey: comment.id,
                                                                  input: input
                                                                  ))
            }
    }
    
    func printModal(_ input: CommentModel, indent: Int = 0) {
        let indentChars = Array(repeating: " ", count: indent).joined(separator: "")
        print("\(indentChars)", input.comment.id)
        if !input.children.isEmpty {
            print("\(indentChars) - Children:")
            input.children.forEach { printModal($0, indent: indent + 4)}
        }
    }
    
    let result = createCommentModels(rootKey: "RootPostID", input: data)
    
    result.forEach {
        printModal($0)
    }
    

    Which yields:

    116
     - Children:
         216
         - Children:
             316
             317
     117
     - Children:
         217
     118
     - Children:
         218