How to Reverse Characters in a Variable

Hi - I know this is likely a very simple solution, but when searching here I could only find "reverse words in a string", whereas I'm looking for a much simpler "reverse characters in a variable" function.

I have a variable of 8 numbers input by user, for instance "01234567" assigned as INPUTVAL, and I would like to automatically assign "76543210" to INPUTVALREVERSE in KM.

Many thanks in advance.

set input to system attribute "KMVAR_INPUTVAL"

script
	property t : id of input
end script

character id (reverse of result's t)
3 Likes

By far not as elegant as @CJK’s solution, but for the sake of adding a KM-“native” solution, here a KM-“native” solution:

48-pty-fs8

33-pty-fs8

[demo] Reverse Variable Content.kmmacros (3.8 KB)

(If you need global variables, just remove the “Local__” from each instance.)

5 Likes

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