Concatenate padded strings
Asked Answered
L

8

15

I have three strings, for example "A", "B", "C". I have to produce the string which results from concatenating them, only the second string must be padded with whitespace to a given length.

This was my first attempt as guided by intuition and general Scala newbiness:

val s1 = "A"
val s2 = "B"
val s3 = "C"
val padLength = 20

val s = s1 + s2.padTo(padLength, " ") + s3

which is wrong because padTo returns a SeqLike whose toString does not return the string inside but a Vector-like representation.

What would be the best idiomatic way to do this in Scala?

Lawgiver answered 7/6, 2013 at 14:12 Comment(0)
F
25

String can be (via an implicit conversion to StringOps here) considered like a collection of characters, so your padding should be:

val s = s1 + s2.padTo(padLength, ' ') + s3 // note the single quotes: a char

Calling .padTo(padLength, " ") on a String actually returns a Seq[Any] since you end up with both chars and strings in your sequence.

Faunie answered 7/6, 2013 at 14:31 Comment(1)
Thank you so much, that was exactly itExonerate
A
10

You didn't say whether you want to pad to the left or right. Just use format:

val s = f"$s1%s$s2%20s$s3"

Or, before Scala 2.10 (or if you need "20" as a parameter):

val s = "%s%"+padLength+"s%s" format (s1, s2, s3)

Use negative padding to add padding space to the right instead of the left.

Automotive answered 7/6, 2013 at 14:29 Comment(1)
For anyone who didn't upvote, a plug for the perils of customizing a stupid little padding routine, scalapuzzlers.com/#pzzlr-027, an oops while adding github.com/scala/scala/blob/master/src/compiler/scala/tools/nsc/….Shantelleshantha
S
5

Someone should mention that you should turn up warnings:

apm@mara:~$ skala -Ywarn-infer-any
Welcome to Scala version 2.11.0-20130524-174214-08a368770c (OpenJDK 64-Bit Server VM, Java 1.7.0_21).
Type in expressions to have them evaluated.
Type :help for more information.

scala> "abc".padTo(10, "*").mkString
<console>:7: warning: a type was inferred to be `Any`; this may indicate a programming error.
       val res0 =
           ^
res0: String = abc*******

Note that there's nothing wrong (per se) with doing it this way.

Maybe there's a use case for:

scala> case class Ikon(c: Char) { override def toString = c.toString }
defined class Ikon

scala> List(Ikon('#'),Ikon('@'),Ikon('!')).padTo(10, "*").mkString
res1: String = #@!*******

or better

scala> case class Result(i: Int) { override def toString = f"$i%03d" }
defined class Result

scala> List(Result(23),Result(666)).padTo(10, "---").mkString
res4: String = 023666------------------------

Since this isn't your use case, maybe you should ask if you want to use an API that is verbose and fraught with peril.

That's why Daniel's answer is the correct one. I'm not sure why the format string in his example looks so scary, but it usually looks more benign, since in most readable strings, you only need formatting chars in a few places.

scala> val a,b,c = "xyz"

scala> f"$a is followed by `$b%10s` and $c%.1s remaining"
res6: String = xyz is followed by `       xyz` and x remaining

The one case where you're required to add a spurious formatter is when you want a newline:

scala> f"$a%s%n$b$c"
res8: String = 
xyz
xyzxyz

I think the interpolator should handle f"$a%n$b". Oh hold on, it's fixed in 2.11.

scala> f"$a%n$b"  // old
<console>:10: error: illegal conversion character
              f"$a%n$b"

scala> f"$a%n$b"  // new
res9: String = 
xyz
xyz

So now there's really no excuse not to interpolate.

Shantelleshantha answered 8/6, 2013 at 4:4 Comment(1)
+1 for the explanations. What I dislike about Daniel's answer is that the interpolated string is much much less readable than the .padTo option.Lawgiver
C
3

There is a formatted method available. For example:

val s = s1 + s2.formatted("%-10s") + s3

as well as format:

val s = s1 + "%-10s".format(s2) + s3

but I would only use it, if the padLength can be embedded directly.

If it's defined elsewhere (as in your example), you can use padTo, just like gourlaysama pointed out.

If you want to "pad left", you could use an implicit class:

object Container {
  implicit class RichChar(c: Char) {
    def repeat(n: Int): String = "".padTo(n, c)
  }

  implicit class RichString(s: String) {
    def padRight(n: Int): String = {
      s + ' '.repeat(n - s.length)
    }

    def padLeft(n: Int): String = {
      ' '.repeat(n - s.length) + s
    }
  }
}

object StringFormat1Example extends App {
  val s = "Hello"

  import Container._

  println(s.padLeft(10))
  println(s.padRight(10))
}
Cistaceous answered 7/6, 2013 at 15:10 Comment(0)
L
2

This works:

val s = s1 + s2.padTo(padLength, " ").mkString + s3

but feels somehow wonky.

Lawgiver answered 7/6, 2013 at 14:13 Comment(2)
Better formulation: b.padTo(10, " ").mkString(a, "", c)Shantelleshantha
@Shantelleshantha yes yes yes, slapface time.Lawgiver
S
2

This is worth adding to the misc reformulations:

scala> val a = "A"; val b="B";val c="C"
a: String = A
b: String = B
c: String = C

scala> b.padTo(10, " ").mkString(a, "", c)
res1: String = AB         C

which saves some +'s for church on Sunday.

I became a believer in exploiting mkString at this really wonky commit.

Shantelleshantha answered 8/6, 2013 at 19:46 Comment(0)
A
1
s1 + s2 + " "*(padLength - s2.size) + s3
Abdullah answered 7/6, 2013 at 14:57 Comment(0)
T
0

I believe the most Scala idiomatic way is to use * operator on a single space Char converted to String by the .toString method, so it would be:

// for right padding
val sr = s1 + s2 + ' '.toString * padLength + s3

// for left padding
val sl = s1 + ' '.toString * padLength + s2 + s3
Torchier answered 16/9, 2023 at 9:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.