Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterate over table filling 2 HasSets in a functional way in Scala

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

like image 831
Phoen Avatar asked Mar 03 '19 16:03

Phoen


2 Answers

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.

like image 136
Travis Brown Avatar answered Nov 15 '22 03:11

Travis Brown


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)
like image 24
Krzysztof Atłasik Avatar answered Nov 15 '22 03:11

Krzysztof Atłasik