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
andlist4
are flattened and added tolist2
which now contains AA, AB, AAA, AAB, ABA and ABB. - back to initial call: the returned
list2
is flattened and added tolist1
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()
}
flattenRoles
is an extension function onList<Role>
. (You can handle the nullability of this list at call site).it.childrenRoles?.flattenRoles()
returns the flattened list of all children roles. IfchildrenRoles
isnull
, anemptyList()
is used.- All the roles in the list are mapped to the list of their
childrenRoles
and then thatList<List<Role>>
is flattened usingflatten
.