1

Here is a part of code I converted from Java

class Regex(valCollection: Collection[MyClass3]) {
  private val val1 = new HashMap[String, MyClass1]
  private val val2 = new HashMap[String, String]
  private val val3 = new HashMap[String, MyClass2]

  private val filteredValCollection = valCollection.map(x => {
    val valCollectionItem = getValCollectionItem(x)

    x.key match {
      case "123" => val1 + (valCollectionItem -> MyClass1.parse(x.value)) //TODO -- append to val1
      case "456" => val2 + (valCollectionItem -> x.value) //TODO -- append to val2
      case "789" => val3 + (valCollectionItem -> MyClass2.parse(x.value)) //TODO -- append to val3
    }

    valCollectionItem
  })

  def getValCollectionItem = { /*.....*/}
}

1) What I want to do is using only immutable collections and immutability initialize all 4 collections: val1, val2, val3 and filteredValCollection. As you can see, filteredValCollection was initialized and that's fine. However,

case "123" => val1 + (valCollectionItem -> MyClass1.parse(x.value))

returns a result to nowhere as well as for val2 and val3.

My thought is, I have to return a tuple from valCollection.map and initialize all the collections I want afterwards.

So how do I do that?

2) Since this code came from Java code, is there any more efficient analog of Collection[MyClass3] in Scala world?

casperOne
  • 73,706
  • 19
  • 184
  • 253
  • *is there any more efficient analog ...* efficient in what sense? – om-nom-nom Jul 01 '13 at 07:55
  • @om-nom-nom in any sense. –  Jul 01 '13 at 08:06
  • 1
    This question has nothing to do with Java, this tag must be removed. You are attracting the eyes of Java experts, who can clearly offer no help, except if they happen to be Scala experts as well, which is what the Scala tag is for. – Marko Topolnik Jul 01 '13 at 08:31
  • @MarkoTopolnik, just turn a blind eye to it, it should help. –  Jul 01 '13 at 08:51
  • No problem, I've flagged it so it's down to the site moderators. That doesn't mean I see the point of your actions, which seem to achieve nothing but the kind of conversation you are having right now. – Marko Topolnik Jul 01 '13 at 09:01

2 Answers2

2

With immutable collections

def parseValCollection = {
  val elemAndItems = valCollection.map{ x => x -> getValCollectionItem(x) }
  val valCollectionItems = elemAndItems.map{ case (_, item) -> item }
  val val1Map = elemAndItems.filter{ case (x, _) => x.key == "123" }.
    map{ case (x, item) => item -> MyClass1.parse(x.value) }.toMap
  val val2Map = elemAndItems.filter{ case (x, _) => x.key == "456" }.
    map{ case (x, item) => item -> x.value }.toMap
  val val3Map = elemAndItems.filter{ case (x, _) => x.key == "789" }.
    map{ case (x, item) => item -> MyClass2.parse(x.value) }.toMap

  (valCollectionItems, val1Map, val2Map, val3Map)
}

private val (valCollectionItems, val1Map, val2Map, val3Map) = parseValCollection

If you want to iterate through valCollection only once you could use foldLeft, but you shouldn't do it normally - it's a classic case of the premature optimization.

If you want to reduce number of iterations you could replace filter{...}.map{...}.toMap with collect{...}(breakOut):

val val1Map: Map[String, MyClass1] = elemAndItems.collect{
    case (x, item) if x.key == "123" => item -> MyClass1.parse(x.value)
  }(breakOut)

Alternatively you could just add view before filter to use lazy collections.

With mutable collections

But if you want to transform your code from java to scala you could just use mutable collections:

import collection.{mutable => m}

class Regex(valCollection: Iterable[MyClass3]) {
  private val val1 = m.Map[String, MyClass1]()
  private val val2 = m.Map[String, String]()
  private val val3 = m.Map[String, MyClass2]()

  private val filteredValCollection = m.Seq[ItemType]()

  for( x <- valCollection){
    val valCollectionItem = getValCollectionItem(x)
    filteredValCollection += valCollectionItem
    x.key match {
      case "123" => val1 += (valCollectionItem -> MyClass1.parse(x.value))
      case "456" => val2 += (valCollectionItem -> x.value)
      case "789" => val3 += (valCollectionItem -> MyClass2.parse(x.value))
    }
  }

  def getValCollectionItem: ItemType = { /*.....*/}
}

more efficient analog of Collection[MyClass3]

If you want to call this constructor from scala code only you should use common interface of all scala collections: Traversable or Iterable and leave to the caller the choice of implementation.

senia
  • 37,745
  • 4
  • 88
  • 129
  • **With immutable collections:** - there are at least 4 loops there (maps). is there any way to do it more effectively? –  Jul 01 '13 at 12:27
  • @Grienders you can doesn't mean you should. You can use `foldLeft`, but with immutable collections you'll get no performance improvement, so you'll have to use builders. Solution with builders is almost the same as solution with mutable collections. You could replace `filter{...}.map{...}.toMap` with `collect{...}(brakOut)` or just add `view` before `filter` to iterate only once for each `valNMap`. – senia Jul 01 '13 at 12:56
  • It's converted to `scala.collection.immutable.Map[java.lang.String,X]` instead of `scala.collection.immutable.HashMap[java.lang.String,X]`. How do I solve this? –  Jul 01 '13 at 14:41
  • @Grienders: I see 2 solutions. 1) just use a `Map`. Are you sure you need a `HashMap`? In scala default implementations for `Seq`, `IndexedSeq`, `Set` and `Map` are good enough. 2) replace `Map` with `HashMap` in my example with `breakOut`. – senia Jul 01 '13 at 15:01
  • and a couple more questions: 1) roughly speaking, collect is the same as filter + map? 2) is there any way to say something like "implicit breakOut" at the beginning of `parseValCollection` to be able to omit (breakOut) in those 3 "collect" method calls? I tried but I failed. –  Jul 02 '13 at 01:37
  • 1) Yes, except `filter` without `view` creates new collection, so there is an intermediate collection with `filter + map`. 2) `you can doesn't mean you should`. [`breakOut`](http://stackoverflow.com/a/1716558/406435) creates a `CanBuildFrom`. And `CanBuildFrom` is used not only in `collect`, but in many other collection methods. For instance in `map`. Such implicit `breakOut` will break all `map` calls. – senia Jul 02 '13 at 07:33
0

What exactly is the return type you want? val1, val2, val3? It's really not clear to me what you're trying to do. I'm going to assume you want to partition valCollection into three HashMaps.

Let's make a case class for that:

case class CollectionResult(
  val val1: HashMap[String, MyClass1],
  val val2: HashMap[String, String],
  val val3: HashMap[String, MyClass2]
)

If all 3 results had the same type, you could just use a simple Tuple3...

But let's work with CollectionResult. You now want a function that takes a Collection[MyClass3] and returns a CollectionResult:

def partition(input: Collection[MyClass3]): CollectionResult

As the name says, we'll go across input, partitioning the values as we go. Since we want one CollectionResult, many of them, we'll use foldLeft:

def partition(input: Collection[MyClass3]): CollectionResult = {
  val initialAccumulator = CollectionResult(HashMap.empty, HashMap.empty, HashMap.empty, Collection.empty)
  input.foldLeft(initialAccumulator)((accumulator, inputVal) => {
    val newVal1 = (if (inputVal.x == "123")  accumulator.val1 + (inputVal -> MyClass1.parse(inputVal.value)) else accumulator.val1)
    val newVal2 = (if (inputVal.x == "456") accumulator.val2 + inputVal.value else accumulator.val2)
    val newVal3 = (if (inputVal.x == "789")  accumulator.val3 + (inputVal -> MyClass2.parse(inputVal.value)) else accumulator.val3)

    CollectionResult(newVal1, newVal2, newVal3)
  })
}

As you can see, instances of CollectionResult are used to immutably pass the state through the foldLeft() process.

But wait, this seems like a common process. Wouldn't it be nice if Scala had a built-in method to do this? It does: groupBy(). It just requires a function that takes a value in and returns a key for a resulting Map.

Let's use it:

val partitioned: Map[String, Class3] = valCollection.groupBy(inputVal => inputVal.x)
val transformed: Map[String, Any] = partitioned.map((keyValue) => {
  val (key, value) = keyValue
  key match {
    case "123" => MyClass1.parse(value)
    case "789" => MyClass2.parse(value)
    case _     => value
  }
})

This is quite succinct, though it has the disadvantage of requiring multiple iterations (probably not a real issue) and having to use the Any type (a larger issue).

pr1001
  • 21,727
  • 17
  • 79
  • 125
  • `I'm going to assume you want to partition valCollection into three HashMaps.` -- What I want to do is using only immutable collections and immutability initialize all 4 collections: val1, val2, val3 and filteredValCollection. –  Jul 01 '13 at 12:16
  • I'm sorry, but I don't understand what you mean by that. But I guess the other answer addresses your goal. – pr1001 Jul 01 '13 at 14:01