I am trying to solve the below problem in a functional way. Given I have a data structure that looks like below:
final case class ActionConfiguration(
configId: Int,
actions: List[Action],
`type`: String,
time: String,
weekday: Option[List[Int]]
)
And I have a Map that has the following signature: Map[TargetLocation, List[ActionConfiguration]]
As you can see I want to execute some actions at a target location at a configured time and weekday. The way it is currently working is, if I have a set of actions to be performed at the same time and day, I am populating only one action in the actions list and creating many ActionConfiguration objects that may have the same configured time and day. My goal is to utilize the Actions list to populate all the actions that I want to execute inside a single ActionConfiguration.
As an example, given the situation today that I have this:
"ActionConfiguration":[
{
"configId":1,
"actions":[
{
"cmd":"doAction1"
}
],
"type":"weekly",
"time":"09:00",
"weekday":[
5
]
},
{
"configId":2,
"actions":[
{
"cmd":"doAction2"
}
],
"type":"weekly",
"time":"09:00",
"weekday":[
5
]
},
{
"configId":3,
"actions":[
{
"cmd":"doAction3"
}
],
"type":"weekly",
"time":"09:00",
"weekday":[
5
]
},
{
"configId":4,
"actions":[
{
"cmd":"doAction4"
}
],
"type":"weekly",
"time":"09:00",
"weekday":[
5
]
},
{
"configId":5,
"actions":[
{
"cmd":"doAction5"
}
],
"type":"weekly",
"time":"22:00",
"weekday":[
4
]
}
]
I want to achieve this:
"ActionConfiguration": [
{
"configId": 1,
"actions": [
{
"cmd": "doAction1"
},
{
"cmd": "doAction2"
},
{
"cmd": "doAction3"
},
{
"cmd": "doAction4"
}
],
"type": "weekly",
"time": "09:00",
"weekday": [
5
]
},
{
"configId": 2,
"actions": [
{
"cmd": "doAction5"
}
],
"type": "weekly",
"time": "22:00",
"weekday": [
4
]
}
]
As you can see I want merge the actions that needs to be performed at the same time into a single list. I am coming from a Java background and currently working on Scala. I know how to solve this problem in Java style, but I am looking for some ways how we can solve this in a functional style as I am very interested to learn functional programming.
CodePudding user response:
Assuming there's a constructor for ActionConfiguration that takes the members in the order below, this probably works (I just typed it here, I didn't compile or test it):
val mergedList = listofActionConfigurations
.groupBy(a => (a.configId, a.time, a.type, a.weekday))
.values
.map(la => (la.head, la.map(_.actions).flatten))
.map(t => new ActionConfiguration(t._1.configId, t._1.time, t._1.type, t._1.weekday, t._2))
.toList
What's happening here?
We group the list of ActionConfigurations by a key created by tupling all fields of ActionConfigurations except the actions list.
Then we throw away the keys and take just the values, which is some Set-like collection containing Lists of ActionConfigurations.
We map over that collection, and for each list of ActionConfigurations in it, we grab the first element of the list (.head) and then we map each ActionConfiguration in the list to its list of actions, and then we flatten the resulting list of lists. We yield a tuple of the head item and the flattened list.
We then map the tuple (head, list of actions) to a new ActionConfiguration with all the members of head except its list of actions, and the flattened list of actions as the new ActionConfiguration's action list.
Finally, since we probably still have a Set-like collection, we explicitly turn it into a List.
It takes much longer to explain than to write. :)
Note that the Scala library docs make no guarantees about the order of the actions in the merged actions list (but the implementation seems to be order-preserving); if you require it to be ordered, sort after flattening then action lists.
CodePudding user response:
It seems all you need is groupMapReduce
to create a Map[TargetLocation, List[Action]]
which you can later map
if you really want a Map[TargetLocation, List[ActionConfiguration]]
configurations.groupMapReduce(
ac => (ac.configId, ac.time, ac.type, ac.weekday)
)(
ac => ac.actions
)(
(l1, l2) => l1 reverse_::: l2 // Similar to l1 l2 but more efficient, although the order changes.
)