Thursday, June 18, 2009

String substitution missing in Erlang - for them lazy boys.

I keep seeing complaints that there is no such thing as substring replace
string:sub("Hello, there!","there","World") -> "Hello, World!"
or the global substitution:
gsub("Hell###, W###rld!","###","o") -> "Hello, World!"

Here is the module strre.erl:

-module(strre).
-author("Serge: onerlang.blogspot.com").
-purpose("string replace functionality").
-export([sub/3,gsub/3,test/0]).

sub(Str,Old,New) ->

Lstr = len(Str),
Lold = len(Old),
Pos = str(Str,Old),
if
Pos =:= 0 ->
Str;
true ->
LeftPart = left(Str,Pos-1),
RitePart = right(Str,Lstr-Lold-Pos+1),
concat(concat(LeftPart,New),RitePart)
end.

gsub(Str,Old,New) ->
Acc = sub(Str,Old,New),
subst(Acc,Old,New,Str).

subst(Str,_Old,_New, Str) -> Str;
subst(Acc, Old, New,_Str) ->
Acc1 = sub(Acc,Old,New),
subst(Acc1,Old,New,Acc).

test() ->
io:format("~p ~p ~p ~p ~p ~p ~p ~n",
[
"SELECT * FROM people WHERE first='John' OR last='John'" =:=
gsub("SELECT * FROM people WHERE first=$1 OR last=$1","$1","'John'"),
"aBc" =:= sub("abc","b","B"),
"Abc" =:= sub("abc","a","A"),
"abC" =:= sub("abc","c","C"),
"aac" =:= gsub("bbc","b","a"),
"abc" =:= gsub("abc","d","C"),
"abc" =:= sub("abc","d","D")]).

4 comments:

Daniello said...

Here is the simplified version:

-module(strre).
-author("Daniel Kwiecinski: lambder.com").
-purpose("string replace functionality").
-export([sub/3,gsub/3]).

sub(Str,Old,New) ->
RegExp = "\\Q"++Old++"\\E",
re:replace(Str,RegExp,New,[multiline, {return, list}]).

gsub(Str,Old,New) ->
RegExp = "\\Q"++Old++"\\E",
re:replace(Str,RegExp,New,[global, multiline, {return, list}]).


http://gist.github.com/132572

Serge said...

Agreed: one-liner is always simpler than anything else. But hey, the regexp-based code takes twice as long to run: it is too heavy a cannon.

Anonymous said...

Unfortunatelly the non-regex code will not work if Old is substring of New, e.g. strre:gsub("aXb","X","Xc").

Working code would be something like:

gsub(S,Old,New) ->
StrLen = string:len(S),
OldLen = string:len(Old),
Pos = string:str(S,Old),
if
Pos =:= 0 ->
S;
true ->
Left = string:left(S,Pos-1),
Right = string:right(S,StrLen-OldLen-Pos+1),
string:concat(string:concat(Left,New++"^"),gsub(Right,Old,New))
end.

Anonymous said...

Errata:

string:concat(string:concat(Left,New),gsub(Right,Old,New))