Hi have a string and the if format of the string is mentioned below:
val str = "{a=10, b=20, c=30}"
All the parameters inside this string is unique and separated by comma and space. Also This string always starts with '{' and ends with '}'. I want to create a Map out of this string something like below:
val values = Map("a" -> 10, "b" -> 20, "c" -> 30)
What is the most efficient way I can achieve this?
scala> val str = "{a=10, b=20, c=30}"
str: String = {a=10, b=20, c=30}
scala> val P = """.*(\w+)=(\d+).*""".r
P: scala.util.matching.Regex = .*(\w+)=(\d+).*
scala> str.split(',').map{ case P(k, v) => (k, v.toInt) }.toMap
res2: scala.collection.immutable.Map[String,Int] = Map(a -> 10, b -> 20, c -> 30)
Use regex can simply achieve this:
"(\\w+)=(\\w+)".r.findAllIn("{a=10, b=20, c=30}").matchData.map(i => {
(i.group(1), i.group(2))
}).toMap
The function you want to write is pretty easy:
def convert(str : String) : Map[String, String] = {
str.drop(1).dropRight(1).split(", ").map(_.split("=")).map(arr => arr(0)->arr(1)).toMap
}
with drop and dropRight, you remove the brackets. Then you split the String with the expression ,, which results in multiple Strings.
Than you split each of this strings, which results in arrays with two elements. Those are used to create a map.
I would do it likes this (I think regex is not needed here):
val str = "{a=10, b=20, c=30}"
val values: Map[String, Int] = str.drop(1).dropRight(1) // drop braces
.split(",") // split into key-value pairs
.map { pair =>
val Array(k, v) = pair.split("=") // split key-value pair and parse to Int
(k.trim -> v.toInt)
}.toMap
Related
I have a Scala map collection that looks something like this:
var collection = Map((A,B) -> 1)
The key is (A,B) and the value is 1.
My question: If I use collection.head._1, the result is (A,B) which is correct. But I want to extract A only, without B, as I need to compare A with some other variable. So the final result should be A stored in a different variable.
I tried to use collection.head._1(0) which results in error
Any does not take parameters
You can try:
val collection = Map(("A","B") -> 1)
collection.map{ case ((a, b),v) => a -> v}
You can use keySet to get all the keys as a Set[(String, String)] and then map it into the first element of each:
val coll: Map[(String, String), Int] =
Map(
("one", "elephant") -> 1,
("two", "elephants") -> 2,
("three", "elephants") -> 3
)
/*
val myKeys = coll.keySet.map { case (x, _) => x }
// equivalent to:
val myKeys = coll.keySet.map(tup => tup._1)
// equivalent to: */
val myKeys = coll.keySet.map(_._1) // Set(one, two, three)
I have following HOCON config:
a {
b.c.d = "val1"
d.f.g = "val2"
}
HOCON represents paths "b.c.d" and "d.f.g" as objects. So, I would like to have a reader, which reads these configs as Map[String, String], ex:
Map("b.c.d" -> "val1", "d.f.g" -> "val2")
I've created a reader and trying to do it recursively:
import scala.collection.mutable.{Map => MutableMap}
private implicit val mapReader: ConfigReader[Map[String, String]] = ConfigReader.fromCursor(cur => {
def concat(prefix: String, key: String): String = if (prefix.nonEmpty) s"$prefix.$key" else key
def toMap(): Map[String, String] = {
val acc = MutableMap[String, String]()
def go(
cur: ConfigCursor,
prefix: String = EMPTY,
acc: MutableMap[String, String]
): Result[Map[String, Object]] = {
cur.fluent.mapObject { obj =>
obj.value.valueType() match {
case ConfigValueType.OBJECT => go(obj, concat(prefix, obj.pathElems.head), acc)
case ConfigValueType.STRING =>
acc += (concat(prefix, obj.pathElems.head) -> obj.asString.right.getOrElse(EMPTY))
}
obj.asRight
}
}
go(cur, acc = acc)
acc.toMap
}
toMap().asRight
})
It gives me the correct result but is there a way to avoid MutableMap here?
P.S. Also, I would like to keep implementation by "pureconfig" reader.
The solution given by Ivan Stanislavciuc isn't ideal. If the parsed config object contains values other than strings or objects, you don't get an error message (as you would expect) but instead some very strange output. For instance, if you parse a typesafe config document like this
"a":[1]
The resulting value will look like this:
Map(a -> [
# String: 1
1
])
And even if the input only contains objects and strings, it doesn't work correctly, because it erroneously adds double quotes around all the string values.
So I gave this a shot myself and came up with a recursive solution that reports an error for things like lists or null and doesn't add quotes that shouldn't be there.
implicit val reader: ConfigReader[Map[String, String]] = {
implicit val r: ConfigReader[String => Map[String, String]] =
ConfigReader[String]
.map(v => (prefix: String) => Map(prefix -> v))
.orElse { reader.map { v =>
(prefix: String) => v.map { case (k, v2) => s"$prefix.$k" -> v2 }
}}
ConfigReader[Map[String, String => Map[String, String]]].map {
_.flatMap { case (prefix, v) => v(prefix) }
}
}
Note that my solution doesn't mention ConfigValue or ConfigReader.Result at all. It only takes existing ConfigReader objects and combines them with combinators like map and orElse. This is, generally speaking, the best way to write ConfigReaders: don't start from scratch with methods like ConfigReader.fromFunction, use existing readers and combine them.
It seems a bit surprising at first that the above code works at all, because I'm using reader within its own definition. But it works because the orElse method takes its argument by name and not by value.
You can do the same without using recursion. Use method entrySet as following
import scala.jdk.CollectionConverters._
val hocon =
"""
|a {
| b.c.d = "val1"
| d.f.g = val2
|}""".stripMargin
val config = ConfigFactory.load(ConfigFactory.parseString(hocon))
val innerConfig = config.getConfig("a")
val map = innerConfig
.entrySet()
.asScala
.map { entry =>
entry.getKey -> entry.getValue.render()
}
.toMap
println(map)
Produces
Map(b.c.d -> "val1", d.f.g -> "val2")
With given knowledge, it's possible to define a pureconfig.ConfigReader that reads Map[String, String] as following
implicit val reader: ConfigReader[Map[String, String]] = ConfigReader.fromFunction {
case co: ConfigObject =>
Right(
co.toConfig
.entrySet()
.asScala
.map { entry =>
entry.getKey -> entry.getValue.render()
}
.toMap
)
case value =>
//Handle error case
Left(
ConfigReaderFailures(
ThrowableFailure(
new RuntimeException("cannot be mapped to map of string -> string"),
Option(value.origin())
)
)
)
}
I did not want to write custom readers to get a mapping of key value pairs. I instead changed my internal data type from a map to list of pairs (I am using kotlin), and then I can easily change that to a map at some later internal stage if I need to. My HOCON was then able to look like this.
additionalProperties = [
{first = "sasl.mechanism", second = "PLAIN"},
{first = "security.protocol", second = "SASL_SSL"},
]
additionalProducerProperties = [
{first = "acks", second = "all"},
]
Not the best for humans... but I prefer it to having to build custom parsing components.
Say i have case classes like this.
case class someClass0(content: someClass1)
case class someClass1(someContent: Option[Map[String, someClass2]])
case class someClass2(someKey: Array[Int])
I need to delete items in Map(which is immutable) by values.
This values i get through iteration.
val keys_to_remove = new ListBuffer[String]()
val keys_to_keep: List[Int] = List(100, 200)
for (x <- keys_to_keep) {
content.someContent.get.foreach {
case (key: String, value: someClass2) => {
if (!value.someKey.contains(x)) {
keys_to_remove.append(key)
}
}
}
}
So, how to keep all the structure, and delete only needed items by key?
I was trying to change type of Map like
content.someContent.map(_.to(collection.mutable.Map))
But content.someContent.get.remove(key) is not working.
What am i doing wrong?
You don't need mutability for that.
val keys_to_keep: List[String] = List("a", "b")
val res = content.someContent.map(
_.filterKeys(k => !keys_to_keep.contains(k))
)
filterKeys filters a Map by testing each entries' key against a condition.
Of course, it is important to remember that you can't test contains on a List[Int] against Strings, as the result will always be false.
Furthermore, try looking up style-guides for Scala:
Classes are usually named in upper camel case
Values and variables are usually named in lower camel case
Try it out
You can do it using - operator and foldLeft on keys to remove.
you are using get for get value, if you want do it safety, you need to use:
content.someContent.map(immutableMap =>
keys_to_remove.foldLeft(immutableMap){
(map, key) =>
map - key
}).getOrElse(Map.empty[String, SomeClass2])
this works like in this example:
import scala.collection.mutable.ListBuffer
val immutableMap = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4)
val keys_to_remove: ListBuffer[String] = ListBuffer("b", "d")
println(immutableMap) // Map(a -> 1, b -> 2, c -> 3, d -> 4)
val mapWithoutKeys = keys_to_remove.foldLeft(immutableMap){
(map, key) =>
map - key
}
println(mapWithoutKeys) //Map(a -> 1, c -> 3)
Here's how you can do it:
val optionalMap = someClass0.content.someContent.map {
contentMap => contentMap - keyToBeRemoved
}
val originalStructure = someClass0.copy(content = SomeClass1(optionalMap))
Here's the Scastie
This will remove all keys and keep structure
val someClass0_copy = someClass0.copy(content = Content(someContent = someClass0.content. someContent.map(_.removedAll(keysToRemove)))
in my main program i receive inputs like -
key1=value1 key2=value2
Now what I want is to create a map out of it. I know the imperative way of doing this where I would get Array[String] that can be foreach and then split by "=" and then key and value can be used to form a Map.
is there a good functional and readable way to achieve this?
Also It will be great if I can avoid mutable Map and I want to avoid initial Dummy value initialization.
def initialize(strings: Array[String]): Unit = {
val m = collection.mutable.Map("dummy" -> "dummyval")
strings.foreach(
s => {
val keyVal:Array[String] = s.split("=")
m += keyVal(0) -> keyVal(1)
})
println(m)
}
you can just use toMap().
However, converting from array to tuple is not quite trivial:
How to convert an Array to a Tuple?
scala> val ar = Array("key1=value1","key2=value2")
ar: Array[String] = Array(key1=value1, key2=value2)
scala> ar.collect(_.split("=") match { case Array(x,y) => (x,y)}).toMap
res10: scala.collection.immutable.Map[String,String] = Map(key1 -> value1, key2 -> value2)
Maybe you have to call Function.unlift for intellij
val r = ar.collect(Function.unlift(_.split("=") match { case Array(x, y) => Some(x, y)})).toMap
similar to above but using only 'map'
ar.map(_.split("=")).map(a=>(a(0), a(1))).toMap
You can use Scopt to do the command line argument parsing in a neat way.
if i have a map and want to build up a string from iterating over it, is there a way to have the final string be a result of an expression instead of defining a variable and modifying that inside a loop?
instead of this
val myMap = Map("1" -> "2", "3"->"4")
var s = ""
myMap foreach s += ...
i'd rather it be
var s = myMap something ...
I'd just map and mkString. For example:
val s = (
Map("1" -> "2", "3"->"4")
map { case (key, value) => "Key: %s\nValue: %s" format (key, value) }
mkString ("", "\n", "\n")
)
As for Daniel's answer, but with a couple of optimisations and my own formatting preferences:
val myMap = Map("1" -> "2", "3"->"4")
val s = myMap.view map {
case (key, value) => "Key: " + key + "\nValue: " + value
} mkString ("", "\n", "\n")
The optimisations:
By first creating a view of the map, I avoid creating an intermediate collection
On profiling, direct String concatenation is faster than String.format
You can do this with a fold:
scala> myMap.foldLeft("") { (s: String, pair: (String, String)) =>
| s + pair._1 + pair._2
| }
res0: java.lang.String = 1234
I'm fairly new to Scala, but you can try reduceLeft. It goes accumulating a partial value (the string being joined with every element). For example, if you want the keys (or the values) joined in a string, just do:
val s = myMap.keys.reduceLeft( (e, s) => e + s)
This results in "13"
This works also fine if you don't bother about your own formatting:
scala> Map("1" -> "2", "3"->"4").mkString(", ")
res6: String = 1 -> 2, 3 -> 4