Updating existing String elements of an ArrayBuffer in Scala - scala

I have an ArrayBuffer of Strings as below:
var myList = ArrayBuffer[String]()
myList += "abc"
myList += "def"
Now, I'm trying to update the String in the ArrayBuffer based on some condition:
for(item <- myList){
if(some condition){
item = "updatedstring"
}
}
When I try to do this, I get an error saying val cannot be reassigned. Why do I get this error even though I have declared myList as a var? If I cannot update it this way, how else can I update the elements when iterating through the ArrayBuffer? I'm new to Scala, so I apologize if I got this all wrong.

The first thing I would point out is that item is not the same as myList - it's an element within myList, and the way that Scala iteration works, it's a val. There are various reasons for this related to immutability which I won't get into here.
I would recommend this instead:
val myNewList = myList.map(originalString =>
if (someCondition) "xyz"
else originalString
)
Then, if you feel so inclined, you could do myList = myNewList (or just forgo having a myNewList entirely and do myList = myList.map(...)).

In the for loop item is a local val. Therefore, it cannot be changed.
You could:
either iterate through the array and update each item
myList.zipWithIndex foreach { case (item, index) if (condition) => myList.update(index, "updated") }
or create a new ArrayBuffer
myList = (0 until myList.length).map { index =>
val item = myList(index)
if (condition) "updated" else item
}

Related

unable to convert a java.util.List into Scala list

I want that the if block returns Right(List[PracticeQuestionTags]) but I am not able to do so. The if/else returns Either
//I get java.util.List[Result]
val resultList:java.util.List[Result] = transaction.scan(scan);
if(resultList.isEmpty == false){
val listIterator = resultList.listIterator()
val finalList:List[PracticeQuestionTag] = List()
//this returns Unit. How do I make it return List[PracticeQuestionTags]
val answer = while(listIterator.hasNext){
val result = listIterator.next()
val convertedResult:PracticeQuestionTag = rowToModel(result) //rowToModel takes Result and converts it into PracticeQuestionTag
finalList ++ List(convertedResult) //Add to List. I assumed that the while will return List[PracticeQuestionTag] because it is the last statement of the block but the while returns Unit
}
Right(answer) //answer is Unit, The block is returning Right[Nothing,Unit] :(
} else {Left(Error)}
Change the java.util.List list to a Scala List as soon as possible. Then you can handle it in Scala fashion.
import scala.jdk.CollectionConverters._
val resultList = transaction.scan(scan).asScala.toList
Either.cond( resultList.nonEmpty
, resultList.map(rowToModel(_))
, new Error)
Your finalList: List[PracticeQuestionTag] = List() is immutable scala list. So you can not change it, meaning there is no way to add, remove or do change to this list.
One way to achieve this is by using scala functional approach. Another is using a mutable list, then adding to that and that list can be final value of if expression.
Also, a while expression always evaluates to Unit, it will never have any value. You can use while to create your answer and then return it seperately.
val resultList: java.util.List[Result] = transaction.scan(scan)
if (resultList.isEmpty) {
Left(Error)
}
else {
val listIterator = resultList.listIterator()
val listBuffer: scala.collection.mutable.ListBuffer[PracticeQuestionTag] =
scala.collection.mutable.ListBuffer()
while (listIterator.hasNext) {
val result = listIterator.next()
val convertedResult: PracticeQuestionTag = rowToModel(result)
listBuffer.append(convertedResult)
}
Right(listBuffer.toList)
}

Concatenate multiple list

I would like to know how to concatenate several list using a loop. Here is an example of what I'm trying to do:
object MyObj {
var objs = Set (
MyObj("MyObj1", anotherObjList),
MyObj("MyObj2", anotherObjList)
)
val list = List.empty[AnotherObj]
def findAll = for (obj <- objs) List.concat(list, obj.anotherObjList)
}
I would like the function findAll to concatenate lists from object of the set objs.
Try this:
objs.flatMap(_.anotherObjList)
It doesn't use a for, but that's probably the most concise and readable way to do it in Scala.
Use reduce
Something like this:
objs.reduce((a,b) => a.anotherObjList ++ b.anotherObjList)
Or if the Set can be empty, use foldLeft:
objs.foldLeft(List.empty[AnotherObj],(a,b) => a.anotherObjList ++ b.anotherObjList)
In your code example, you are using a val list which cannot be reassigned. When you do List.concat(list, obj.anotherObjList) you are creating a new list that concats the empty list to the current obj's anotherObjList, but the result is never used, so list will still be empty after the execution of the for-loop.
If you really need to use a imperative for-loop, either use an immutable collection and assign it to a var which can be reassigned from the for-loop's body or use a mutable collection:
object MyObj {
var objs = Set(
MyObj("MyObj1", anotherObjList),
MyObj("MyObj1", anotherObjList),
)
def findAllLoop1 = {
var list = List.empty
for (obj <- objs) list = list ++ obj.anotherObjList
list
}
def findAllLoop2 = {
val buf = collection.mutable.ListBuffer[Int]() // replace Int with your actual type of elements
for (obj <- objs) buf ++= obj.anotherObjList
}
}
But If you don't have to use a imperative loop for some reason, I would strongly suggest to use a functional alternative:
object MyObj {
var objs = Set(
MyObj("MyObj1", anotherObjList),
MyObj("MyObj1", anotherObjList),
)
def findAll =
objs.flatMap(_.anotherObjList) // if Set as return type is okay
def findAll: List[Int] =
objs.flatMap(_.anotherObjList)(collection.breakOut) // if elements are of type Int and you want to get a List at the end, not a Set
}

For loop inside a .map() in scala: return type is "Unit"

EDIT: I found this What is Scala's yield? (particularly the second, most popular, answer) to be very instructive after the accepted answer solved my problem.
==
I have a HashMap, which I want to iterate in, and for each keys, use a for loop to create new objects.
I'm trying to get a list of those new objects, but I'm always given back an empty "Unit" sequence. I'd like to understand better the behaviour of my code.
case class MyObject(one: String, two: String, three: Int)
val hm = new HashMap[String,Int]
hm += ("key" -> 3)
hm += ("key2" -> 4)
val newList = hm.map { case (key,value) =>
for (i <- 0 until value) {
new MyObject(key, "a string", i)
}}.toSeq
result:
newList:Seq[Unit] = ArrayBuffer((), ())
If I don't use any for loop inside the .map(), I have the type of structure I'm expecting:
val newList = hm.map { case (key,value) =>
new MyObject(key, "a string", value)}.toSeq
results in:
newList:Seq[MyObject] = ArrayBuffer(MyObject(key,host,3), MyObject(key2,host,4))
As I mentioned in my comment, you are missing yield on the for comprehension in your map statement. If you do not include the yield keyword then your for comprehension is purely side effecting and does not produce anything. Change it to:
for (i <- 0 until value) yield {
Now from here, you will end up with a Seq[IndexedSeq[MyObject]]. If you want to end up with just a Seq[MyObject] then you can flatten like so:
val newList = hm.map { case (key,value) =>
for (i <- 0 until value) yield {
MyObject(key, "a string", i)
}}.toSeq.flatten
}
And in fact (as pointed out by #KarolS), you can shorten this even further by replacing map with flatMap and remove the explicit flatten at the end:
val newList = hm.flatMap { case (key,value) =>
for (i <- 0 until value) yield {
MyObject(key, "a string", i)
}}.toSeq
}

scala append to a mutable LinkedList

Please check this
import scala.collection.mutable.LinkedList
var l = new LinkedList[String]
l append LinkedList("abc", "asd")
println(l)
// prints
// LinkedList()
but
import scala.collection.mutable.LinkedList
var l = new LinkedList[String]
l = LinkedList("x")
l append LinkedList("abc", "asd")
println(l)
// prints
// LinkedList(x, abc, asd)
Why does the second code snippet works but the first one doesnt? This is on Scala 2.10
The documentation says If this is empty then it does nothing and returns that. Otherwise, appends that to this.. That is exactly, what you observed. If you really need a mutable list, I would suggest you to use scala.collection.mutable.ListBuffer instead, with it you can do
val lb = new ListBuffer[Int]
scala> lb += 1
res14: lb.type = ListBuffer(1)
scala> lb
res15: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1)
scala> lb ++= Seq(1,2,3)
res17: lb.type = ListBuffer(1, 1, 2, 3, 1, 2, 3)
scala> lb
res18: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 1, 2, 3, 1, 2, 3)
As I understand it is related to First/Last (Nil) element in the list (if list is empty Nil is first and last element at the same time).
LinkedList (still) follows "primitive charm" strategy. So it does not try to add/append new data to/after Nil, to have possible result like this: {Nil, newElement}. (After all Nil should be last element)
Of course it could check if list is empty then put addingList to the beginning and Nil to the end. But this would be "too smart", I guess.
But, anyway append() returns "expecting" result Like this:
val addingList = new LinkedList[String]("a", "b")
val result = emptyList append addingList
result = {"a", "b"}. In this case it returns 'addingList' itself, and/but does not change initial list.
If we try to assign newElement to the next ref:
emptyList.next = LinkedList("whatever")
As result we would have emtyList changed like this:
LinkedList(null, whatever)
I.e. it creates fist element as null, since we have used next() assigning new/next element to it. So it moves Nil to the end, because first element which is null, has next reference to new element we added (addingElelement).
Because
"the "emptyList" is also the "head" link"
and head in our case head is Nil, but Nill can not have next, so it has to create new first element (which is has null value) with next() referece to our new addingElelement.
Personally I find it "too much primitive" and not "so much elegant". But it depends, I guess.
Task oriented story:
For my initial task (why I start thinking about this 'strange' list behaviour [even though it's mutable]) -- I wanted to use mutable list for a class/object called Dictionary which would keep Words in it (dictionary by default has not any words). And I would have methods like addWord(wod:String) for adding new words. For now my implementation will be changed (I'm not going to use this LinkedList, but rather MutableList. It seems it is more mutable than previous one):
object Dictionary {
val words = new mutable.MutableList[Word]();
def addWord(word: Word): Unit = {
words += word;
}
}
But possible implementation could be like this:
object Dictionary {
var words = new mutable.LinkedList[Word]();
def addWord(word: Word): Unit = {
if (words.isEmpty) {
words = words append( mutable.LinkedList[Word](word) ) // rely on append result
} else {
words append( mutable.LinkedList[Word](word) )
}
}
}
But then I have to use var instead of val, and I should transform every new Word to LinkedList, and my logic became more complicated.

Why my list is empty?

I have this code:
val products = List()
def loadProducts(report: (Asset, Party, AssetModel, Location, VendingMachineReading)) = {
report match {
case (asset, party, assetModel, location, reading) =>
EvadtsParser.parseEvadts(reading.evadts, result)
(result.toMap).map(product => ReportData(
customer = party.name,
location = location.description,
asset = asset.`type`,
category = "",
product = product._1,
counter = product._2,
usage = 0,
period = "to be defined")).toList
}
}
results.foreach(result => products ::: loadProducts(result))
println(products)
Can you please tell me what I am doing wrong because products list is empty? If I println products inside loadProducts method, products is not empty. Is the concatenation I am doing wrong?
PS: I am a scala beginner.
As I've already said, ::: yields a new list instead of mutating the one you already have in place.
http://take.ms/WDB http://take.ms/WDB
You have two options to go: immutable and mutable
Here is what you can do in immutable and idiomatic style:
def loadProducts(report: (...)): List[...] = {
...
}
val products = result.flatMap(result => loadProducs(result))
println(products)
But also, you can tie with mutability and use ListBuffer to do the things you've wanted:
def loadProducts(report: (...)): List[T] = {
...
}
val buffer = scala.collection.mutable.ListBuffer[T]()
result.foreach(result => buffer ++ = loadProducs(result))
val products = buffer.toList
println(products)
P.S. flatMap( ...) is an analog to map(...).flatten, so don't be confused that I and Tomasz written it so differently.
List type is immutable, val implies a reference that never changes. Thus you can't really change the contents of products reference. I suggest building a "list of lists" first and then flattening:
val products = results.map(loadProducts).flatten
println(products)
Notice that map(loadProducts) is just a shorthand for map(loadProducts(_)), which is a shorthand for map(result => loadProducts(result)).
If you become more experienced, try foldLeft() approach, which continuously builds products list just like you wanted to do this:
results.foldLeft(List[Int]())((agg, result) => agg ++ loadProducts(result))

Categories