1. Overview
In this tutorial, we’ll see how to create a single String from a collection of elements, and in particular, a collection of Strings.
2. Joining a Collection of Strings
As developers, we often need to deal with collections of elements. Whether it’s a List, an Array, or a Set it’s not important at this moment. Frequently we’d need to join all those elements into a single String. For instance, imagine that we’re given a list of paragraphs, and we want to join all of the paragraphs as the full text*.* In addition, we’d like to join all the paragraphs using the newline character (\n).
Each programming language has its own way to achieve this. Some solve the problem with standard lib utility functions. Others use external libs. And a few others solve it by using naive solutions, like looping through the elements.
Let’s look at different approaches to solve this problem.
2.1. Using mkString
The first solution is probably the most idiomatic and it’s very simple to use. We can call the mkString method and it will concatenate the collection elements:
scala> List("a","b","c").mkString
val res1: String = abc
As a nice bonus, it allows us to specify a custom separator between each String from the collection as well:
scala> List("a","b","c").mkString(",")
val res2: String = a,b,c
scala> List("a","b","c").mkString("-")
val res3: String = a-b-c
This solution works for any GenTraversableOnce. This means it can be used in most common standard collections, such as List, Set, Array, and so on:
scala> Array("a","b","c").mkString("/")
val res4: String = a/b/c
scala> Set("a","b","c").mkString("/")
val res5: String = a/b/c
scala> Map("a"->"A","b"->"B").mkString(" // ")
val res6: String = a -> A // b -> B
2.2. Using Fold Methods
Scala standard lib offers many methods to iterate through collections. One of the most powerful is the ‘fold family’ methods: foldLeft and foldRight. We’re not going to cover the difference between them, but you should be able to use any of them in our examples with a minimal change. We’ll leave it as a challenge for the reader.
These methods receive two arguments. The first is an initial value representing the empty state. The second argument is a merger function. The function will merge the current accumulated value with the next element iterated:
scala> List("a","b","c").fold("") { (accumulator, elem) => accumulator + "" + elem }
val res0: String = abc
This solution may not as readable, but it’s more powerful as we can perform expensive operations in the merger function. On the other hand, it doesn’t deal very well with edge cases as the mkString function does. For instance, if we use a naive function, it adds an unwanted separator at the beginning of the concatenated String:
scala> List("a","b","c").fold("") { (accumulator, elem) => accumulator + "/" + elem }
val res1: String = /a/b/c
A solution will be adding a more complex logic to check if the accumulator variable is empty. Or simply can simply drop the first character.
2.3. Using Reduce Methods
We may also use another very similar alternative to the fold family methods: the reduce family methods. Just like their counterpart, it has a few methods with subtle differences:reduceLeft, reduceLeftOption, reduceRight, reduceRightOption, reduceOption. Unlike the fold method, they don’t have a parameter for the initial empty state, because it expects a non-empty collection:**
scala> List("a","b","c").reduce { (accumulator, elem) => accumulator + elem }
val res0: String = abc
scala> List("a","b","c").reduce { (accumulator, elem) => accumulator + "/" + elem }
val res1: String = a/b/c
2.4. Using String Concatenation
Finally, we resort to the most naive solution, using a simple loop:
scala> var result = ""
var result: String = ""
scala> for (elem <- List("a","b","c")) {
| result = result + "/" + elem
| }
scala> result
val res0: String = /a/b/c
Take into consideration this is far from idiomatic code in Scala and should be used only as a last resort.
2.5. Joining Custom Objects
So far, we’ve been joining Strings into a single String. But, let’s imagine that we have a list of objects and we want to join all their attributes into a single String. For instance, we may need to export them as a CSV file, which is nothing more than a String of comma-separated values.
The easiest way to achieve this is starting by transforming the custom object into its String representation. Afterward, we can apply what we’ve learned so far in the previous section. For instance, if we are using case classes, then it’s trivial:
scala> case class Person(firstName: String, lastName: String)
class Person
scala> val p1 = Person("John", "Doe")
val p1: Person = Person(John,Doe)
scala> val p2 = Person("Jane", "Doe")
val p2: Person = Person(Jane,Doe)
scala> List(p1,p2).map(_.toString).mkString(" and ")
val res0: String = Person(John,Doe) and Person(Jane,Doe)
3. Conclusion
In this article, we saw many different solutions to join a collection of Strings into a single one using Scala.