2
\$\begingroup\$

I came across an issue the other day where I really just could not think of a functional solution. So I fell back on my imperative programming skills. Since a functional solution still eludes me, I figured I'd shout out for some help here.

Use Case

I have a List of Strings with arbitrary lengths. These Strings need to be combined into rows that have the same max length.

When a single String's length is greater than max length, that String just gets it's own row as each row eventually get's handled by a function that ensures all Strings are truncated to an appropriate length.

Example Usage

scala> val rows = combineStrings(List("Short", "This is a particularly long String...", "More", "Small", "Strings"), 35)

Current Output

scala> rows.foreach(println)
Short
This is a particularly long String...
More, Small, Strings

Desired Output

scala> rows.foreach(println)
Short, More, Small, Strings
This is a particularly long String...

Current Solution

def combineStrings(strs: List[String], maxLen: Int)
: List[String] = {
  import scala.collection.mutable.ListBuffer

  var scratch = strs.mkString("|")
  val rows = ListBuffer[String]()

  while (scratch.length > 1) {
    scratch.indexOf("|") match {
      case idx if (idx == -1 || scratch.length < maxLen) =>
        rows += scratch
        scratch = ""

      case idx if idx > maxLen =>
        rows += scratch.substring(0, idx)
        scratch = scratch.substring(idx+1)

      case idx =>
        val lIdx = scratch.lastIndexOf("|", maxLen)
        rows += scratch.substring(0, lIdx)
        scratch = scratch.substring(lIdx+1)
    }
  }

  rows.toList.map(_.replaceAll("\\|", ", "))
}

Notes

  • Pipes are used because the original Strings are more likely to have their own commas than their own pipes. Maybe other solutions won't need this hackery.
  • Strings are kept in the same order, but in the end I'd prefer to have the most "compact" rows possible.

Summary

Any ideas on how to make this more functional would be greatly appreciated. I found several functions in Scala's "String" that looked promising, but I'm not really sure how to integrate them with the length requirements.

\$\endgroup\$
  • \$\begingroup\$ Can you give an example of input and result? \$\endgroup\$ – Juliano Alves Jan 17 '14 at 16:13
  • \$\begingroup\$ Added example usage section to describe input and current result. \$\endgroup\$ – David P. Jan 17 '14 at 22:54
  • \$\begingroup\$ I think what you want is to combine strings into one string (separated by ", ") such that the combined string has length under and as close to maxLength as possible without splitting the strings. Furthermore if a string is already over maxLength then it is left. Yes? Please clarify (1) do the joined strings have to be under maxLength or 'minimally over'? (2) is this an optimization problem - i.e. should the joined length be as close as possible to maxLength (3) is order important? \$\endgroup\$ – samthebest Jan 19 '14 at 17:53
  • \$\begingroup\$ @samthebest, 1) Joined string length must be <= maxLength; 2) Yes, this is essentially a space optimization solution; 3) Order is not important. \$\endgroup\$ – David P. Jan 19 '14 at 18:51
3
\$\begingroup\$

This is much more "Functional" than my first attempt:

  def combineStrings(strs: List[String], maxLen: Int)
  : List[String] = {
    import scala.collection.mutable.StringBuilder

    strs.map(_.trim).filter(_.nonEmpty)
      .aggregate(List[StringBuilder]())({ (lbs, s) =>
        s.length match {
          case l if (l > maxLen) =>
            new StringBuilder(s) :: lbs
          case l =>
            lbs.find(_.length <= maxLen - (l+2))
              .map { sb => sb ++= s", $s"; lbs }
              .getOrElse(new StringBuilder(s) :: lbs)
        }
      }, _ ++ _
    ).map(_.toString).reverse
  }

Not sure why I didn't do this the first time around. This solution even provides the desired output, and accounts for the ", " separators in the length calculations.

scala> val rows = combineStrings(List("Short", "This is a particularly long String...", "More", "Small", "Strings"), 35)
rows: List[String] = List(Short, More, Small, Strings, This is a particularly long String...)

scala> rows.foreach(println)
Short, More, Small, Strings
This is a particularly long String...
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.