Suppose I have a nexted Map in Scala as below:
type MapType = Map[String, Map[String, Map[String, (String, String)]]]
val m: MapType = Map("Alphabet" -> Map( "Big Boss" -> Map("Clandestine Mssion" -> ("Dracula Returns", "Enemy at the Gates"))))
println(m)
This would output the Map as shown below:
Map(Alphabet -> Map(Big Boss -> Map(Clandestine Mssion -> (Dracula Returns,Enemy at the Gates))))
How can I print it like below instead?:
Map(
Alphabet -> Map(Big Boss -> Map(
Clandestine Mssion -> (Dracula Returns,Enemy at the Gates)
)
)
)
Or in a way that is similar to pretty nested JSON.
This should do the trick
object App {
def main(args : Array[String]) {
type MapType = Map[String, Map[String, Map[String, (String, String)]]]
val m: MapType = Map("Alphabet" -> Map( "Big Boss" -> Map("Clandestine Mssion" -> ("Dracula Returns", "Enemy at the Gates"))))
println(m.prettyPrint)
}
implicit class PrettyPrintMap[K, V](val map: Map[K, V]) {
def prettyPrint: PrettyPrintMap[K, V] = this
override def toString: String = {
val valuesString = toStringLines.mkString("\n")
"Map (\n" + valuesString + "\n)"
}
def toStringLines = {
map
.flatMap{ case (k, v) => keyValueToString(k, v)}
.map(indentLine(_))
}
def keyValueToString(key: K, value: V): Iterable[String] = {
value match {
case v: Map[_, _] => Iterable(key + " -> Map (") ++ v.prettyPrint.toStringLines ++ Iterable(")")
case x => Iterable(key + " -> " + x.toString)
}
}
def indentLine(line: String): String = {
"\t" + line
}
}
}
The output is
Map (
Alphabet -> Map (
Big Boss -> Map (
Clandestine Mssion -> (Dracula Returns,Enemy at the Gates)
)
)
)
Here's an approach using type classes that avoids casting and toString.
trait Show[A] {
def apply(a: A): String
}
object Show {
def apply[A](f: A => String): Show[A] =
new Show[A] {
def apply(a: A): String = f(a)
}
}
implicit def showString: Show[String] = Show(identity)
implicit def show2[A, B](implicit sA: Show[A], sB: Show[B]): Show[(A, B)] =
Show({ case (a, b) => s"(${sA(a)}, ${sB(b)})" })
implicit def showMap[A, B](implicit sA: Show[A], sB: Show[B]): Show[Map[A, B]] =
Show(m => s"Map(\n${
m.map({
case (a, b) => s" ${sA(a)} -> ${sB(b).replace("\n", "\n ")}"
}).mkString(",\n")
}\n)")
def show[A](a: A)(implicit s: Show[A]): String = s(a)
val m = Map("Alphabet" -> Map("Big Boss" -> Map("Clandestine Mission" ->
("Dracula Returns", "Enemy at the Gates"))))
show(m)
Result:
Map(
Alphabet -> Map(
Big Boss -> Map(
Clandestine Mission -> (Dracula Returns, Enemy at the Gates)
)
)
)
Related
def merge(bigrams1: Map[String, mutable.SortedMap[String, Int]],
bigrams2: Map[String, mutable.SortedMap[String, Int]]): Map[String, mutable.SortedMap[String, Int]] = {
bigrams2 ++ bigrams1
.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap())
.map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
}
At compile time, I get these errors:
Error:(64, 114) diverging implicit expansion for type scala.math.Ordering[T1]
starting with method Tuple9 in object Ordering
bigrams2 ++ bigrams1.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap()).map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
Error:(64, 114) not enough arguments for method apply: (implicit ord: scala.math.Ordering[A])scala.collection.mutable.SortedMap[A,B] in class SortedMapFactory.
Unspecified value parameter ord.
bigrams2 ++ bigrams1.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap()).map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
Specifying the types of the sorted map solves the problem:
def merge(bigrams1: Map[String, mutable.SortedMap[String, Int]],
bigrams2: Map[String, mutable.SortedMap[String, Int]]): Map[String, mutable.SortedMap[String, Int]] = {
bigrams2 ++ bigrams1
.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap[String, Int]())
.map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
}
Why do these type parameters need to be specified? Why can they not be inferred without problems with the implicit ordering?
Full code:
import java.io.File
import scala.annotation.tailrec
import scala.collection.mutable
import scala.io.Source
import scala.util.matching.Regex
case class Bigrams(bigrams: Map[String, mutable.SortedMap[String, Int]]) {
def mergeIn(bigramsIn: Map[String, mutable.SortedMap[String, Int]]): Bigrams = {
Bigrams(Bigrams.merge(bigrams, bigramsIn))
}
def extractStatistics(path: String): Bigrams = {
val entry: File = new File(path)
if (entry.exists && entry.isDirectory) {
val bigramsFromDir: Map[String, mutable.SortedMap[String, Int]] = entry
.listFiles
.filter(file => file.isFile && file.getName.endsWith(".sgm"))
.map(Bigrams.getBigramsFrom)
.foldLeft(Map[String, mutable.SortedMap[String, Int]]())(Bigrams.merge)
val bigramsFromSubDirs: Bigrams = entry
.listFiles
.filter(entry => entry.isDirectory)
.map(entry => extractStatistics(entry.getAbsolutePath))
.foldLeft(Bigrams())(Bigrams.merge)
bigramsFromSubDirs.mergeIn(bigramsFromDir)
} else if (entry.exists && entry.isFile) {
Bigrams(Bigrams.getBigramsFrom(entry))
} else
throw new RuntimeException("Incorrect path")
}
def getFreqs(word: String): Option[mutable.SortedMap[String, Int]] = {
bigrams.get(word)
}
}
object Bigrams {
def fromPath(path: String): Bigrams = {
new Bigrams(Map[String, mutable.SortedMap[String, Int]]()).extractStatistics(path)
}
def apply(): Bigrams = {
new Bigrams(Map())
}
val BODY: Regex = "(?s).*<BODY>(.*)</BODY>(?s).*".r
// Return a list with the markup for each article
#tailrec
def readArticles(remainingLines: List[String], acc: List[String]): List[String] = {
if (remainingLines.size == 1) acc
else {
val nextLine = remainingLines.head
if (nextLine.startsWith("<REUTERS ")) readArticles(remainingLines.tail, nextLine +: acc)
else readArticles(remainingLines.tail, (acc.head + "\n" + nextLine) +: acc.tail)
}
}
def merge(bigrams1: Map[String, mutable.SortedMap[String, Int]],
bigrams2: Map[String, mutable.SortedMap[String, Int]]): Map[String, mutable.SortedMap[String, Int]] = {
bigrams2 ++ bigrams1
.map(entry1 => entry1._1 -> (entry1._2 ++ bigrams2.getOrElse(entry1._1, mutable.SortedMap[String, Int]())
.map(entry2 => entry2._1 -> (entry2._2 + entry1._2.getOrElse(entry2._1, 0)))))
}
def merge(bigrams1: Bigrams, bigrams2: Bigrams): Bigrams = {
new Bigrams(merge(bigrams1.bigrams, bigrams2.bigrams))
}
def getBigramsFrom(path: File): Map[String, mutable.SortedMap[String, Int]] = {
val file = Source.fromFile(path)
val fileLines: List[String] = file.getLines().toList
val articles: List[String] = Bigrams.readArticles(fileLines.tail, List())
val bodies: List[String] = articles.map(extractBody).filter(body => !body.isEmpty)
val sentenceTokens: List[List[String]] = bodies.flatMap(getSentenceTokens)
sentenceTokens.foldLeft(Map[String, mutable.SortedMap[String, Int]]())((acc, tokens) => addBigramsFrom(tokens, acc))
}
def getBigrams(tokens: List[String]): List[(String, String)] = {
tokens.indices.
map(i => {
if (i < tokens.size - 1) (tokens(i), tokens(i + 1))
else null
})
.filter(_ != null).toList
}
// Return the body of the markup of one article
def extractBody(article: String): String = {
try {
val body: String = article match {
case Bigrams.BODY(bodyGroup) => bodyGroup
}
body
}
catch {
case _: MatchError => ""
}
}
def getSentenceTokens(text: String): List[List[String]] = {
val separatedBySpace: List[String] = text
.replace('\n', ' ')
.replaceAll(" +", " ") // regex
.split(" ")
.map(token => if (token.endsWith(",")) token.init.toString else token)
.toList
val splitAt: List[Int] = separatedBySpace.indices
.filter(i => i > 0 && separatedBySpace(i - 1).endsWith(".") || i == 0)
.toList
groupBySentenceTokens(separatedBySpace, splitAt, List()).map(sentenceTokens => sentenceTokens.init :+ sentenceTokens.last.substring(0, sentenceTokens.last.length - 1))
}
#tailrec
def groupBySentenceTokens(tokens: List[String], splitAt: List[Int], sentences: List[List[String]]): List[List[String]] = {
if (splitAt.size <= 1) {
if (splitAt.size == 1) {
sentences :+ tokens.slice(splitAt.head, tokens.size)
} else {
sentences
}
}
else groupBySentenceTokens(tokens, splitAt.tail, sentences :+ tokens.slice(splitAt.head, splitAt.tail.head))
}
def addBigramsFrom(tokens: List[String], bigrams: Map[String, mutable.SortedMap[String, Int]]): Map[String, mutable.SortedMap[String, Int]] = {
var newBigrams = bigrams
val bigramsFromTokens: List[(String, String)] = Bigrams.getBigrams(tokens)
bigramsFromTokens.foreach(bigram => { // TODO: This code uses side effects to get the job done. Try to remove them.
val currentFreqs: mutable.SortedMap[String, Int] = newBigrams.get(bigram._1)
.map((map: mutable.SortedMap[String, Int]) => map)
.getOrElse(mutable.SortedMap())
val incrementedWordFreq = currentFreqs.get(bigram._2)
.map(freq => freq + 1)
.getOrElse(1)
val newFreqs = currentFreqs + (bigram._2 -> incrementedWordFreq)
newBigrams = newBigrams - bigram._1 + (bigram._1 -> newFreqs)
})
newBigrams
}
}
The thing is that method Map#getOrElse in 2.13 (or MapLike#getOrElse in 2.12) has signature
def getOrElse[V1 >: V](key: K, default: => V1): V1
https://github.com/scala/scala/blob/2.13.x/src/library/scala/collection/Map.scala#L132-L135
https://github.com/scala/scala/blob/2.12.x/src/library/scala/collection/MapLike.scala#L129-L132
i.e. it expects not necessarily default of the same type as V in Map[K, +V] (or MapLike[K, +V, +This <: MapLike[K, V, This] with Map[K, V]]) but possibly of a supertype of V. In your case V is mutable.SortedMap[String, Int] and there are so many its supertypes (you can look at inheritance hierarchy yourself). mutable.SortedMap() can be of any type mutable.SortedMap[A, B] or their super types.
If you replace method getOrElse with having fixed V
implicit class MapOps[K, V](m: Map[K, V]) {
def getOrElse1(key: K, default: => V): V = m.get(key) match {
case Some(v) => v
case None => default
}
}
or in 2.12
implicit class MapOps[K, V, +This <: MapLike[K, V, This] with Map[K, V]](m: MapLike[K, V, This]) {
def getOrElse1(key: K, default: => V): V = m.get(key) match {
case Some(v) => v
case None => default
}
}
then in your code
... bigrams2.getOrElse1(entry1._1, mutable.SortedMap())
all types will be inferred and it will compile.
So sometimes types should be just specified explicitly when compiler asks.
I have a map which is something like
val m = Map("foo" -> "bar", "faz" -> "baz")
I need to write a custom get method, so that the key can be the key in the map with a number in the end.
So for example:
m.get("foo1") should return "bar"
I am looking for a good scala pattern to solve this problem.
Also I am generating the above map from a for loop using yield, so I can't do something like this
val m = CustomMap("foo" -> "bar")
Any solutions will be appreciated.
Thanks
First of all, you can generate a map from a for comprehension, and then convert it to CustomMap. You just need to define a
def apply(map: Map[String, String]) = CustomMap(map.toSeq :_*) in CustomMap - then you can do val m = CustomMap( for { ... } yield ... )
Secondly, if it doesn't have to be named get (it probably shouldn't be anyway), you can do this sort of thing with an implicit:
object PimpMyMap {
val pref = ".*?(\\d+)".r
implicit class Pimped[V](val map: Map[String,V]) extends AnyVal {
def getPrefix(key: String): Option[V] = map.get(key).orElse { key match {
case pref(k) => map.get(k)
case _ => None
}
}
Now you can write things like:
import PimpMyMap._
val map = Map("foo" -> 1)
val one = map.getPrefix("foo123") // Some(1)
val anotherOne = map.getPrefix("foo") // also Some(1);
You can do this with an implicit class and implicit conversion:
import scala.language.implicitConversions
object MapHelpers {
implicit def optionStringToString(maybeS: Option[String]): String = maybeS.getOrElse("")
implicit class MapWithIntKey(val m: Map[String, String]) extends Map[String, String] {
override def get(key: String): Option[String] = {
val intRegex = """(\d+)""".r
val keyWithoutInt = intRegex
.findFirstMatchIn(key)
.map(int => {
val idx = key.indexOf(int.toString)
key.slice(0, idx)
})
.getOrElse(key)
m.get(keyWithoutInt)
}
def +[V1 >: String](
kv: (String, V1)): scala.collection.immutable.Map[String, V1] = m + kv
def -(key: String): scala.collection.immutable.Map[String, String] = m - key
def iterator: Iterator[(String, String)] = m.iterator
}
}
object App {
import MapHelpers._
def testMapImplicit(): Unit = {
val myMap: MapWithIntKey = Map("foo" -> "bar", "faz" -> "baz")
val result: String = myMap.get("foo1")
println("result", result) // bar
}
}
Working Scastie
If you have a sure way to get the real key from the fake key, you can do this with Map.withDefault:
class CustomMap[K, +V] private (underlying: Map[K, Option[V]]) {
def get(k: K): Option[V] = underlying(k)
}
object CustomMap {
def apply[K, V](original: Map[K, V], keyReducer: K => K) = new CustomMap(originalMap.
mapValues(Some(_)).
withDefault(k => originalMap.get(keyReducer(k))
)
}
In your case, you can use this with
val stringKeyReducer: String => String = k.reverse.dropWhile(_.isDigit).reverse
to drop the digits at the end of your strings, so
CustomMap(Map("foo" -> "bar"), stringKeyReducer).get("foo1") = Some("bar")
Here is solution which combines both the answers.
import scala.language.implicitConversions
object MapHelpers {
implicit def optionStringToString(maybeS: Option[String]): String = maybeS.getOrElse("")
implicit class MapWithIntKey(val m: Map[String, String]) extends Map[String, String] {
override def get(key: String): Option[String] = {
val prefix = "(.*?)\\d+".r
m.get(key).orElse{
key match {
case prefix(p) => m.get(p)
case _ => None
}
}
}
def +[V1 >: String](kv: (String, V1)): scala.collection.immutable.Map[String, V1] = m + kv
def -(key: String): scala.collection.immutable.Map[String, String] = m - key
def iterator: Iterator[(String, String)] = m.iterator
}
}
object App {
import MapHelpers._
def testMapImplicit(): Unit = {
val myMap: MapWithIntKey = Map("foo" -> "bar", "faz" -> "baz")
println("result - number match ", myMap.get("foo1"))
println("result - exact match ", myMap.get("foo"))
}
}
App.testMapImplicit()
Working Scastie
I was wondering if anyone could provide some insight on a problem I'm having. I've made a gist with some code and explanation of my problem: https://gist.github.com/tbrown1979/9993f07c8f4fa2786c83
Basically I'm trying to make something that will allow me to convert List[String] to a case class. I've made a Reader that will allow me to do so, but I've run into the issue where a Reader defined for a case class can't contain a reader for a separate case class.
Looking at the 'non-working example' below - I encounter an issue where, when reading, I don't know how many items to pull out of the list. With Bar, which holds a Test, I would need to pull 2 elements out (because Test has two parameters). Is there a way for me to know the amount of fields a case class has just from its type? Is there a better way to do this?
Here is an example of how to use the Reader. I've included a non-working example as well.
////Working Example////
case class Foo(a: Int, s: String)
object Foo {
implicit val FooReader : Reader[Foo] =
Reader[Int :: String :: HNil].map(Generic[Foo].from _)
}
val read: ValidationNel[String, Foo] = Reader.read[Foo](List("12","text"))
println(read)//Success(Foo(12, "text"))
///////////////////////////
////Non-working Example////
case class Test(a: Int, b: String)
object Test {
implicit val TestReader: Reader[Test] =
Reader[Int :: String :: HNil].map(Generic[Test].from _)
}
case class Bar(c: Test)
object Bar {
implicit val BarReader: Reader[Bar] =
Reader[Test :: HNil].map(Generic[Bar].from _)
}
val barRead = Reader.read[Bar](List("21", "someString"))
println(barRead) //Failure(NonEmptyList("Invalid String: List()", "Exepected empty, but contained value"))
//////////////////////////
Something like this works for me (modification of this)
object ShapelessStringToTypeConverters {
import cats._, implicits._, data.ValidatedNel
import mouse._, string._, option._
import shapeless._, labelled._
private type Result[A] = ValidatedNel[ParseFailure, A]
case class ParseFailure(error: String)
trait Convert[V] {
def parse(input: String): Result[V]
}
object Convert {
def to[V](input: String)(implicit C: Convert[V]): Result[V] =
C.parse(input)
def instance[V](body: String => Result[V]): Convert[V] = new Convert[V] {
def parse(input: String): Result[V] = body(input)
}
implicit def booleans: Convert[Boolean] =
Convert.instance(
s =>
s.parseBooleanValidated
.leftMap(e => ParseFailure(s"Not a Boolean ${e.getMessage}"))
.toValidatedNel)
implicit def ints: Convert[Int] =
Convert.instance(
s =>
s.parseIntValidated
.leftMap(e => ParseFailure(s"Not an Int ${e.getMessage}"))
.toValidatedNel)
implicit def longs: Convert[Long] =
Convert.instance(
s =>
s.parseLongValidated
.leftMap(e => ParseFailure(s"Not an Long ${e.getMessage}"))
.toValidatedNel)
implicit def doubles: Convert[Double] =
Convert.instance(
s =>
s.parseDoubleValidated
.leftMap(e => ParseFailure(s"Not an Double ${e.getMessage}"))
.toValidatedNel)
implicit def strings: Convert[String] = Convert.instance(s => s.validNel)
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sealed trait SchemaMap[A] {
def readFrom(input: Map[String, String]): ValidatedNel[ParseFailure, A]
}
object SchemaMap {
def of[A](implicit s: SchemaMap[A]): SchemaMap[A] = s
private def instance[A](body: Map[String, String] => Result[A]): SchemaMap[A] = new SchemaMap[A] {
def readFrom(input: Map[String, String]): Result[A] =
body(input)
}
implicit val noOp: SchemaMap[HNil] =
SchemaMap.instance(_ => HNil.validNel)
implicit def parsing[K <: Symbol, V: Convert, T <: HList](implicit key: Witness.Aux[K], next: SchemaMap[T]): SchemaMap[FieldType[K, V] :: T] =
SchemaMap.instance { input =>
val fieldName = key.value.name
val parsedField = input
.get(fieldName)
.cata(entry => Convert.to[V](entry), ParseFailure(s"$fieldName is missing").invalidNel)
.map(f => field[K](f))
(parsedField, next.readFrom(input)).mapN(_ :: _)
}
implicit def classes[A, R <: HList](implicit repr: LabelledGeneric.Aux[A, R], schema: SchemaMap[R]): SchemaMap[A] =
SchemaMap.instance { input =>
schema.readFrom(input).map(x => repr.from(x))
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sealed trait SchemaList[A] {
def readFrom(input: List[String]): ValidatedNel[ParseFailure, A]
}
object SchemaList {
def of[A](implicit s: SchemaList[A]): SchemaList[A] = s
private def instance[A](body: List[String] => Result[A]): SchemaList[A] = new SchemaList[A] {
def readFrom(input: List[String]): Result[A] = body(input)
}
implicit val noOp: SchemaList[HNil] =
SchemaList.instance(_ => HNil.validNel)
implicit def parsing[K <: Symbol, V: Convert, T <: HList](implicit key: Witness.Aux[K], next: SchemaList[T]): SchemaList[FieldType[K, V] :: T] =
SchemaList.instance { input =>
val fieldName = key.value.name
val parsedField = input
.headOption
.cata(entry => Convert.to[V](entry), ParseFailure(s"$fieldName is missing").invalidNel)
.map(f => field[K](f))
(parsedField, next.readFrom(input.tail)).mapN(_ :: _)
}
implicit def classes[A, R <: HList](implicit repr: LabelledGeneric.Aux[A, R], schema: SchemaList[R]): SchemaList[A] =
SchemaList.instance { input =>
schema.readFrom(input).map(x => repr.from(x))
}
}
}
/*
case class Foo(a: String, b: Int, c: Boolean)
def m: Map[String, String] = Map("a" -> "hello", "c" -> "true", "b" -> "100")
def e: Map[String, String] = Map("c" -> "true", "b" -> "a100")
val result = SchemaMap.of[Foo].readFrom(m)
val lst = List("145164983", "0.01862523", "16.11681596", "21:38:57", "bid")
case class Trade0(tid: Long, price: Double, amount: Double, time: String, tpe: String)
val result2 = SchemaList.of[Trade0].readFrom(lst)
*/
So I'm getting into Scala and I have a question if what I want to do is possible, or if there's a better way.
I want to be able to take as a parameter a Map whose keys are either strings, or 0-args function returning a string. So for example
def main(args: List[String]){
f = F(
Map(
"key" -> "value",
"key2" ->(()=> {"valule2"})
)
)
println(f("key"))
}
case class F(arg: Map[String, ???]){
def apply(s: String): String = {arg(s)}
}
This obviously doesn't compile. Is there any way to do this?
In this case you can use scala.Either
scala> val map: Map[String, Either[String, () => String]] = Map.empty
map: Map[String,Either[String,() => String]] = Map()
scala> map + ("key" -> Left("value"))
res0: scala.collection.immutable.Map[String,Either[String,() => String]] = Map(key -> Left(value))
scala> res0("key")
res1: Either[String,() => String] = Left(value)
scala> map + ("key2" -> Right(() => "value2"))
res2: scala.collection.immutable.Map[String,Either[String,() => String]] = Map(key2 -> Right(<function0>))
scala> res2("key2")
res3: Either[String,() => String] = Right(<function0>)
Update
You can hide implementation from caller using something like this.
def toEither[T <: Any : Manifest](x: T): Either[String, () => String] =
x match {
case x: String => Left(x)
case x: Function0[String] if manifest[T] <:< manifest[() => String] => Right(x)
case _ => throw new IllegalArgumentException
}
The actual type of Function0 is eliminated due type erasure, but it can be verified with Manifest
scala> map + ("key" -> toEither("value"))
res1: scala.collection.immutable.Map[String,Either[String,() => String]] = Map(key -> Left(value))
scala> map + ("key2" -> toEither(() => "value2"))
res2: scala.collection.immutable.Map[String,Either[String,() => String]] = Map(key2 -> Right(<function0>))
scala> res2("key2").right.get()
res3: String = value2
scala> map + ("key2" -> toEither(() => 5))
java.lang.IllegalArgumentException
scala> map + ("key2" -> toEither(false))
java.lang.IllegalArgumentException
Update2
As #Submonoid rightly corrected me in the comments below, there is much simpler way of dealing with Either.
type StringOrFun = Either[String, () => String]
implicit def either(x: String): StringOrFun = Left(x)
implicit def either(x: () => String): StringOrFun = Right(x)
val m: Map[String, StringOrFun] = Map("key" -> "value", "key2" -> (() => "value2"))
Alternatively, you can wrap any string in a function which evaluates to that string:
implicit def delayed[A](a : A) = () => a
val m = Map[String, () => String]("a" -> "b", "c" -> (() => "d"))
This inspiration for this question came when I tried to answer this one.
Say you have a sequence of data (may be from a CSV file for instance). groupBy can be used to analyze certain aspect of the data, grouping by column or a combination of columns. For instance:
val groups0: Map[String, Array[String]] =
seq.groupBy(row => row(0) + "-" + row(4))
If I then want to create sub-groups within the groups I can do
val groups1: Map[String, Map[String, Array[String]]] =
groups0.mapValues(row => row.groupBy(_(1))
If I want to do this more one time it gets really cumbersome:
val groups2 =
groups1.mapValues(groups => groups.mapValues(row => row.groupBy(_(2)))
So here is my question given an arbitrary nesting of Map[K0, Map[K1, ..., Map[Kn, V]]], how do you write a mapValues function that takes a f: (V) => B and applies to the innermost V to return a Map[K0, Map[K1, ..., Map[Kn, B]]]?
My first instinct said that handling arbitrary nesting in a type-safe way would be impossible, but it seems that it IS possible if you define a few implicits that tell the compiler how to do it.
Essentially, the "simple" mapper tells it how to handle the plain non-nested case, while "wrappedMapper" tells it how to drill down through one Map layer:
// trait to tell us how to map inside of a container.
trait CanMapInner[WrappedV, WrappedB,V,B] {
def mapInner(in: WrappedV, f: V => B): WrappedB
}
// simple base case (no nesting involved).
implicit def getSimpleMapper[V,B] = new CanMapInner[V,B,V,B] {
def mapInner(in: V, f: (V) => B): B = f(in)
}
// drill down one level of "Map".
implicit def wrappedMapper[K,V,B,InnerV,InnerB]
(implicit innerMapper: CanMapInner[InnerV,InnerB,V,B]) =
new CanMapInner[Map[K,InnerV], Map[K,InnerB],V,B] {
def mapInner(in: Map[K, InnerV], f: (V) => B): Map[K, InnerB] =
in.mapValues(innerMapper.mapInner(_, f))
}
// the actual implementation.
def deepMapValues[K,V,B,WrappedV,WrappedB](map: Map[K,WrappedV], f: V => B)
(implicit mapper: CanMapInner[WrappedV,WrappedB,V,B]) = {
map.mapValues(inner => mapper.mapInner(inner, f))
}
// testing with a simple map
{
val initMap = Map(1 -> "Hello", 2 -> "Goodbye")
val newMap = deepMapValues(initMap, (s: String) => s.length)
println(newMap) // Map(1 -> 5, 2 -> 7)
}
// testing with a nested map
{
val initMap = Map(1 -> Map("Hi" -> "Hello"), 2 -> Map("Bye" -> "Goodbye"))
val newMap = deepMapValues(initMap, (s: String) => s.length)
println(newMap) // Map(1 -> Map(Hi -> 5), 2 -> Map(Bye -> 7))
}
Of course, in real code the pattern-matching dynamic solution is awfully tempting thanks to its simplicity. Type-safety isn't everything :)
I'm sure there is a better way using Manifest, but pattern matching seems to distinguish Seq and Map, so here it is:
object Foo {
def mapValues[A <: Map[_, _], C, D](map: A)(f: C => D): Map[_, _] = map.mapValues {
case seq: Seq[C] => seq.groupBy(f)
case innerMap: Map[_, _] => mapValues(innerMap)(f)
}
}
scala> val group0 = List("fooo", "bar", "foo") groupBy (_(0))
group0: scala.collection.immutable.Map[Char,List[java.lang.String]] = Map((f,List(fooo, foo)), (b,List(bar)))
scala> val group1 = Foo.mapValues(group0)((x: String) => x(1))
group1: scala.collection.immutable.Map[_, Any] = Map((f,Map(o -> List(fooo, foo))), (b,Map(a -> List(bar))))
scala> val group2 = Foo.mapValues(group1)((x: String) => x(2))
group2: scala.collection.immutable.Map[_, Any] = Map((f,Map(o -> Map(o -> List(fooo, foo)))), (b,Map(a -> Map(r -> List(bar)))))
Edit:
Here's a typed version using higher-kinded type.
trait NestedMapValue[Z] {
type Next[X] <: NestedMapValue[Z]
def nextValues[D](f: Z => D): Next[D]
}
trait NestedMap[Z, A, B <: NestedMapValue[Z]] extends NestedMapValue[Z] { self =>
type Next[D] = NestedMap[Z, A, B#Next[D]]
val map: Map[A, B]
def nextValues[D](f: Z => D): Next[D] = self.mapValues(f)
def mapValues[D](f: Z => D): NestedMap[Z, A, B#Next[D]] = new NestedMap[Z, A, B#Next[D]] { val map = self.map.mapValues {
case x: B => x.nextValues[D](f)
}}
override def toString = "NestedMap(%s)" format (map.toString)
}
trait Bottom[A] extends NestedMapValue[A] {
type Next[D] = NestedMap[A, D, Bottom[A]]
val seq: Seq[A]
def nextValues[D](f: A => D): Next[D] = seq match {
case seq: Seq[A] => groupBy[D](f)
}
def groupBy[D](f: A => D): Next[D] = seq match {
case seq: Seq[A] =>
new NestedMap[A, D, Bottom[A]] { val map = seq.groupBy(f).map { case (key, value) => (key, new Bottom[A] { val seq = value })} }
}
override def toString = "Bottom(%s)" format (seq.toString)
}
object Bottom {
def apply[A](aSeq: Seq[A]) = new Bottom[A] { val seq = aSeq }
}
scala> val group0 = Bottom(List("fooo", "bar", "foo")).groupBy(x => x(0))
group0: NestedMap[java.lang.String,Char,Bottom[java.lang.String]] = NestedMap(Map(f -> Bottom(List(fooo, foo)), b -> Bottom(List(bar))))
scala> val group1 = group0.mapValues(x => x(1))
group1: NestedMap[java.lang.String,Char,Bottom[java.lang.String]#Next[Char]] = NestedMap(Map(f -> NestedMap(Map(o -> Bottom(List(fooo, foo)))), b -> NestedMap(Map(a -> Bottom(List(bar))))))
scala> val group2 = group1.mapValues(x => x.size)
group2: NestedMap[java.lang.String,Char,Bottom[java.lang.String]#Next[Char]#Next[Int]] = NestedMap(Map(f -> NestedMap(Map(o -> NestedMap(Map(4 -> Bottom(List(fooo)), 3 -> Bottom(List(foo)))))), b -> NestedMap(Map(a -> NestedMap(Map(3 -> Bottom(List(bar))))))))