Copy method(Scala Tuple) not working as desired - scala

Going by the answer given here
You can't reassign tuple values. They're intentionally immutable: once you have created a tuple, you can be confident that it will never change. This is very useful for writing correct code!
But what if you want a different tuple? That's where the copy method comes in:
val tuple = (1, "test")
val another = tuple.copy(_2 = "new")
When I run the below code
var TupleOne=("One", 2, true)
println(TupleOne._1) //Gives One(As desired)
var TupleTwo=("Two", TupleOne.copy(_1 = "new"),false)
println(TupleTwo._2) //Gives (new,2,true)->weird
As per my understanding the second tuple should be ("Two","new",false) and printing TupleTwo._2 should give "new"
Why the behavior is different here?

TupleOne.copy(_1 = "new") is ("One", "new", true). So when you put it into a tuple, TupleTwo is ("Two", ("One", "new", true), false). And TupleTwo._2 is of course ("One", "new", true) again. (You don't get the quotes " when they are printed, because that's how toString on String is defined.)

1) You don't even need .copy based on your example.
you have
var TupleOne=("One", 2, true)
you are expecting
var TupleTwo = ("Two","new",false)
Theres nothing to be copied tupleOne.
The other thing is .copy returns type of instance that you are copying from, which is Tuple too
Example to copy first element but mutate second element.
scala> val tupleOne = ("copyme", "dont-copy-me")
tupleOne: (String, String) = (copyme,dont-copy-me)
scala> val tupleTwo = tupleOne.copy(_2 = "I'm new")
tupleTwo: (String, String) = (copyme,I'm new)
2) Lets go to your example,
scala> val TupleOne=("One",2,true)
TupleOne: (String, Int, Boolean) = (One,2,true)
To get your expected result, you have to alter each element of TupleOne as below which makes no sense because you are copying nothing.
scala> TupleOne.copy(_1= "Two", _2 = "new", _3= false)
res3: (String, String, Boolean) = (Two,new,false)
3) Again, since Tuple.copy returns Tuple, your TupleTwo is Tuple inside Tuple. (#2 is what you probably looking for)
scala> var TupleOne=("One",2,true)
TupleOne: (String, Int, Boolean) = (One,2,true)
scala> var TupleTwo=("Two",TupleOne.copy(_1 = "new"),false)
TupleTwo: (String, (String, Int, Boolean), Boolean) = (Two,(new,2,true),false)

Related

scala - Array("one", "two").mkString(":") defined in a method return WrappedArray, not a String

scala> def joinWith(separator: String, values: String*): String = {
| Array(values).mkString(separator)
| }
joinWith: (separator: String, values: String*)String
scala> joinWith(":", "one", "two")
res0: String = WrappedArray(one, two) // shouldn't we get a String here?
scala> Array("one", "two").mkString(":")
res1: String = one:two // this works as expected but only like this
scala>
I expected a String returned, but I see a WrappedArray. Why?
How should the function be defined, if I want a String returned?
The only problem I see with your joinWith function is the way you convert variable arguments to an array. Result of Array(values) has a type Array[Seq[String]] - I doubt this is what you want. This is not necessary at all. The arguments values are already passed as Seq to you. Did you perhaps intend to write values.mkString(separator)?
The WrappedArray text you see is the result of mkString converting the inner Array with a value of Array("one", "two")to a string. You are not getting a value with a type WrappedArray (this would not even be possible, as you have explicitly typed your function to return a String), you get a string with a value WrappedArray(...)
This is working for me:
def joinWith(separator: String, values: String*): String =
Array(values:_*).mkString(separator)
Here is the REPL:
scala> def joinWith(separator: String, values: String*): String = Array(values:_*).mkString(separator)
joinWith: (separator: String, values: String*)String
scala> joinWith(":", "a", "s")
res7: String = a:s
I think the problem was that you need to specify the _* to the Array.
As Suma says, if you do not specify the _*, you have the type Array[Seq[String]] when you create the Array.

Modify a position in a (String, String) variable in Scala

I have tuple separated by a coma that looks like this:
("TRN_KEY", "88.330000;1;2")
I would like to add some more info to the second position.
For example:
I would like to add ;99;99 to the 88.330000;1;2 so that at the end it would look like:
(TRN_KEY, 88.330000;1;2;99;99)
One way is to de-compose your tuple and concat the additional string to the second element:
object MyObject {
val (first, second) = ("TRN_KEY","88.330000;1;2")
(first, second + ";3;4"))
}
Which yields:
res0: (String, String) = (TRN_KEY,88.330000;1;2;3;4)
Another way to go is copy to tuple with the new value using Tuple2.copy, as tuples are immutable by design.
You can not modify the data in place as Tuple2 is immutable.
An option would be to have a var and then use the copy method.
In Scala due to structural sharing this is a rather cheap and fast operation.
scala> var tup = ("TRN_KEY","88.330000;1;2")
tup: (String, String) = (TRN_KEY,88.330000;1;2)
scala> tup = tup.copy(_2 = tup._2 + "data")
tup: (String, String) = (TRN_KEY,88.330000;1;2data)
Here is a simple function that gets the job done. It takes a tuple and appends a string to the second element of the tuple.
def appendTup(tup:(String, String))(append:String):(String,String) = {
(tup._1, tup._2 + append)
}
Here is some code using it
val tup = ("TRN_KEY", "88.330000;1;2")
val tup2 = appendTup(tup)(";99;99")
println(tup2)
Here is my output
(TRN_KEY,88.330000;1;2;99;99)
If you really want to make it mutable you could use a case class such as:
case class newTup(col1: String, var col2: String)
val rec1 = newTup("TRN_KEY", "88.330000;1;2")
rec1.col2 = rec1.col2 + ";99;99"
rec1
res3: newTup = newTup(TRN_KEY,88.330000;1;2;99;99)
But, as mentioned above, it would be better to use .copy

How do I append to a listbuffer which is a value of a mutable map in Scala?

val mymap= collection.mutable.Map.empty[String,Seq[String]]
mymap("key") = collection.mutable.ListBuffer("a","b")
mymap.get("key") += "c"
The last line to append to the list buffer is giving error. How the append can be done ?
When you run the code in the scala console:
→$scala
scala> val mymap= collection.mutable.Map.empty[String,Seq[String]]
mymap: scala.collection.mutable.Map[String,Seq[String]] = Map()
scala> mymap("key") = collection.mutable.ListBuffer("a","b")
scala> mymap.get("key")
res1: Option[Seq[String]] = Some(ListBuffer(a, b))
You'll see that mymap.get("key") is an optional type. You can't add a string to the optional type.
Additionally, since you typed mymap to Seq[String], Seq[String] does not have a += operator taking in a String.
The following works:
val mymap= collection.mutable.Map.empty[String,collection.mutable.ListBuffer[String]]
mymap("key") = collection.mutable.ListBuffer("a","b")
mymap.get("key").map(_ += "c")
Using the .map function will take advantage of the optional type and prevent noSuchElementException as Łukasz noted.
To deal with your problems one at a time:
Map.get returns an Option[T] and Option does not provide a += or + method.
Even if you use Map.apply (mymap("key")) the return type of apply will be V (in this case Seq) regardless of what the actual concrete type is (Vector, List, Set, etc.). Seq does not provide a += method, and its + method expects another Seq.
Given that, to get what you want you need to declare the type of the Map to be a mutable type:
import collection.mutable.ListBuffer
val mymap= collection.mutable.Map.empty[String,ListBuffer[String]]
mymap("key") = ListBuffer("a","b")
mymap("key") += "c"
will work as you expect it to.
If you really want to have immutable value, then something like this should also work:
val mymap= collection.mutable.Map.empty[String,Seq[String]]
mymap("key") = Vector("a","b")
val oldValue = mymap.get("key").getOrElse(Vector[String]())
mymap("key") = oldValue :+ "c"
I used Vector here, because adding elements to the end of List is unefficient by design.

Scala: Default Value for a Map of Tuples

Look at the following Map:
scala> val v = Map("id" -> ("_id", "$oid")).withDefault(identity)
v: scala.collection.immutable.Map[String,java.io.Serializable] = Map(id -> (_id,$oid))
The compiler generates a Map[String,java.io.Serializable] and the value of id can be retrieved like this:
scala> v("id")
res37: java.io.Serializable = (_id,$oid)
Now, if I try to access an element that does not exist like this...
scala> v("idx")
res45: java.io.Serializable = idx
... then as expected I get back the key itself... but how do I get back a tuple with the key itself and an empty string like this?
scala> v("idx")
resXX: java.io.Serializable = (idx,"")
I always need to get back a tuple, regardless of whether or not the element exists.
Thanks.
Instead of .withDefault(identity) you can use
val v = Map("id" -> ("_id", "$oid")).withDefault(x => (x, ""))
withDefault takes as a parameter a function that will create the default value when needed.
This will also change the return type from useless Serializable to more useful (String, String).

In Scala, how can I reassign tuple values?

I'm trying to do something like the following
var tuple = (1, "test")
tuple._2 = "new"
However this does not compile it complains about val
You can't reassign tuple values. They're intentionally immutable: once you have created a tuple, you can be confident that it will never change. This is very useful for writing correct code!
But what if you want a different tuple? That's where the copy method comes in:
val tuple = (1, "test")
val another = tuple.copy(_2 = "new")
or if you really want to use a var to contain the tuple:
var tuple = (1, "test")
tuple = tuple.copy(_2 = "new")
Alternatively, if you really, really want your values to change individually, you can use a case class instead (probably with an implicit conversion so you can get a tuple when you need it):
case class Doublet[A,B](var _1: A, var _2: B) {}
implicit def doublet_to_tuple[A,B](db: Doublet[A,B]) = (db._1, db._2)
val doublet = Doublet(1, "test")
doublet._2 = "new"
You can wrapper the component(s) you need to modify in a case class with a var member, like:
case class Ref[A](var value: A)
var tuple = (Ref(1), "test")
tuple._1.value = 2
println(tuple._1.value) // -> 2