Home > Blockchain >  How to flatten nested tree of objects?
How to flatten nested tree of objects?

Time:11-25

I have next models structure:

data class Person(
    val id: UUID,
    val firstName: String,
    val lastName: String,
    val email: String,
    val login: String,
    val passwordHash: String,
    val roles: Set<Role>
)

data class Role(
    val id: UUID,
    val name: String,
    val childrenRoles: List<Role>?
)

And trying to flatten nested structure of objects to get list of resolved roles. But have no idea how to do it in functional style. Tried several wrong approaches:

 fun flattenAllRoles(rootRoles: List<Role>?, roles: List<Role>?): List<Role>? {
        return if (rootRoles == null && roles == null && roles?.isEmpty() == true) {
            null
        } else {
            roles
                ?.mapNotNull { rootRoles?.plus((flattenAllRoles(rootRoles, it.childrenRoles))) }
                ?.flatten()
                ?.toList() as List<Role>?
        }
    }

    fun resolve(rootPerson: Person): List<Role>? {
        val rootRoles = rootPerson.roles.toList()
        return flattenAllRoles(
            rootRoles = rootRoles,
            roles = rootRoles.mapNotNull { it.childrenRoles }.flatten().toList()
        )
    }

Would you kindly help me with it?

CodePudding user response:

Try collect the children recursively and add them to the list of parent roles passed as a parameter (since plus() returns a new list this doesn't change the parameter so you're safe):

fun flattenAllRoles(roles: List<Role>?): List<Role>? {
  return roles?.plus(roles?.mapNotNull { flattenAllRoles(it.childrenRoles) }
                          ?.flatten()
                          ?.toList())
}

Assuming we have the following role tree:

 A
  - AA
   - AAA
   - AAB
  - AB
   - ABA
   - ABB
 

You should now get a list of A, AA, AB, AAA, AAB, ABA, ABB.

How this works:

  • initial call: A is added to list1
  • recursion 1: AA and AB are added list2 (passed as parameters)
  • recursion 2 (from 1): AAA and AAB are added list3 (passed as parameters)
  • recursion 3 (from 1): ABA and ABB are added list4 (passed as parameters)
  • back to recursion 1: the collected lists list3 and list4 are flattened and added to list2 which now contains AA, AB, AAA, AAB, ABA and ABB.
  • back to initial call: the returned list2 is flattened and added to list1 which finally is returned.

CodePudding user response:

If you have a lot of nesting, you might want to avoid re-creating list objects over and over.

By providing a function similar to flatMapTo of the Kotlin stdlib, you can achieve this goal.

fun Role.flattenedTo(destination: MutableCollection<Role>) {
    destination.add(this)

    childrenRoles?.forEach { it.flattenedTo(destination) }
}

fun Role.flattened(): List<Role> {
    val allRoles = mutableListOf<Role>()

    this.flattenedTo(allRoles)

    return allRoles
}

If you want to convert a nullable list of Roles, you might add this little function to it.

fun flattenAllRoles(roles: List<Role>?): List<Role>? = roles?.flatMap { it.flattened() }

However, you might rather want to add an extension to the Person class I assume.

fun Person.allRoles() = roles.flatMap { it.flattened() }

CodePudding user response:

You can use the following function to flatten the entire hierarchy:

fun List<Role>.flattenRoles(): List<Role> {
    return this   map { it.childrenRoles?.flattenRoles() ?: emptyList() }.flatten()
}

try it yourself

  • flattenRoles is an extension function on List<Role>. (You can handle the nullability of this list at call site).
  • it.childrenRoles?.flattenRoles() returns the flattened list of all children roles. If childrenRoles is null, an emptyList() is used.
  • All the roles in the list are mapped to the list of their childrenRoles and then that List<List<Role>> is flattened using flatten.
  • Related