0

I am struggling with finding an elegant FP approach to solving the following problem in Scala:

Say I have a set of candidate keys

val validKeys = Set("key1", "key2", "key3")

And a list that

  1. Starts with a key
  2. has some number of non-keys (> 0) between each key
  3. Does not end with a key

For example:

val myList = List("key3", "foo", "bar", "key1", "baz")

I'd like to transform this list into a map by choosing using valid keys as the key and aggregating non-keys as the value. So, in the example above:

("key3" -> "foo\nbar", "key1" -> "baz")

Thanks in advance.

2
  • What should happen if the same key turns up more than once in the list? Commented Dec 25, 2015 at 9:02
  • Good question, assume no duplicate keys. Commented Dec 25, 2015 at 16:48

3 Answers 3

2

Short and simple:

def create(a: List[String]): Map[String, String] = a match {
    case Nil => Map()
    case head :: tail =>
        val (vals, rest) = tail.span(!validKeys(_))
        create(rest) + (head -> vals.mkString("\n"))
}
Sign up to request clarification or add additional context in comments.

4 Comments

Heh. I just wrote something identical apart from variable names :) it doesn't collect all values if keys can appear more than once but it's possible that's not something that can happen in the OP's scenario. And it's not tail recursive, but it's easy to make it so.
Yours uses appending to string. Strings in Scala are immutable, therefore the whole string is copied on append. It is better to collect all the strings and join them in one go.
Agreed. The problem with the foldLeft is you don't get a chance to collect all the strings at the end (it could convert to a list of strings then do a map of mkString, but that makes it less clear).
Thanks, I went with the tail-rec version of this, but otherwise exactly what I was looking for.
1

Traversing a list from left to right, accumulating a result should suggest foldLeft

 myList.foldLeft((Map[String, String](), "")) {
    case ((m, lk), s) =>
      if (validKeys contains s)
        (m updated (s, ""), s)
      else (m updated (lk, if (m(lk) == "") s else m(lk) + "\n" + s), lk)
  }._1
  // Map(key3 -> foo\nbar, key1 -> baz)

Comments

0

As a first approximation solution:

def group(list:List[String]):List[(String, List[String])] = {
  @tailrec
  def grp(list:List[String], key:String, acc:List[String]):List[(String, List[String])] =
    list match {
      case Nil => List((key, acc.reverse))
      case x :: xs if validKeys(x) => (key, acc.reverse)::group(x::xs)
      case x :: xs => grp(xs, key, x::acc)
    }
  list match {
    case Nil => Nil
    case x::xs => grp(xs, x, List())
  }
}

val map = group(myList).toMap

Another option:

list.foldLeft((Map[String, String](), "")) {
  case ((map, key), item) if validKeys(item) => (map, item)
  case ((map, key), item) => 
    (map.updated(key, map.get(key).map(v => v + "\n" + item).getOrElse(item)), key)
}._1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.