Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manipulate strings in GO to reverse them?

I'm trying to invert a string in go but I'm having trouble handling the characters. Unlike C, GO treats strings as vectors of bytes, rather than characters, which are called runes here. I tried to do some type conversions to do the assignments, but so far I could not.

The idea here is to generate 5 strings with random characters of sizes 100, 200, 300, 400 and 500 and then invert their characters. I was able to make C work with ease, but in GO, the language returns an error saying that it is not possible to perform the assignment.

 func inverte() {
    var c = "A"
    var strs, aux string

    rand.Seed(time.Now().UnixNano())
    // Gera 5 vetores de 100, 200, 300, 400, e 500 caracteres
    for i := 1; i < 6; i++ {
        strs = randomString(i * 100)
        fmt.Print(strs)

        for i2, j := 0, len(strs); i2 < j; i2, j = i+1, j-1 {
           aux = strs[i2]
           strs[i2] = strs[j]
           strs[j] = aux
       }
   }
}
like image 337
Valney Faria Avatar asked Jan 28 '23 04:01

Valney Faria


2 Answers

If you want to take into account unicode combining characters (characters that are intended to modify other characters, like an acute accent ´ + e = é), Andrew Sellers has an interesting take in this gist.

It starts by listing the Unicode block range for all combining diacritical marks (CDM) (the Unicode block containing the most common combining characters)

  • regulars (inherited), so the usual ◌̀ ◌́ ◌̂ ◌̃ ◌̄ ◌̅ ◌̆ ◌̇ ◌̈, ...;
  • extended (containing diacritical marks used in German dialectology -- Teuthonista)
  • supplement (or the Uralic Phonetic Alphabet, Medievalist notations, and German dialectology -- again, Teuthonista)
  • for symbols (arrows, dots, enclosures, and overlays for modifying symbol characters)
  • Half Marks (diacritic mark parts for spanning multiple characters, as seen here)
var combining = &unicode.RangeTable{
    R16: []unicode.Range16{
        {0x0300, 0x036f, 1}, // combining diacritical marks
        {0x1ab0, 0x1aff, 1}, // combining diacritical marks extended
        {0x1dc0, 0x1dff, 1}, // combining diacritical marks supplement
        {0x20d0, 0x20ff, 1}, // combining diacritical marks for symbols
        {0xfe20, 0xfe2f, 1}, // combining half marks
    },
}

You can then read, rune after rune, your initial string:

sv := []rune(s)

But if you do so in reverse order, you will encounter combining diacritical marks (CDMs) first, and those need to preserve their order, to not be reversed

for ix := len(sv) - 1; ix >= 0; ix-- {
        r := sv[ix]
        if unicode.In(r, combining) {
            cv = append(cv, r)
            fmt.Printf("Detect combining diacritical mark ' %c'\n", r)
        }

(note the space around the %c combining rune: '%c' without space would means combining the mark with the first 'ͤ': instead of ' ͤ '. I tried to use the CGJ Combining Grapheme Joiner \u034F, but that does not work)

If you encounter finally a regular rune, you need to combine with those CDMs, before adding it to your reverse final rune array.

        } else {
            rrv := make([]rune, 0, len(cv)+1)
            rrv = append(rrv, r)
            rrv = append(rrv, cv...)
            fmt.Printf("regular mark '%c' (with '%d' combining diacritical marks '%s') => '%s'\n", r, len(cv), string(cv), string(rrv))
            rv = append(rv, rrv...)
            cv = make([]rune, 0)
        }

Where it gets even more complex is with emojis, and, for instance more recently, modifiers like the Medium-Dark Skin Tone, the type 5 on the Fitzpatrick Scale of skin tones.
If ignored, Reverse '👩🏾‍🦰👱🏾🧑🏾‍⚖️' will give '️⚖‍🏾🧑🏾👱🦰‍🏾👩', loosing the skin tone on the last two emojis.

And don't get me started on the ZERO WIDTH JOINER (200D), which, from Wisdom/Awesome-Unicode, forces adjacent characters to be joined together (e.g., Arabic characters or supported emoji). It Can be used this to compose sequentially combined emoji.

Here are two examples of composed emojis, whose inner elements order should remain in the same order when "reversed":

👩🏾‍🦰 alone is (from Unicode to code points converter):

  • 👩: women (1f469)
  • dark skin (1f3fe)
  • ZERO WIDTH JOINER (200d)
  • 🦰red hair (1f9b0)

Those should remain in the exact same order.

The "character" "judge" (meaning an abstract idea of the semantic value for "judge") can be represented with several glyphs or one glyph.

🧑🏾‍⚖️ is actually one composed glyph (composed here of two emojis), representing a judge. That sequence should not be inverted.
The program below correctly detect the "zero width joiner" and do not invert the emojis it combines.
It you inspect that emoji, you will find it composed of:

  • 🧑: Adult (1F9D1)
  • 🏾: dark skin (1f3fe)
  • ZERO WIDTH JOINER (200d) discussed above
  • ⚖: scale (2696)
  • VARIATION SELECTOR (FE0F), part of the unicode combining characters (characters that are intended to modify other characters), here requesting that character 'scale' to be displayed emoji-style (with color) ⚖️, using VS16 (U+FE0F), instead of text style (monochrome) '⚖', using VS15 (U+FE0E).

Again, that sequence order needs to be preserved.

Note: the actual judge emoji 👨🏾‍⚖️ uses a MAN 🧑 (1F468), instead of an Adult 🧑 (1F9D1) (plus the other characters listed above: dark skin, ZWJ, scale), and is therefore represented as one glyph, instead of a cluster of graphemes.

Meaning: the single glyph, official emoji for "judge", needs to combine "man" with "scale" (resulting in one glyph 👨🏾‍⚖️) instead of "adult" + "scale".
The latter, "adult" + "scale", is still considered as "one character": you cannot select just the scale, because of the ZWJ (Zero Width Joiner).
But that "character" is represented as a composed glyph 🧑🏾‍⚖️, two glyphs, each one a concrete written representations a corresponding grapheme through codepoint+font)

Obviously, using the first combination ("man"+"scale") results in a more expressive character 👨🏾‍⚖️.

See "The relationship between graphemes and abstract characters for textual representation"

Graphemes and orthographic characters are fairly concrete objects, in the sense that they are familiar to common users—non-experts, who are typically taught to work in terms of them from the time they first learn their “ABCs” (or equivalent from their writing system, of course).

In the domain of information systems, however, we have a different sense of character: abstract characters which are minimal units of textual representation within a given system.
These are, indeed, abstract in two important senses:

  • first, some of these abstract characters may not correspond to anything concrete in an orthography, as we saw above in the case of HORIZONTAL TAB.
  • Secondly, the concrete objects of writing (graphemes and orthographic characters) can be represented by abstract characters in more than one way, and not necessarily in a one-to-one manner, as we saw above in the case of “ô” being represented by a sequence <O, CIRCUMFLEX>.

Then: "From grapheme to codepoint to glyph":

https://scripts.sil.org/cms/scripts/render_graphic.php?&type=media_graphic&filename=../sites/nrsi/media/IWS-CharacterIntervene.png&image_type=png&width=648&height=421

  • Graphemes are the units in terms of which users are usually accustomed to thinking.
  • Within the computer, however, processes are done in terms of characters.

We don’t make any direct connection between graphemes and glyphs.
As we have defined these two notions here, there is no direct connection between them. They can only be related indirectly through the abstract characters.
This is a key point to grasp: the abstract characters are the element in common through which the others relate.


Full example in Go playground.

Reverse 'Hello, World' => 'dlroW ,olleH'
Reverse '👽👶⃠🎃' => '🎃👶⃠👽'
Reverse '👩🏾‍🦰👱🏾🧑🏾‍⚖️' => '🧑🏾‍⚖️👱🏾👩🏾‍🦰'
Reverse 'aͤoͧiͤ  š́ž́ʟ́' => 'ʟ́ž́š́  iͤoͧaͤ'
Reverse 'H̙̖ell͔o̙̟͚͎̗̹̬ ̯W̖͝ǫ̬̞̜rḷ̦̣̪d̰̲̗͈' => 'd̰̲̗͈ḷ̦̣̪rǫ̬̞̜W̖͝ ̯o̙̟͚͎̗̹̬l͔leH̙̖'
like image 62
VonC Avatar answered Feb 07 '23 19:02

VonC


As you correctly identified, go strings are immutable, so you cannot assign to rune/character values at given indices.

Instead of reversing the string in-place one must create a copy of the runes in the string and reverse those instead, and then return the resulting string.

For example (Go Playground):

func reverse(s string) string {
  rs := []rune(s)
  for i, j := 0, len(rs)-1; i < j; i, j = i+1, j-1 {
    rs[i], rs[j] = rs[j], rs[i]
  }
  return string(rs)
}

func main() {
  fmt.Println(reverse("Hello, World!"))
  // !dlroW ,olleH
  fmt.Println(reverse("Hello, 世界!"))
  // !界世 ,olleH
}

There are problems with this approach due to the intricacies of Unicode (e.g. combining diacritical marks) but this will get you started.

like image 25
maerics Avatar answered Feb 07 '23 17:02

maerics