0

Was tinkering about translating a Java module into Scala and thought this would be a good learning exercise on Maps/Placeholders... Is there a way to translate the essence of this code block into single (map?) statement? (This is what I've come up with so far for a Scala-ized version)

dataProcList foreach (processor => { // #1
  val fieldName: String = processor.getWriteColumn // #2
  val sqlValue: String = processor.getSqlValue(hashMapOfFieldValuePairs.get(fieldName)) // #3
  sqlColumnValuePair += ((fieldName, sqlValue)) // #4
})

In short:

  1. A list of DataProcessor(s) is iterated
  2. From each DP, a string is extracted (call it "Alpha")
  3. Alphpa is used as the look-up key to a (externally defined) HashMap, which gets/returns a SQL-prep'd value (call it "Zed")
  4. Alpha and Zed are then paired and appended to their own List/Hash/Set/Whatever (called "sqlColumnValuePair")

Those lines have been expanded for readability; and portions could be distilled down using various map-ings (for example 1 & 2), but is there a way to do it in using a single statment? It feels like I should be able to condense it down that far...but I don't have enough Scala experience with Map-ings/placeholders to know if it is a fruitless endeavor, or not.

TIA.

(NOTE: I found the answer from this post to be a pretty educational summary on Scala's Map operations: Functional programming, Scala map and fold left)

EDIT: I removed a .getOrElse() operation from the original example, as I think it was just a distraction to the core problem.

EDIT 2: After some consideration, maybe a better way to phrase my learning objective here would be: "How to duplicate this functionality using the fewest number of operations." (Which I had assumed would be some combination of map operations, partial functions, and so forth.) And in doing so, hopefully gain a broader understanding of the set of Scala classes/operators that can be used in similar situations.

Community
  • 1
  • 1
mjk
  • 659
  • 1
  • 9
  • 20

4 Answers4

1

I'm not really sure what you mean by "condense it to a single statement". You could define a function that, given a Processor, return a pair (fieldName, sqlValue) and use that function in a map.

def getColumnValuePair(p:Processor) = { 
  val fieldName = p.getWriteColumn
  fieldName -> p.getSqlValue(hashMapOfFieldValuePairs.get(fieldName))
}

val sqlColumnValuePair = dataProcList.map(getColumnValuePair)
Marth
  • 23,920
  • 3
  • 60
  • 72
  • 1
    I was namely curious as to whether a series of inline map() operations could be used to achieve the same outcome. I've seen some pretty elaborate map-ings using placholders and didn't know if my example could be re-tooled in that fashion, or not...before I wasted a lot of time/effort trying. – mjk Aug 16 '13 at 17:41
1

You could do this with a for comprehension (and this is probably the most idiomatic way to do it):

for {
  processor <- dataProcList
  fieldName = processor.getWriteColumn
  fieldValue <- hashMapOfFieldValuePairs.get(fieldName)
} yield (fieldName -> processor.getSqlValue(fieldValue))

This amounts to: for each processor, get the write column, look this up in the field-value hashmap (get back an Option), then for each value in that Option (either one or zero values), yield (roughly: add to a growing sequence of result values) the pair fieldName and corresponding sqlValue, if any.

Note that in this case, there is no getOrElse call, as such - if there isn't an entry in the hash map for a given field name, then no (fieldname, sqlValue) pair will be generated for that processor. The final result will have those values filtered out (which may or may not match the semantics you are after).

Shadowlands
  • 14,994
  • 4
  • 45
  • 43
  • 1
    Thanks. I think the yield might be the component that I was missing, and a quick list.length comparison between Procs and KVPair will should fine (at least for my purposes) as a getOrElse replacement. OOC, given your experience with Scala - is that how you'd recommend tackling the translation (regardless of what I was trying to learn)? – mjk Aug 16 '13 at 17:49
  • 1
    @mjk Without knowing more about what exactly your code is doing, I expect I would handle it as I have shown above. Note that the for comprehension is actually syntactic sugar for a chain of flatMap and map calls anyway, but this is generally seen as more readable and expressive. Further, in this case you are wanting to hold on to the intermediate values for processor and fieldName to use at the end, which would make the chain of mapping methods look quite convoluted (imagine debugging that...). Just because you can write complex one-liners, doesn't mean you always should! – Shadowlands Aug 17 '13 at 00:02
  • "Just because you can write complex one-liners, doesn't mean you always should!"... I absolutely agree with you. In reality, I find that I don't particular use/like map-ops, but it appears others do as I find that I end up reading those operations in a lot of other-people's-code. This seemed like a good hands-on exercise to push my own knowledge boundry (and get some insight into how those with more experience in the language might go about producing the same functionality. -cheers – mjk Aug 17 '13 at 17:12
1

Here's my tested solution. It does not handle any failure (same as original solution).

object ListStuff extends App {

  import ListStuffMocks._

  // original solution (slightly modified to actually compile and run)
  var sqlColumnValuePair = List[(String, String)]()
  dataProcList foreach (processor => {
    val fieldName: String = processor.getWriteColumn
    val sqlValue: String = processor.getSqlValue(hashMapOfFieldValuePairs.get(fieldName).get)
    sqlColumnValuePair :+= ((fieldName, sqlValue))
  })
  val originalSolution = sqlColumnValuePair

  // IMO most readable solution
  val newSolution = for {
    processor <- dataProcList
    fieldName = processor.getWriteColumn
    sqlValue = processor.getSqlValue(hashMapOfFieldValuePairs.get(fieldName).get)
  } yield (fieldName, sqlValue)

  // requested map-solution
  val newSolution2 = dataProcList.map(
    (p) => {
      val fieldName = p.getWriteColumn
      val sqlValue = p.getSqlValue(hashMapOfFieldValuePairs.get(fieldName).get)
      (fieldName, sqlValue)
    }
  )

  println(s"Original solution: $originalSolution")
  println(s"New solution:      $newSolution")
  println(s"New solution #2:   $newSolution2")
}

object ListStuffMocks {

  object DataProcessor {
    val db = Map(
      "valWriteCol1" -> "sqlValWriteCol1",
      "valWriteCol2" -> "sqlValWriteCol2",
      "valWriteCol3" -> "sqlValWriteCol3"
    )
  }

  class DataProcessor(writeColumn: String) {
    def getWriteColumn = writeColumn

    def getSqlValue(field: String): String = DataProcessor.db.get(field).get
  }

  val hashMapOfFieldValuePairs = Map(
    "writeCol1" -> "valWriteCol1",
    "writeCol2" -> "valWriteCol2",
    "writeCol3" -> "valWriteCol3"
  )
  val dataProcList = List(
    new DataProcessor("writeCol1"),
    new DataProcessor("writeCol3")
  )
}

Output:

Original solution: List((writeCol1,sqlValWriteCol1), (writeCol3,sqlValWriteCol3))  
New solution:      List((writeCol1,sqlValWriteCol1), (writeCol3,sqlValWriteCol3))  
New solution #2:   List((writeCol1,sqlValWriteCol1), (writeCol3,sqlValWriteCol3))

EDIT1:
I don't know why you don't want to use the for comprehension - in the end it gets compiled into the map calls... But as you wish, I added a solution only with one map call(newSolution2).

monnef
  • 3,903
  • 5
  • 30
  • 50
  • "I don't know why you don't want to use the for comprehension..." A: my goal in posting the question was twofold: 1) learn an alternate method to accomplish something that I had already translated, and 2) hear alternative suggestions from those with more Scala knowledge than myself. Thanks for your answer/suggestions. -cheers – mjk Aug 18 '13 at 20:42
1

What about using map like this:

dataProcList.map( x => 
  ( x.getWriteColumn -> x.getSqlValue(hashMapOfFieldValuePairs.get(x.getWriteColumn)) ) 
)

or even this, if you don't like calling getWriteColumn twice:

dataProcList.map(x=>(x,x.getWriteColumn)).map(y=>(y._2->y._1.getSqlValue(hashMapOfFieldValuePairs.get(y._2)))) 
deepkimo
  • 3,187
  • 3
  • 23
  • 21