I'm a Scala beginner and looking at the trim()
method of the Java API. I noticed side effects, so I attempted to implement a functional version in Scala.
Here is the Java version:
public String trim() {
int len = count;
int st = 0;
int off = offset; /* avoid getfield opcode */
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[off + st] <= ' ')) {
st++;
}
while ((st < len) && (val[off + len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < count)) ? substring(st, len) : this;
}
My Scala version iterates over a string, and when it encounters a space char, the tail of the string is returned. The entire string is then reversed, so it can access the trailing whitespace. This new string is iterated over until a space char is encountered and the tail of this string is returned. This new string is then reversed again to maintain the original string order:
def trim(stringToTrim : String) : String = {
def removePreString[String](list : List[String]) : java.lang.String = list match {
case head :: tail => {
if(head.toString.equals(" ")){
removePreString(tail)
}
else {
head.toString + tail.mkString
}
}
}//end removePreString
val preString = removePreString(stringToTrim.toList)
val reveresedString = preString.toList.reverse
val reveresedPostString = removePreString(reveresedString)
reveresedPostString.reverse
}//end trim
How can this code be improved? Does it seem wasteful reversing the string twice?
3 Answers 3
No good idea about the concern of reversing the list twice. But you may
Let you inner function return a List instead of a string thus having less conversions from list to string and string to list.
Change your match to the following pattern (not tested):
case " " :: tail => removePreString(tail) case _ => _
I just realize you could also use on a list def dropWhile(p: (A) ⇒ Boolean): List[A]
which drops longest prefix of elements that satisfy a predicate. You could then chain dropWhile not a space | reverse | dropwhile not a space | reverse...
you can test for a whitespace in patter matching, an alternative version of your removePreSign
method therefore could be written as:
def removePreString(list : List[Char]): String = list match {
case ' ' :: tail => removePreString(tail)
case _ => list.mkString
}
but as pgras noted, you can simply use str.dropWhile(_ == ' ')
which avoids the whole String <=> List
issue.
A possible way to do it without reverse
and dropWhile
using only recursion:
def trim(stringToTrim : String) : String = {
def removeLeading(str: String): String =
if (str startsWith " " ) removeLeading(str tail)
else str
def removeTrailing(str: String): String =
if (str endsWith " ") removeTrailing(str dropRight 1 )
else str
removeTrailing(removeLeading(stringToTrim))
}
-
\$\begingroup\$ your function 'removePreString' does not work for Strings with trailing whitespace. e.g - " this is a test " \$\endgroup\$blue-sky– blue-sky2013年01月14日 21:38:48 +00:00Commented Jan 14, 2013 at 21:38
-
\$\begingroup\$ neither does the original function, it is just another way to achieve the same thing, but as noted above it is equivalent to dropWhile \$\endgroup\$Markus Hauck– Markus Hauck2013年01月15日 09:31:47 +00:00Commented Jan 15, 2013 at 9:31
- Since left and right trim may be useful on their own, expose them.
- View
String
as a list ofChar
instead of converting toList[String]
- The recursion creates a string at each step ! Take advantage of the rich library of methods for lists (and the like).
- For instance, reversing twice is fine as long you manipulate iterators.
Applying these advices leads to :
def trimLeft (s: String) = s dropWhile ( _ == ' ' )
def trimRight (s: String) = (s.reverseIterator dropWhile ( _ == ' ' ))
.toSeq.reverseIterator mkString
val trim = trimLeft _ compose trimRight _
It you prefer to extract the sub-string of interest at once, there are fine functions to find the indices you need.