How to Reverse Characters in a Variable

Guys, this is just what I need. I was looking online at ways to do it in javascript - but it was going over my head and couldn’t get it to work even when passing the KME variable. This is great though, both approaches work well. Many thanks.

Nice, creative solution!

Here's the basic JXA/JavaScript solution. Of course you will need to add the call to the KM app to get the KM variable.

function reverse(str){
  return str.split("").reverse().join("");
}

Questions?

2 Likes

Imagine we have a km variable called activeLang

(() => {

    const kme = Application('Keyboard Maestro Engine');

    // kmVar reversed value
    const raVkm = strVarName => [...kme.getvariable(strVarName)]
        .reverse().join('');

    return raVkm('activeLang');

    //-> e.g. 'YTREWQ-werbeH.tuoyalyek.elppa.moc'
})();

2 Likes

Alternatively a 'right fold' (reduceRight in JavaScript), also naturally produces a reversal:

(() => {

    const kme = Application('Keyboard Maestro Engine');

    const kmReverse = strVarName =>  [...kme.getvariable(strVarName)]
        .reduceRight((a, b) => a + b, '');


    return kmReverse('activeLang');

    //-> e.g. 'YTREWQ-werbeH.tuoyalyek.elppa.moc'
})();
3 Likes

If you only have singlebyte characters in your input (for example [a-z][A-Z][0-9]) you can also get away with a very nice and short Perl solution:

39-pty-fs8

#!/usr/bin/env perl

use strict;
use warnings;

$_ =  reverse <>;
print;

Or as a command-line one-liner:

perl -pe '$_ = reverse'

[Skip the rest of this post if you are not interested in reversing any kind of UTF characters.]


The pitfall (or: “what I have learned today :wink: ) is that this fails to a different degree if the string contains multibyte UTF characters like ä, é, ợ, đ etc.

For the more simple things like Käse, résumé etc. it seems to be sufficient to explicitly set the stdin mode to “utf8” with

binmode STDIN, ':utf8';

With this modification, the Perl script from above produces correctly esäK and émusér.

But with more “sophisticated” strings like Rượu đế this is not enough.


So far the only solutions I have found to work with any kind of strings are these:

1) — Splitting the string with the \X special (“Match Unicode eXtended grapheme cluster”):

#!/usr/bin/env perl

use strict;
use warnings;

binmode STDIN, ':utf8';

$_ = join('', reverse <> =~ /\X/g);
print;

From perldoc.perl.org

2) — Or using the Unicode::GCString module and reversing the result as list:

#!/usr/bin/env perl

use strict;
use warnings;
use Unicode::GCString;

binmode STDIN, ':utf8';

my $str = Unicode::GCString->new(<>);
$_ = join '', reverse @{ $str->as_arrayref };
print;

From Stack Overflow


As test case I have used the nice Vietnamese word Rượu đế, found in a related discussion on perlmonks.org.

The correct reverse string is ếđ uợưR.

Depending on the used technique, I’ve seen two different wrong results:

  • Ì‚Ìe‘Ä u£Ì›Ìo›ÌuR when using the multibyte incompatible script at the top of this post, and
  • ́̂eđ ựơuR when only setting the binmode to utf8.

If you want to try the scripts above:

All the scripts are written to be used in a KM Shell Script action with input from a variable (stdin), as shown in the screenshot at the top.
You should also set the output to a (non-local) variable, because the KM Shell Script Results window doesn’t show all characters correctly. (At least on my computer.)

Edit/PS:

If you want to test a script with the Vietnamese test string, you have to copy it from the following line (do not copy one of the inline instances in the text!) :

Rượu đế

Although the string looks different here, this will maintain the original composition of the characters. Once you paste the string into a Unicode-compliant editor (and using a compliant font) on your computer it will again look like “ Rượu đế ”, but with the correct composition.

Alternatively you can copy the string from inside this macro.

4 Likes

Innovative. :smile:

But lets give the newbies something a little more approachable.

tell application "Keyboard Maestro Engine" to ¬
	reverse of characters of (getvariable "INPUTVAL") as text

-Chris

1 Like

I see your point, and I think I just copied and pasted it at the time from the AppleScript libraries I’m writing, and happened to be updating my _Text library.

When stored as a handler, the reason why I elect for my method over the more obvious is because it avoids any possible interactions with text item delimiters.

So, whilst it appears mildly absurd in this context, there was reason behind the madness.

1 Like

Hi Chris,

Even if it was not your intention, your solution has another advantage: Besides the two Perl scripts from the second part of my post above, it is the only solution on this thread that works correctly with my test string from above. (Rượu đế --> ếđ uợưR) :+1:

2 Likes

Hey @CJK,

Characters, words, and paragraphs are completely separate from AppleScript's text item delimiters.

-Chris

Yes, I know. But this wouldn't be:

Hey @CJK,

Good point.

Even so – esoterica doesn't really have an advantage over meat and potatoes in this case.

I haven't seen an AppleScriptObjC method for doing this. Have you?

-Chris

----------------------------------------------------------------

set dataStr to "Now is the time for all good men to come to the aid of their country."
set newStr to reverseStr(dataStr)

----------------------------------------------------------------
--» HANDLERS
----------------------------------------------------------------
on reverseStr(dataStr)
   set {oldTIDS, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ""}
   set revStr to (reverse of (characters of dataStr)) as text
   set AppleScript's text item delimiters to oldTIDS
   return revStr
end reverseStr
----------------------------------------------------------------
1 Like

Totally agree, and I endorse your solution over mine, both for simplicity and, as @Tom stated, for the fact that you were wise enough to use getvariable, which handles diacritics with ease. I wasn't trying to be a smart ass with my 5-course dinner of an AppleScript. It just coincided with the moment I happened to be writing a handler that did that particular text transformation. I actually believe simplicity is generally better for several reasons, unless there's a specific reason to employ more obscure methods. I remember deleting a post of mine off this forum because I went back to read the AppleScript I had supplied and, although ti worked, I was disgusted with myself for how obnoxiously opaque the whole thing was for no good reason.

How about:

set dataStr to "Now is the time for all good men to come to the aid of their country."
set newStr to reverseStr(dataStr)

on reverseStr(dataStr)
    character id (reverse of dataStr's id)
end reverseStr

Not a builtin method, but I've no doubt you already thought of this:

use framework "Foundation"

set str to "Now is the time for all good men to come to the aid of their country."
set NSstr to current application's NSArray's arrayWithArray:(characters of str)
NSstr's reverseObjectEnumerator()'s allObjects() as list as text
1 Like

Hey @CJK,

Hmm... That method is fully unicode compliment, and it's actually a bit faster than decomposing the characters and using AppleScript's text item delimiters.

Okay, you've got a winner.  :smile:

I knew Shane's BridgePlus library had such a method, so I was afraid that Objective C lacked one.

(Bad Apple!)

-Chris

1 Like

On my machine, it isn’t.

It works correctly with the “simple” kind of strings like résumé, but fails with the Rượu đế test string from above. (The diacritics ́̂ and ̛̣ get detached from the letters they belong to.)

(The AppleScriptObjC method works correctly with all.)



For ease of testing, here a macro with all posted solutions so far:

[test] Ways to Reverse String.kmmacros (18.5 KB)

  1. Open the macro in KM Editor.
  2. Use “Try Action” from the contextual menu on the first action to set the variable.
  3. Open KM Editor > Preferences > Variables and select the “INPUTVALREVERSE” variable. Leave that window open and visible on screen.
  4. Now apply “Try Action” on a script action you want to test and watch the output in the variables window.

As said, for display I’m using the variables window, because the font (Menlo) used in the KM Shell Script Results window cannot handle all characters correctly.

The colors of the actions reflect the grade of Unicode compliance of the script:

  • red: fails completely
  • orange: OK with résumé | Käse, fails with Rượu đế
  • green: OK with all

Untitled-pty-fs8

The green actions should produce this result:

07-pty-fs8

2 Likes

Thanks for sharing, Tom. This is a very useful tool.

From my perspective, I prefer Chris' (@ccstone) AppleScript to be the best for all of my use cases:

It is fast and seems to work with all unicode characters. We shouldn't be afraid to use AppleScript Text Item Delimiters (TID), just learn how to use them properly, always setting and restoring.
I find that encapsulating TID in a handler, like Chris did, easily removes any issue with using TID.

BTW, it is very disappointing the ASObjC does not offer a native reverse() function for a string. Since using it also requires set/restore of TID, I see no advantage in using it.

Assuming I know what it means for a diacritic to become detached, then I don't see this happening in my testing. As far as I can tell, the handler I gave to @ccstone handles the string you supplied correctly:

I agree with @JMichaelTX, in that the fact one still needs to be mindful of the text item delimiters does take away from both the appeal and the simplicity of implementing it. There's also the overhead of using ASObjC methods, but if the rest of the script is already using them, that's less of a concern. However, I wonder whether it brings with it any actual performance benefits. My gut feeling is that, particularly for more commonly-sized strings that most scripts are likely to handle, vanilla AppleScript is going to be faster than ASObjC.

@ccstone said my alternative method was a tad faster, which makes sense just from the number of operations each method is performing. But I'll wait to hear back from @Tom with advice on how to reproduce the error he's seeing, as I'm not getting the same result.

@Tom, just to clarify, are you still utilising the macro code I posted in the first instance ? That one did indeed have an issue with those special characters, although that was down to my whimsical choice to use system attributes to read the value of a Keyboard Maestro variable, rather than tell app "Keyboard Maestro Engine"....

I really like this communal debugging and swapping of ideas and different methodologies. I wish there were more of this somewhere. The Latenight Forum is a good place for educational tips, but doesn't have this feel of collaboration that occurs more often in this forum.

On a parting note, if you wish to circumvent the text item delimiters, you can opt to coerce, for example, the ASObjC's Cocoa object to a linked list instead of a list, i.e.:

NSstr's reverseObjectEnumerator()'s allObjects() as linked list as text

That way, the subsequent coercion to text concatenates the list items without using a delimiter. This method applies equally to lists created from exploding a string into its characters:

(reverse of characters of dataStr) as linked list as text

Weird. I’ve run it in Script Editor now and still get the same (wrong) result:

33-pty-fs8

I think you see what I meant with “detached”: The ̛̣ went to the “u” (formerly on the “o”) and the ́̂ went before the “e” (formerly on the “e”).

Have you tried my macro from above? The script actions I have marked orange and red, are they also producing correct results on your system?

No, your very first version produces this (on my system!):

ÅÃÇÃeëƒ u£ÃõÃoõÃuR

I do see what you mean, and that's odd that it's happening for you and not for me. Here are my system details: System info: AppleScript version: 2.7 System version: 10.13.6, although I don't think that's especially pertinent to what's going on. As some diacritics are character modifiers that operate upon a preceding character, it almost seems like the reversal has caused the diacritics to have been removed for the text transformation, then reapplied afterwards when the base characters were in reverse order. Whether that would be the AppleScript engine processing the characters wrongly, or the textviews of Script Editor drawing them wrongly, I couldn't guess. It could even be the fonts being used in Script Editor, some of which could lack full unicode support. You could try Script Debugger, though I suspect the result will be the same as it uses the same AppleScript engine, the same compiler, the same fonts, ...

Yes, there we match. And that result is perfectly expected from what I know about Keyboard Maestro and the pros and cons of the various different methods of reading its variable data.

I have not as yet, but I will do so and report back my findings.

But I did try it in a shell, which can be fussy with non-Roman characters, but it seems happy with them in this instance:

In the shell I’m also getting the same as with Script Editor or KM:

26-pty-fs8

(I appended a space to the string for better visibility.)

I’m using DejaVu in Script Editor (certainly one of the most Unicode-capable fonts) and SF Mono in the Terminal.

If it’s a font problem you should see it already with the input string, for example:

57-pty-fs8

Yes, Script Debugger gives the same.


I’m on macOS 10.14.1, AppleScript version 2.7.

This is the shell env:

TERM_PROGRAM=Apple_Terminal
SHELL=/bin/bash
TERM=xterm-256color
CLICOLOR=1
TMPDIR=/var/folders/wn/28w_v3513m50gcc9qtvg3bfh0000gn/T/
PERL5LIB=/Users/tom/perl5/lib/perl5
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.h6zvb2xn2V/Render
TERM_PROGRAM_VERSION=421.1
PERL_MB_OPT=--install_base "/Users/tom/perl5"
TERM_SESSION_ID=0645EEDB-5FE2-470F-BE4C-4E601362C25E
LC_ALL=en_US.UTF-8
USER=tom
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.yB3a2jkO6w/Listeners
PATH=/Users/tom/perl5/bin:/Users/tom/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/Users/tom/ConTeXt/Beta/tex/texmf-osx-64/bin:/Applications/VMware Fusion.app/Contents/Public:/opt/X11/bin
PWD=/Users/tom
EDITOR=/usr/local/bin/bbedit
LANG=en_US.UTF-8
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
SHLVL=1
HOME=/Users/tom
PERL_LOCAL_LIB_ROOT=/Users/tom/perl5
LOGNAME=tom
VISUAL=/usr/local/bin/bbedit
LC_CTYPE=UTF-8
DISPLAY=/private/tmp/com.apple.launchd.zmpTruViEy/org.macosforge.xquartz:0
PERL_MM_OPT=INSTALL_BASE=/Users/tom/perl5
_=/usr/bin/env

One thing that changes the behavior in the Terminal is the “wide” setting for East Asian in the preferences. But selecting it makes it worse.

45-pty-fs8