Scala: Idiomatic way to convert Map[String, String] to String - scala

I have a map that contains a few HTTP parameters that will be sent to an API.
val sortedParameters: SortedMap[String, String] = SortedMap(
"oauth_nonce" -> nonce,
"oauth_callback" -> callbackURL,
"oauth_signature_method" -> signatureMethod,
"oauth_consumer_key" -> consumerKey
)
The above parameters have to be URL encoded and concatenated in the form key1=value1&key2=value2 etc.
What would be the best idiomatic way to achieve this in Scala?

Pretty much the same as the other answer but including encoding.
scala> import scala.collection.immutable.SortedMap
import scala.collection.immutable.SortedMap
scala> val enc = (s: String) => java.net.URLEncoder.encode(s, "utf-8")
enc: String => String = $$Lambda$1060/160696258#6c796cc1
scala> val sortedMap = SortedMap("a" -> "b&&c means both b and c are true", "c" -> "d=1")
sortedMap: scala.collection.immutable.SortedMap[String,String] = Map(a -> b&&c means both b and c are true, c -> d=1)
scala> sortedMap.map(kv => s"${enc(kv._1)}=${enc(kv._2)}").mkString("&")
res2: String = a=b%26%26c+means+both+b+and+c+are+true&c=d%3D1
EDIT:
And a more idiomatic destructuring from a comment:
sortedMap.map({ case (k, v) => s"${enc(k)}=${enc(v)}" }).mkString("&")
res2: String = a=b%26%26c+means+both+b+and+c+are+true&c=d%3D1

.map() on each elem to create k=v pattern then concat them using TraversableOnce#foldLeft(z)(op) or TraversableOnce#mkString(separator)
example,
scala> import scala.collection.SortedMap
import scala.collection.SortedMap
scala> val sortedParameters = SortedMap("a" -> 1, "b" -> 2, "c" -> 3)
sortedParameters: scala.collection.SortedMap[String,Int] = Map(a -> 1, b -> 2, c -> 3)
using mkString,
scala> sortedParameters.map(kv => kv._1 + "=" + kv._2).mkString("&")
res1: String = a=1&b=2&c=3
using foldLeft,
scala> sortedParameters.map(kv => kv._1 + "=" + kv._2)
.foldLeft(new String)((a, b) => { if(a.equals("")) b else a + "&" + b})
res2: String = a=1&b=2&c=3

Related

Convert Seq[Option[Map[String, Any]]] to Option[Map[String, Any]]

How to Convert Seq[Option[Map[String, Any]]] to Option[Map[String, Any]]
So skip any option that is None, and keep the valid Maps and merge them. If every option is None, then the final option should be None as well.
A flatMap along with groupMapReduce, followed by an Option filter should do the job:
val listOfMaps: List[Option[Map[String, Any]]] =
List(Some(Map("a"->"p", "b"->2)), None, Some(Map("a"->"q", "c"->"r")))
val mergedMap = listOfMaps.
flatMap(_.getOrElse(Map.empty[String, Any])).
groupMapReduce(_._1)(t => List[Any](t._2))(_ ::: _)
// mergedMap: Map[String, List[Any]] =
// Map("a" -> List("p", "q"), "b" -> List(2), "c" -> List("r"))
Option(mergedMap).filter(_.nonEmpty)
// res1: Option[Map[String, List[Any]]] =
// Some(Map("a" -> List("p", "q"), "b" -> List(2), "c" -> List("r")))
A few notes:
groupMapReduce is available only on Scala 2.13+.
If you must stick to Seq instead of List, simply replace method ::: with ++ in groupMapReduce.
It is assumed that merging of the Maps means aggregating the Map values of a common key into a List. Replace groupMapReduce with toMap if keeping only one of the Map values of a common key is wanted instead.
This solution treats Some(Map(.empty[String, Any])) the same as None.
This is a one-line solution
val in: Seq[Option[Map[String, Any]]] = ???
val out: Option[Map[String, Any]] =
in.flatten.headOption.map(_ => in.flatten.reduce(_ ++ _))
The in.flatten.headOption is a simple way of getting Some if at least one of the elements is Some or None if they are all None.
The reduce just combines all the Maps into one.
This can clearly be optimised by avoiding the duplicate in.flatten call.
Here is an example with flatten,fold, ++ and a match at the end to provide Some or None.
baz.scala
package baz
object baz {
// fixtures
val x0 = Seq[Option[Map[String, Any]]]()
val x1 = Seq[Option[Map[String, Any]]](None)
val x2 = Seq[Option[Map[String, Any]]](Some(Map("a" -> 1, "b" -> "two")))
val x3 = Seq[Option[Map[String, Any]]](Some(Map("a" -> 1, "b" -> "two")), Some(Map("c" -> 3.0)), None)
def f(x: Seq[Option[Map[String, Any]]]) =
x.flatten.fold(Map[String, Any]())((a,b) => a ++ b) match { case m if m.isEmpty => None case m => Some(m) }
}
Sample run
bash-3.2$ scalac baz.scala && scala -classpath .
Welcome to Scala 2.13.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_66).
Type in expressions for evaluation. Or try :help.
scala> import baz.baz._
import baz.baz._
scala> x0 -> f(x0)
res0: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(),None)
scala> x1 -> f(x1)
res1: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(None),None)
scala> x2 -> f(x2)
res2: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(Some(Map(a -> 1, b -> two))),Some(Map(a -> 1, b -> two)))
scala> x3 -> f(x3)
res3: (Seq[Option[Map[String,Any]]], Option[Map[String,Any]]) = (List(Some(Map(a -> 1, b -> two)), Some(Map(c -> 3.0)), None),Some(Map(a -> 1, b -> two, c -> 3.0)))
scala> :quit

Replace mapValues for ListMap

Scala immutable Map has a member mapValues, which allows to perform mapping on values only (not keys). When using immutable ListMap instead, this member is inherited, however it is not overridden, therefore it still returns a Map, not ListMap.
Is there some simple way how to implement mapValues for a ListMap?
In the following fragment, I want the return type to be a ListMap, not Map:
import scala.collection.immutable.ListMap
val lm = ListMap(1 -> "1", 0 -> "0", 2 -> "2")
lm.mapValues ( v => v+v )
How about:
def listMapValues[K,V1,V2](lm:ListMap[K,V1], f: V1 => V2) = lm.map { case (k,v1) => (k,f(v1))}
You can then use it like:
scala> listMapValues(lm, v => v + v)
res16: scala.collection.immutable.ListMap[Int,String] = Map(1 -> 11, 0 -> 00, 2 -> 22)
If you want to use it as infix method, just need to declare it an implicit class:
implicit class ListMapOps[K,V1](lm: ListMap[K,V1]) {
def listMapValues[V2](f: V1 => V2)= lm.map { case (k,v1) => (k,f(v1))}
}
scala> lm.listMapValues( v => v + v )
res17: scala.collection.immutable.ListMap[Int,String] = Map(1 -> 11, 0 -> 00, 2 -> 22)
You can do like this.
ListMap[Int, String]().empty ++ lm.mapValues(v => v + v)
Your code :
import scala.collection.immutable.ListMap
val lm: ListMap[Int, String] = ListMap(1 -> "1", 0 -> "0", 2 -> "2")
val result: ListMap[Int, String] = ListMap[Int, String]().empty ++ lm.mapValues(v => v + v)
Output at REPL :
import scala.collection.immutable.ListMap
lm: scala.collection.immutable.ListMap[Int,String] = Map(1 -> 1, 0 -> 0, 2 -> 2)
result: scala.collection.immutable.ListMap[Int,String] = Map(1 -> 11, 0 -> 00, 2 -> 22)

Better way to create state transformation for adding set elements to Map[K, Set[V]]

I have Map[K, Set[V]] and I am using Scalaz Lenses and State to add elements to it.
So far, I see myself doing this repeatedly:
myMapLens.member(key) %= {
case Some(vals) => Some(vals + newValue)
case None => Some(Set(newValue))
}
Is there a better way to do this using Scalaz ? Casting my value set into Some(...) every time seems wasteful.
Specifically, is there a way to compose Scalaz MapLens and SetLens to achieve this ?
You can write an adapter to "flatten" the Option:
import scalaz._, Scalaz._
def noneZero[A: Monoid]: Lens[Option[A], A] = Lens.lensg(_ => Some(_), _.orZero)
This is a little more generic than you need, but has the same behavior for your use case:
val myMapLens = Lens.lensId[Map[String, Set[Int]]]
val myLens = myMapLens.member("foo").andThen(noneZero).contains(1)
You could of course use any of the other methods on SetLens—contains just makes for a nice demonstration:
scala> myLens.get(Map("foo" -> Set(1)))
res0: Boolean = true
scala> myLens.get(Map("bar" -> Set(1)))
res1: Boolean = false
scala> myLens.set(Map("foo" -> Set(2)), true)
res2: Map[String,Set[Int]] = Map(foo -> Set(2, 1))
scala> myLens.set(Map("bar" -> Set(2)), true)
res3: Map[String,Set[Int]] = Map(bar -> Set(2), foo -> Set(1))
scala> myLens.set(Map("foo" -> Set(1)), false)
res4: Map[String,Set[Int]] = Map(foo -> Set())
The following is arguably a slightly more principled way to write the adapter:
def noneZero[A: Monoid: Equal]: Lens[Option[A], A] = Lens.lensg(
_ => a => a.ifEmpty[Option[A]](none)(some(a)),
_.orZero
)
This behaves the same except that unsetting the last value in a set removes it from the map:
scala> myLens.set(Map("foo" -> Set(1)), false)
res5: Map[String,Set[Int]] = Map()
This may not be what you want, though.
Vanilla
myMap + (key -> myMap.get(key).fold(Set(newValue))(_ + newValue))
seems easier.
So does writing an extension method, and there it's worth a little extra work to avoid needless reconstruction of anything:
implicit class MapsToSetsCanAdd[K,V](map: Map[K, Set[V]]) {
def setAdd(key: K, value: V) = map.get(key) match {
case Some(set) => if (set contains value) map else map + (key -> (set + value))
case None => map + (key -> Set(value))
}
}
Now you can merrily myMap setAdd (key, newValue).

Get value with the lowest key value from Map[Int, String]

Say I have a map: Map[Int, String]. How would I get the value [String] with the lowest key [Int]. I've been trying to implement this functionally, but just can't figure out how to do this.
The following code will get you a value with a lowest key (ignoring some corner cases).
def lowestKeyMember[A](m: Map[Int,A]): A = m(m.keys.min)
This will break ties arbitrarily and throw on an empty map. If you need to do this operation frequently and/or on large maps, you should look into SortedMap.
Maps are not normally sorted. You could however use a SortedMap, then the map will be sorted and the first value will be the head. All you need to do is retrieve the head.
map.head()
Come on, people! "Functionally" is code word for "folding".
scala> val m = Map(1->"eins",2->"zwei",3->"drei")
m: scala.collection.immutable.Map[Int,String] = Map(1 -> eins, 2 -> zwei, 3 -> drei)
scala> m.foldLeft(Int.MaxValue -> "") { case (min,p) => if (min._1 <= p._1) min else p }
res0: (Int, String) = (1,eins)
But an 8-char operator?
Let's see, is that enough parens? Don't tell me -> is like - and /: is like /.
scala> (Int.MaxValue -> "" /: m) { case (min,p) => if (min._1 <= p._1) min else p }
<console>:9: error: missing arguments for method /: in trait TraversableOnce;
follow this method with `_' if you want to treat it as a partially applied function
(Int.MaxValue -> "" /: m) { case (min,p) => if (min._1 <= p._1) min else p }
^
Oh, well, OK.
scala> ((Int.MaxValue -> "") /: m) { case (min,p) => if (min._1 <= p._1) min else p }
res2: (Int, String) = (1,eins)
Or,
scala> import math.Ordering.Implicits._
import math.Ordering.Implicits._
scala> ((Int.MaxValue -> "") /: m) { case (min,p) if min <= p => min case (_, p) => p }
res5: (Int, String) = (1,eins)
A variant of the _.keys.min solution that works with Options (i.e. will not throw on an empty map):
scala> val a : Map[Int, String]=Map(1 -> "1", 2 -> "2")
a: Map[Int,String] = Map(1 -> 1, 2 -> 2)
scala> val b : Map[Int, String]=Map()
b: Map[Int,String] = Map()
scala> def valueForMinKey[K,V](a : Map[K,V])(implicit cmp : Ordering[K]) = a.keys.reduceOption(cmp.min(_, _)).map(a(_))
valueForMinKey: [K, V](a: Map[K,V])(implicit cmp: Ordering[K])Option[V]
scala> valueForMinKey(a)
res27: Option[String] = Some(1)
scala> valueForMinKey(b)
res28: Option[String] = None
In this example, the implicit parameter cmp will be satisfied by Ordering.Int. The example will work with any Map where the keys can be ordered (and a matching implict can be found by the compiler).

How do I parse a x-www-url-encoded string into a Map[String, String] using Lift?

From Lift, I'm getting a string of the form
TOKEN=EC%2d454178600K772032D&TIMESTAMP=2011%2d06%2d29T13%3a10%3a58Z&CORRELATIONID=cbd56e97cad38&ACK=Success&VERSION=64&BUILD=1936884
from the response of an HTTP request.
Although it's probably ultra-trivial, I can't find the Lift function that parses this into a nice Map[String, String]. Any help?
From Lift's Req.scala:
// calculate the query parameters
lazy val queryStringParam: (List[String], Map[String, List[String]]) = {
val params: List[(String, String)] =
for {
queryString <- request.queryString.toList
nameVal <- queryString.split("&").toList.map(_.trim).filter(_.length > 0)
(name, value) <- nameVal.split("=").toList match {
case Nil => Empty
case n :: v :: _ => Full((urlDecode(n), urlDecode(v)))
case n :: _ => Full((urlDecode(n), ""))
}} yield (name, value)
val names: List[String] = params.map(_._1).distinct
val nvp: Map[String, List[String]] = params.foldLeft(Map[String, List[String]]()) {
case (map, (name, value)) => map + (name -> (map.getOrElse(name, Nil) ::: List(value)))
}
(names, nvp)
}
I haven't seen any Lift's implementation for that. You can achieve this with something like this:
val input = "TOKEN=EC%2d454178600K772032D&TIMESTAMP=2011%2d06%2d29T13%3a10%3a58Z&CORRELATIONID=cbd56e97cad38&ACK=Success&VERSION=64&BUILD=1936884"
val res = input.split('&') map { str =>
val pair = str.split('=')
(pair(0) -> pair(1))
} toMap
note: it assumes, that you have a well-formed string. In your code you should probably check if the string is ok.
I put together a small Scala library to help do this: https://github.com/theon/scala-uri
You can parse a uri and get the parameters into a Map[String,List[String]] like so:
val uri = parseUri("http://example.com?one=1&two=2").query.params
It also has a DSL for building URLs with query strings:
val uri = "http://example.com" ? ("one" -> 1) & ("two" -> 2)
scala> val queryParams = "a=4&b=5"
scala> queryParams.split("&").toList.map(_.trim).filter(_.length > 0).flatMap(x => {
val s = x.split('=')
Map[String, String](s(0) -> s(1))
}).toMap[String, String]
res0: scala.collection.immutable.Map[String,String] = Map(a -> 4, b -> 5)