PixeL_GaMMa 31 Posted May 12, 2018 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 4 Share this post Link to post Share on other sites
HazJ 1289 Posted May 12, 2018 Very useful. I don't remember of any official command or function. 2 Share this post Link to post Share on other sites
PixeL_GaMMa 31 Posted May 13, 2018 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
7erra 629 Posted May 13, 2018 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 2 Share this post Link to post Share on other sites
Muzzleflash 111 Posted May 13, 2018 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
Grumpy Old Man 3549 Posted May 13, 2018 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. Cheers 2 Share this post Link to post Share on other sites
PixeL_GaMMa 31 Posted May 13, 2018 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; > 2 Share this post Link to post Share on other sites
Grumpy Old Man 3549 Posted May 14, 2018 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; 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; Cheers 5 Share this post Link to post Share on other sites
johnnyboy 3799 Posted May 14, 2018 2 hours ago, Grumpy Old Man said: You young kids and your loops, get off my lawn! lol 1 3 Share this post Link to post Share on other sites
7erra 629 Posted May 14, 2018 4 hours ago, Grumpy Old Man 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. Share this post Link to post Share on other sites
Muzzleflash 111 Posted May 14, 2018 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. 2 Share this post Link to post Share on other sites
Harzach 2518 Posted May 14, 2018 Not to confuse matters, but there is also Kronzky's String Library, if you want to make another comparison. Share this post Link to post Share on other sites
dr death jm 117 Posted May 14, 2018 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
Harzach 2518 Posted May 14, 2018 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
PixeL_GaMMa 31 Posted May 14, 2018 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; }; 1 Share this post Link to post Share on other sites
Grumpy Old Man 3549 Posted May 15, 2018 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 1 Share this post Link to post Share on other sites
PixeL_GaMMa 31 Posted May 15, 2018 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 :) 1 Share this post Link to post Share on other sites
Grumpy Old Man 3549 Posted May 15, 2018 Good news, everyone! Spoiler 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; 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
Muzzleflash 111 Posted May 15, 2018 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!" 1 Share this post Link to post Share on other sites
Muzzleflash 111 Posted May 15, 2018 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; }; 4 Share this post Link to post Share on other sites
Muzzleflash 111 Posted May 15, 2018 @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). 3 Share this post Link to post Share on other sites
Grumpy Old Man 3549 Posted May 15, 2018 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
PixeL_GaMMa 31 Posted May 15, 2018 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. 1 Share this post Link to post Share on other sites
HazJ 1289 Posted May 15, 2018 Why BIS_fnc_splitString? Engine command solution: https://community.bistudio.com/wiki/splitString Or did I miss something? Share this post Link to post Share on other sites
PixeL_GaMMa 31 Posted May 15, 2018 yep, check third param of BIS_fnc_splitString 1 Share this post Link to post Share on other sites