Reduce list of tuples to a single tuple in scala - scala

Item is a custom type.
I have a Iterable of pairs (Item, Item). The first element in every pair is the same, so I want to reduce the list to a single pair of type (Item, Array[Item])
// list: Iterable[(Item, Item)]
// First attempt
val res = list.foldLeft((null, Array[Item]()))((p1,p2) => {
(p2._1, p1._2 :+ p2._2)
}
// Second attempt
val r = list.unzip
val res = (r._1.head, r._2.toArray))
1. I don't know how to correctly setup the zero value in the first ("foldLeft") solution. Is there any way to do something like this?
2. Other than the second solution, is there a better way to reduce a list of custom object tuples to single tuple ?

If you are sure the first element in every pair is the same, why don't you use that information to simplify?
(list.head._1, list.map(_._2))
should do the work
if there are other cases where the first element is different, you may want to try:
list.groupBy(_._1).map { case (common, lst) => (common, lst.map(_._2)) }

Related

Expected Map[] but Found List[Map[]]

The scala program is iterating through a list of words and appending the word with value if already found or adding key->word otherwise. It is expected to produce Map[] but producing List[Map[]] instead.
val hashmap:Map[List[(Char, Int)], List[String]]=Map()
for (word <- dictionary) yield {
val word_occ = wordOccurrences(word)
hashmap + (if (hashmap.contains(word_occ)) (word_occ -> (hashmap(word_occ) ++ List(word))) else (word_occ -> List(word)))
}
Note that in this case you probably want to build the Map in a single pass rather than modifying a mutable Map:
val hashmap:Map[List[(Char, Int)], List[String]]=
dictionary
.map(x => (wordOccurrences(x), x))
.groupBy(_._1)
.map { case (k, v) => k -> v.map(_._2) }
In Scala 2.13 you can replace the last two lines with
.groupMap(_._1)(_._2)
You can also use a view on the dictionary to avoid creating the intermediate list if performance is a significant issue.
A for comprehension with a single <- generator de-sugars to a map() call on the original collection. And, as you'll recall, map() can change the elements of a collection, but it won't change the collection type itself.
So if dictionary is a List then what you end up with will be a List. The yield specifies what is to be the next element in the resulting List.
In your case the code is creating a new single-element Map for each element in the dictionary. Probably not what you want. I'd suggest you try using foldLeft().

Scala: Compare elements at same position in two arrays

I'm in the process of learning Scala and am trying to write some sort of function that will compare one element in an list against an element in another list at the same index. I know that there has to be a more Scalatic way to do this than two write two for loops and keep track of the current index of each manually.
Let's say that we're comparing URLs, for example. Say that we have the following two Lists that are URLs split by the / character:
val incomingUrl = List("users", "profile", "12345")
and
val urlToCompare = List("users", "profile", ":id")
Say that I want to treat any element that begins with the : character as a match, but any element that does not begin with a : will not be a match.
What is the best and most Scalatic way to go about doing this comparison?
Coming from a OOP background, I would immediately jump to a for loop, but I know that there has to be a good FP way to go about it that will teach me a thing or two about Scala.
EDIT
For completion, I found this outdated question shortly after posting mine that relates to the problem.
EDIT 2
The implementation that I chose for this specific use case:
def doRoutesMatch(incomingURL: List[String], urlToCompare: List[String]): Boolean = {
// if the lengths don't match, return immediately
if (incomingURL.length != urlToCompare.length) return false
// merge the lists into a tuple
urlToCompare.zip(incomingURL)
// iterate over it
.foreach {
// get each path
case (existingPath, pathToCompare) =>
if (
// check if this is some value supplied to the url, such as `:id`
existingPath(0) != ':' &&
// if this isn't a placeholder for a value that the route needs, then check if the strings are equal
p2 != p1
)
// if neither matches, it doesn't match the existing route
return false
}
// return true if a `false` didn't get returned in the above foreach loop
true
}
You can use zip, that invoked on Seq[A] with Seq[B] results in Seq[(A, B)]. In other words it creates a sequence with tuples with elements of both sequences:
incomingUrl.zip(urlToCompare).map { case(incoming, pattern) => f(incoming, pattern) }
There is already another answer to the question, but I am adding another one since there is one corner case to watch out for. If you don't know the lengths of the two Lists, you need zipAll. Since zipAll needs a default value to insert if no corresponding element exists in the List, I am first wrapping every element in a Some, and then performing the zipAll.
object ZipAllTest extends App {
val incomingUrl = List("users", "profile", "12345", "extra")
val urlToCompare = List("users", "profile", ":id")
val list1 = incomingUrl.map(Some(_))
val list2 = urlToCompare.map(Some(_))
val zipped = list1.zipAll(list2, None, None)
println(zipped)
}
One thing that might bother you is that we are making several passes through the data. If that's something you are worried about, you can use lazy collections or else write a custom fold that makes only one pass over the data. That is probably overkill though. If someone wants to, they can add those alternative implementations in another answer.
Since the OP is curious to see how we would use lazy collections or a custom fold to do the same thing, I have included a separate answer with those implementations.
The first implementation uses lazy collections. Note that lazy collections have poor cache properties so that in practice, it often does does not make sense to use lazy collections as a micro-optimization. Although lazy collections will minimize the number of times you traverse the data, as has already been mentioned, the underlying data structure does not have good cache locality. To understand why lazy collections minimize the number of passes you make over the data, read chapter 5 of Functional Programming in Scala.
object LazyZipTest extends App{
val incomingUrl = List("users", "profile", "12345", "extra").view
val urlToCompare = List("users", "profile", ":id").view
val list1 = incomingUrl.map(Some(_))
val list2 = urlToCompare.map(Some(_))
val zipped = list1.zipAll(list2, None, None)
println(zipped)
}
The second implementation uses a custom fold to go over lists only one time. Since we are appending to the rear of our data structure, we want to use IndexedSeq, not List. You should rarely be using List anyway. Otherwise, if you are going to convert from List to IndexedSeq, you are actually making one additional pass over the data, in which case, you might as well not bother and just use the naive implementation I already wrote in the other answer.
Here is the custom fold.
object FoldTest extends App{
val incomingUrl = List("users", "profile", "12345", "extra").toIndexedSeq
val urlToCompare = List("users", "profile", ":id").toIndexedSeq
def onePassZip[T, U](l1: IndexedSeq[T], l2: IndexedSeq[U]): IndexedSeq[(Option[T], Option[U])] = {
val folded = l1.foldLeft((l2, IndexedSeq[(Option[T], Option[U])]())) { (acc, e) =>
acc._1 match {
case x +: xs => (xs, acc._2 :+ (Some(e), Some(x)))
case IndexedSeq() => (IndexedSeq(), acc._2 :+ (Some(e), None))
}
}
folded._2 ++ folded._1.map(x => (None, Some(x)))
}
println(onePassZip(incomingUrl, urlToCompare))
println(onePassZip(urlToCompare, incomingUrl))
}
If you have any questions, I can answer them in the comments section.

Gatling: transform findAll to sorted list

I'm new to scala and Gatling. I'm trying to transform the result of findAll into a sorted list and then return a String representation of the sorted list. I can't seem to do this with the following code:
http(requestTitle)
.post(serverUrl)
.body(ElFileBody(sendMessageFile))
.header("correlation-id", correlationId)
.check(status.is(200),
jsonPath("$.data.sendMessage.targetedRecipients").findAll.transform(recipients => {
println("recipients class: " + recipients.getClass)
var mutable = scala.collection.mutable.ListBuffer(recipients: _*)
var sortedRecipients = mutable.sortWith(_ < _)
println("users sorted "+ usersSorted)
usersSorted.mkString(",")
}).is(expectedMessageRecipients))
Recipients is of type scala.collection.immutable.Vector.
I thought I would be able to convert the immutable collection into a mutable collection using scala.collection.mutable.ListBuffer. Any help would be appreciated, thanks.
I don't think your problem is immutability, it's JSON parsing vs Gatling's .find and .findAll methods.
I'm going to make a guess that your response looks something like...
{"data":{"sendMessage":{"targetedRecipients":[1,4,2,3]}}}
in which case Gatling's .findAll method will return a vector (it always does if it finds something), but it will only have one element which will be "[1,4,2,3]" - ie: a string representing json data, so sorting the collection of a single element naturally achieves nothing. To get .findAll to behave like you seem to be expecting, you would need a response something like...
{"data":
{"sendMessage":
{"targetedRecipients":
[{"recipientId":1},
{"recipientId":4},
{"recipientId":2},
{"recipientId":3}]
}}}
which you could use .jsonPath("$..recipientId").findAllto turn into a Vector[String] of the Ids.
So assuming you are indeed just getting a single string representation of an array of values, you could use a straight transform to generate an array and sort (as you tried in your example)
Here's a working version
val data = """{"data":{"sendMessage":{"targetedRecipients":[1,4,2,3]}}}"""
def sortedArray : ScenarioBuilder = scenario("sorting an array")
.exec(http("test call")
.post("http://httpbin.org/anything")
.body(StringBody(data)).asJson
.check(
status.is(200),
jsonPath("$.json.data.sendMessage.targetedRecipients")
.find
.transform(_
.drop(1)
.dropRight(1)
.split(",")
.toVector
.sortWith(_<_)
)
.saveAs("received")
))
.exec(session => {
println(s"received: ${session("received").as[Vector[String]]}")
session
})
There is no reason to use mutable collection if all you want is to sort the result:
Vector(5,4,3,2,1).sortWith(_ < _).mkString(", ") // "1, 2, 3, 4, 5"
To use ListBuffer you have to copy all elements into newly allocated object, so it isn't even more optimal in any way. Same with vars - you can use vals as you don't update the reference
println(s"recipients class: ${recipients.getClass}")
val result = recipients.sortWith(_ < _).mkString(", ")
println(s"users sorted $result")
result

List of functions mapped on view - is the view traversed more than once?

I'm trying to wrap my head around how views are used in Scala. I have the following example code:
class Square extends (Seq[Int] => Int) {
def apply(x: Seq[Int]) = x.reduce(_ * _)
}
class Sum extends (Seq[Int] => Int) {
def apply(x: Seq[Int]) = x.reduce(_ + _)
}
val functionList = List(new Square, new Sum)
val list = List(1, 2, 3, 4, 5, 6)
val view = list.view
functionList.map(f => f(view))
My questions are:
Will the list be traversed once or twice in the above example when the map is applied?
If it is traversed more than once, is there any other design pattern that I can use that allows me to define a collection of functions and map over some other collection while only traversing the second collection once?
The list is being traversed twice. What you're essentially doing is first taking the Square function and applying a reduce on all the elements. Next the Sum function will also apply a reduce on all the elements.
A view turns the collection into a lazy one. Meaning if you have multiple transformations they will only be applied when they are needed. In this case, I don't think this has any impact on your solution, since you're applying functions that do calculations on whole collections (meaning they need to be evaluated to get the result). See this question for an answer on when to use views.
Traversing the second collection once isn't easy in this case. You have two functions that do a reduce on the whole list, meaning each one needs a seperate accumulator and has a seperate result. If you want to only traverse your element list once, you need to change your logic a bit. Instead of defining operations on the whole list, you need to define operations on how to combine two elements. Here's what I came up with:
val functionListWithInitialValues = List(
((a: Int, b: Int) => a * b, 1), //This is a Tuple that defines how to combine two calculations and what the initial value is.
((a: Int, b: Int) => a + b, 0)
)
val results = list.foldLeft(functionListWithInitialValues) {
case (accumulators, next) => // foldLeft gives us the previous results
// (which is essentailly a Tuple(function, value)),
// and the next element, to combine with the previous result.
// Now let's go through our functions and apply the functions on the accumulator and the next element
accumulators.map {
case (function, previousResult) =>
(function, function(previousResult, next))
}
}
results.map { case (function, result) => println(result) }
This solution will only traverse your elements once, applying the combining function on the accumulator and the next element.
Not sure what List you are talking about.
You are applying the map on an actual strict List so functionList will be traversed once, and applying 'f' to the view will reduce the List[Int] for each function in functionList.
If you want to apply your modificators lazily then functionList should be a view so that 'map' is not strict.

LinkedList: Iterate and remove element

In Scala, while iterating through the elements of a LinkedList, I would like to have some method remove() which removes the current element and (very important) makes the iterator point to the next element (or to the first if the current element is the last one; to null or something if there are no more elements).
Not sure if this what you want but how about:
#annotation.tailrec
def round(xs: Set[(Int, Int)]) = {
// here you might check if items are exhausted and possibly don't go through recursion deeper
val suitable = xs.filter {
case (x, i) =>
println("Element "+x+" on position "+i)
x != 0
}
// do something with suitable items
round(xs.diff(suitable)) // next round with items that doesn't succeed in current round
}
val list = List(3,4,5)
round(list.zipWithIndex.toSet)