Jump to content

Recommended Posts

Back again with another function, Not sure if there are other string replacement routines, but I've quickly put this one together.

 

Source

//
// PX_fnc_stringReplace :: Replace substrings
// Author: Colin J.D. Stewart
// Usage: ["xxx is awesome, I love xxx!", "xxx", "Arma"] call PX_fnc_stringReplace;
//
PX_fnc_stringReplace = {
    params["_str", "_find", "_replace"];
    
    private _return = "";
    private _len = count _find;    
    private _pos = _str find _find;

    while {(_pos != -1) && (count _str > 0)} do {
        _return = _return + (_str select [0, _pos]) + _replace;
        
        _str = (_str select [_pos+_len]);
        _pos = _str find _find;
    };    
    _return + _str;
};

 

Example:

["xxx is awesome, I love xxx!", "xxx", "Arma"] call PX_fnc_stringReplace;

// Output: Arma is awesome, I love Arma!

 

-Colin

  • Like 4

Share this post


Link to post
Share on other sites

Very useful. I don't remember of any official command or function.

  • Like 2

Share this post


Link to post
Share on other sites

A quick update that I required today for replacing multiple strings with value:

 

//
// PX_fnc_stringReplace :: Replace substrings
// Author: Colin J.D. Stewart
// Usage: ["xxx is awesome, I love xxx!", "xxx" || [], "Arma"] call PX_fnc_stringReplace;
//
PX_fnc_stringReplace = {
    params["_str", "_find", "_replace"];
    private["_return", "_len", "_pos"];
    
    if (!(_find isEqualType [])) then {
        _find = [_find];
    };
    
    {
        _return = "";
        _len = count _x;        
        _pos = _str find _x;
        
        while {(_pos != -1) && (count _str > 0)} do {
            _return = _return + (_str select [0, _pos]) + _replace;
            
            _str = (_str select [_pos+_len]);
            _pos = _str find _x;
        };    
        _str = _return + _str;
    } forEach _find;
    
    _str;
};

 

This will allow:

 

["abc<br>def<br />ghi<br/>jkl!", ["<br>", "<br/>", "<br />"], "\n"] call PX_fnc_stringReplace;

In the scenario above, there are better ways to accomplish this, but this is the lazy and easy way :)

Share this post


Link to post
Share on other sites

I made nearly the same function a year ago :D

Here is an efficency comparison:
 

Code:
["xxx is awesome, I love xxx!", "xxx", "Arma"] call PX_fnc_stringReplace;
Result:
0.0137 ms
Cycles:
10000/10000

Code:
["xxx is awesome, I love xxx!", "xxx", "Arma"] call TER_fnc_editString;
Result:
0.0157 ms
Cycles:
10000/10000

You win this time ;D

  • Like 2

Share this post


Link to post
Share on other sites

Can you guys give a real world example of when you need to replace strings? Can only come up with examples where you get data from an external source you do not control.

Share this post


Link to post
Share on other sites
8 minutes ago, Muzzleflash said:

Can you guys give a real world example of when you need to replace strings? Can only come up with examples where you get data from an external source you do not control.

MP chat sanitizer comes to mind. Or you could replace words players were typing into the chat into something different, some kind of autocorrect hack inside arma, heh. Have fun with that.

:yay:

 

Cheers

  • Like 2

Share this post


Link to post
Share on other sites

another could be multi-line listbox tooltips, replacing <br /> to \n for it to work :)
 

_hint = [getText(configFile >> "CfgMagazines" >> _class >> "descriptionShort"), ["<br />", "<br/>"], "\n"] call PX_fnc_stringReplace;

>

1526220154_5af8457a9854a.jpg

  • Like 2

Share this post


Link to post
Share on other sites

You young kids and your loops, get off my lawn!


GOM_fnc_replaceInString = {
	params ["_string","_find","_replaceWith"];
	_pos = (_string find _find);
	if (_pos isEqualTo -1) exitWith {_string};
	[[(_string select [0,_pos]),_replaceWith,_string select [_pos + count _find,count _string]] joinString "",_find,_replaceWith] call GOM_fnc_replaceInString;
};

_result = ["xxx is awesome, I love xxx!", "xxx", "Arma"] call GOM_fnc_replaceInString;
systemchat _result;

:yay:

 

Edit: Made performance test out of curiosity:

Result:
0.0138 ms
Cycles:
10000/10000
Code: 
["xxx is awesome, I love xxx!", "xxx", "Arma"] call GOM_fnc_replaceInString; 


Result:
0.0148 ms
Cycles:
10000/10000
Code:
["xxx is awesome, I love xxx!", "xxx", "Arma"] call PX_fnc_stringReplace;


Result:
0.0248 ms
Cycles:
10000/10000
Code:
["xxx is awesome, I love xxx!", "xxx", "Arma"] call TER_fnc_editString;

 

OouoLUj.jpg

 

Cheers

  • Like 5

Share this post


Link to post
Share on other sites
2 hours ago, Grumpy Old Man said:

You young kids and your loops, get off my lawn!

lol

  • Like 1
  • Haha 3

Share this post


Link to post
Share on other sites
4 hours ago, Grumpy Old Man said:

OouoLUj.jpg

Huh when I tested it I had about the same result for GOM_fnc_replaceString as for PX_fnc_stringReplace (GOM's was 0.0001ms faster) and a way better performance for my code... Must be the hardware.

Share this post


Link to post
Share on other sites

The average seems stable most of the benchmark runs, but sometimes they would just double when I ran them. The following is median of 3 (10000-iterations) runs (ludicrous runs excluded).

PX_fnc_stringReplace    : ~0.0123 ms
PX_fnc_stringReplaceEx  : ~0.0115 ms
PX_fnc_stringReplace2   : ~0.0149 ms
GOM_fnc_replaceInString : ~0.0144 ms
TER_fnc_editString      : ~0.0174 ms
MF_fnc_replaceInString  : ~0.0109 ms

But there is more to benchmarking than just trying with fixed arguments. Like, what if no matches? Or what if longer input. I ten-doubled the input text (first argument) so it basically is of a single paragraph length and got these numbers then (again median of 3 runs). Now the disparity us much larger. This can be important if you replacing in long texts:

PX_fnc_stringReplace    : ~0.0855 ms
PX_fnc_stringReplaceEx  : ~0.0768 ms
PX_fnc_stringReplace2   : ~0.0810 ms
GOM_fnc_replaceInString : ~0.1486 ms
TER_fnc_editString      : ~0.8496 ms
MF_fnc_replaceInString  : ~0.0623 ms

These are the median of 3 runs if I change the search text to 'Schwifty' (e.g. not match), still 10 times input length:

PX_fnc_stringReplace    : ~0.0056 ms
PX_fnc_stringReplaceEx  : ~0.0037 ms
PX_fnc_stringReplace2   : ~0.0061 ms
GOM_fnc_replaceInString : ~0.0037 ms
TER_fnc_editString      : ~0.0045 ms
MF_fnc_replaceInString  : ~0.0036 ms

Final comment, for GOM_fnc_replaceInString I added 'private' before '_pos = ...', since this should actually be done to avoid messing up outer scopes, which might cause frustrating bugs for callers. But I do not think this invalidates the test, since it both makes the code safer, and also in fact makes GOM_fnc_replaceInString run a tiny bit faster.

  • Like 2

Share this post


Link to post
Share on other sites
23 minutes ago, Harzach said:

Not to confuse matters, but there is also Kronzky's String Library, if you want to make another comparison.

is that even still around? its from 2007 ?

never mined I found it.

Share this post


Link to post
Share on other sites

You know, I thought he had kept it updated through A3, but maybe I am mistaken. Here's a link to an actual download: http://kronzky.info/snippets/strings/index.htm

*edit* - Considering it's checking for v1.09, I'd say I was definitely wrong :D

 

Anyway, it's really neat to see different approaches to the same problem. Thanks, guys!

Share this post


Link to post
Share on other sites

Grumpy Old Man: Loops will always be better than recursive calls in the long run, and your function does not replace multiple keys :P

MuzzleFlash: I would be interested to see these added to your variation of tests :)

//
// PX_fnc_stringReplaceEx :: Replace substrings
// Author: Colin J.D. Stewart
// Usage: ["xxx is awesome, I love xxx!", "xxx", "Arma"] call PX_fnc_stringReplaceEx;
//
PX_fnc_stringReplaceEx = {
    params["_str", "_find", "_replace"];
    
    private _pos = _str find _find;
    if (_pos == -1) exitWith {_str};
    
    private _return = "";
    private _len = count _find;    

    while {_pos != -1} do {
        _return = _return + (_str select [0, _pos]) + _replace;
        
        _str = (_str select [_pos+_len]);
        _pos = _str find _find;
    };    
    _return + _str;
};

//
// PX_fnc_stringReplace :: Replace substrings
// Author: Colin J.D. Stewart
// Usage: ["xxx is awesome, I love xxx!", "xxx" || [], "Arma"] call PX_fnc_stringReplace;
//
PX_fnc_stringReplace = {
    params["_str", "_find", "_replace"];
    private["_return", "_len", "_pos"];
    
    if (!(_find isEqualType [])) then {
        _find = [_find];
    };
    
    {    
        _pos = _str find _x;
        if (_pos != -1) then {
            _return = "";
            _len = count _x;        
            while {_pos != -1} do {
                _return = _return + (_str select [0, _pos]) + _replace;
                
                _str = (_str select [_pos+_len]);
                _pos = _str find _x;
            };    
            _str = _return + _str;
        };
    } forEach _find;
    
    _str;
};

 

  • Like 1

Share this post


Link to post
Share on other sites
5 hours ago, 7erra said:

Huh when I tested it I had about the same result for GOM_fnc_replaceString as for PX_fnc_stringReplace (GOM's was 0.0001ms faster) and a way better performance for my code... Must be the hardware.

 

I wouldn't give too much about the built in performance test, just use it as getting a general idea of the performance.

As @Muzzleflash stated, when testing a single function upon multiple game restarts the runtime can, for whatever reason, double occasionally.

Adding a private statement in front of _pos is a sensible thing to do, I just put this function together within a few minutes, just to see if a recursive function would even compete with a classical loop approach, heh.

If speed is a serious concern the most efficient way would probably to write a C++ extension and go from there.

 

Cheers

  • Like 1

Share this post


Link to post
Share on other sites

Yep, I considered writing a plugin in Ziron (assembly), but the main issue being that this could only be used server-side, unless users are willing to install a client side plugin (most people, especially new-comers are oblivious how to do that) -so the only alternative is to write optimised sqf :)

  • Like 1

Share this post


Link to post
Share on other sites

Good news, everyone!

Spoiler

:yay:

Did some reading and apparently there's also splitString/joinString which completely evaded my mind.

So here goes:

GOM_fnc_replaceInString = {
	params ["_string","_replace","_replaceWith"];
	private _pos = (_string find _replace);
	if (_pos isEqualTo -1) exitWith {
		_string;
	};
	if (_pos isEqualTo 0) exitWith {
		[_replaceWith,((_string splitString _replace) joinString _replaceWith)] joinString "";
	};
	(_string splitString _replace) joinString _replaceWith;
};
_result = ["xxx is awesome, I love xxx!", "xxx", "Arma"] call GOM_fnc_replaceInString;//0.0056ms
systemchat _result;

A5n7zpW.png

 

Pretty decent speed increase, quickly shuffled together and didn't test thoroughly.

Freed up plenty of overhead so you can adjust it to replace multiple strings, couldn't think of an efficient way at a glance, maybe apply, time's sparse, heh.

If anyone can beat/improve this, be my guest!

 

Disregard the above.

Was too good to be true after all,

see reason below!

 

Cheers

Share this post


Link to post
Share on other sites

To be honest, I don't think anyone has to beat it, since your solution is cheating and only works for properly for this particular input. Though you do admit you didn't test thoroughly. Put simply, splitString is not very useful here, and I suggest you read more carefully what it actually does. Examples:
 

["xxNOx is awesome, I love xxx!", "xxx", "Arma"] call GOM_fnc_replaceInString ==> "NOArma is awesome, I love Arma!"

["xxx is awesome, I love xxx!", "xx ", "Arma"] call GOM_fnc_replaceInString ==> "isArmaawesome,ArmaIArmaloveArma!"

["x x x is awesome, I love xxx!", "xxx", "Arma"] call GOM_fnc_replaceInString ==> " Arma Arma is awesome, I love Arma!"

["xxx is awesome, I love xxx!", "love", "Arma"] call GOM_fnc_replaceInString ==> "xxx is awArmasArmamArma, I Arma xxx!"

["xxx is awesome, I love xxx!", "awesome", "Arma"] call GOM_fnc_replaceInString ==> "xxx iArma Arma, I lArmavArma xxx!"

 

  • Like 1

Share this post


Link to post
Share on other sites

This is my version. I tried to optimize for "predictable worst case performance", but it seems to run quite well. I updated the benchmark results I ran above to include it:

MF_fnc_replaceInString = {
    params ["_str", "_find", "_replace"];
    private _matchPos = _str find _find;
    if (_matchPos isEqualTo -1) exitWith {_str};
    private _parts = [];
    private _len = count _find;
    while {_matchPos >= 0} do {
        _parts pushBack (_str select [0, _matchPos]);
        _str = _str select [_matchPos + _len];
        _matchPos = _str find _find;
    };
    _parts pushBack _str;
    _parts joinString _replace;  
};

 

  • Like 4

Share this post


Link to post
Share on other sites

@PixeL_GaMMa  I added your two new variants (I suffixed the second with '2'. PX_fnc_stringReplaceEx seems to perform well. The results are updated above. For the negative case, there is no reason why PX_fnc_stringReplaceEx should have different speed than mine, so we can safely assume the 0.001ms difference is noise (or the difference between  == and isEqualTo).

  • Like 3

Share this post


Link to post
Share on other sites
1 hour ago, Muzzleflash said:

To be honest, I don't think anyone has to beat it, since your solution is cheating and only works for properly for this particular input. Though you do admit you didn't test thoroughly. Put simply, splitString is not very useful here, and I suggest you read more carefully what it actually does. Examples

 

Ah indeed, it does filter for every single character inside the split parameter. The wiki is already clear as day on this, heh.

That basically leaves joinString as most likely the fastest way to add multiple strings.

Back to the drawingboard it is!

 

Cheers

Share this post


Link to post
Share on other sites

Awesome, thanks, I think it is good enough.... I did experiment with split string and join, it can be done but it does not perform very well. But i will add it here anyway.

 

PX_fnc_splitReplace = {
    params["_str", "_find", "_replace"];    
    private _result = ([" "+_str+" ", _find, true] call BIS_fnc_splitString) joinString _replace;    
    _result select [1, (count _result)-2];
};

The reason for the spaces is because bis splitstring will remove the found words if they are at the start or end of the string.

  • Like 1

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×