I'm writing a Scala app and trying to iterate over a table (in form of a 2-dimensional Array) in a functional way. For every line in the table I want to fill a Set with all distinct values from the first column and a second Set with all distinct values from the second column.
I tried many approaches but could not find any solution how to do it in a functional style. Because I get 2 new variables from the iteration, it seems impossible without non-functional help.
Here is a non-functional example with mutable HashSets for a table with products and customers:
val myInputTable =
Array(Array("Product A","Customer 1"), Array("Product B","Customer 1"),
Array("Product C","Customer 2"), Array("Product A","Customer 2"))
val productSet = new collection.mutable.HashSet[String]
val customerSet = new collection.mutable.HashSet[String]
for(
inputLine <- myInputTable;
inputElement <- inputLine
) {
if (inputLine.indexOf(inputElement) == 0) {
productSet.add(inputElement)
} else {
customerSet.add(inputElement)
}
}
println("Product Set:")
productSet.foreach(println)
println("\nCustomer Set:")
customerSet.foreach(println)
Is there a way how I could do this with immutable Sets, other objects or maybe the for-yield syntax?
Thanks for any answers or hints,
Felix
Any time you find yourself trying to FP-ify some code that iterates over a sequence while updating some mutable state, a good first approach is to use foldLeft
:
val myInputTable =
Array(Array("Product A","Customer 1"), Array("Product B","Customer 1"),
Array("Product C","Customer 2"), Array("Product A","Customer 2"))
val (products, customers) =
myInputTable.foldLeft((Set.empty[String], Set.empty[String])) {
case ((ps, cs), Array(p, c)) => (ps + p, cs + c)
case ((ps, cs), _) => (ps, cs) // Alternatively fail here.
}
The first argument to foldLeft
is the initial state. We want to work with two immutable sets, so we use a tuple of Set.empty[String]
. The next argument to foldLeft
here is a function:
{
case ((ps, cs), Array(p, c)) => (ps + p, cs + c)
case ((ps, cs), _) => (ps, cs) // Alternatively fail here.
}
This should be a function from the currently accumulated state (ps, cs)
and each element Array(p, c)
to the next state. It will be applied to every function in the collection from left to right (hence foldLeft
), accumulating the state changes, and it will return the final value of the state. It works like this:
scala> val (products, customers) =
| myInputTable.foldLeft((Set.empty[String], Set.empty[String])) {
| case ((ps, cs), Array(p, c)) => (ps + p, cs + c)
| case ((ps, cs), _) => (ps, cs) // Alternatively fail here.
| }
products: scala.collection.immutable.Set[String] = Set(Product A, Product B, Product C)
customers: scala.collection.immutable.Set[String] = Set(Customer 1, Customer 2)
In some cases there may be more specific combinators that allow you to express your operation more concisely, but foldLeft
is a good general starting place that allows fairly straightforward translations from mutable to purely functional code.
Maybe just transpose would do a job for you?
val table = Array(
Array("Product A","Customer 1"),
Array("Product B","Customer 1"),
Array("Product C","Customer 2"),
Array("Product A","Customer 2")
)
val Array(productSet, customerSet) = table.transpose.map(_.toSet)
productSet //Set(Product A, Product B, Product C)
customerSet //Set(Customer 1, Customer 2)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With