Arpith Siromoney đź’¬

Variables, Scopes and fiddling with Strings in Go

Some more fun with strings! I’m finally weaning myself off Sscanf and onto encoding/hex, so this time I’ll decode the original string with

    h := os.Args[1]
    bytes, e := hex.DecodeString(h)
    if e != nil {
            panic(e)
    }

Note that while the parentheses (around e != nil) aren’t required, the brackets are. That page also mentions

A statement can precede conditionals; any variables declared in this statement are available in all branches.

Which made me wonder, where are variables available? The Golang Book's page on variables says

According to the language specification “Go is lexically scoped using blocks”. Basically this means that the variable exists within the nearest curly braces { } (a block) including any nested curly braces (blocks), but not outside of them.

And the spec explains

Go is lexically scoped using blocks:

  1. The scope of a predeclared identifier is the universe block.
  2. The scope of an identifier denoting a constant, type, variable, or function (but not method) declared at top level (outside any function) is the package block.
  3. The scope of the package name of an imported package is the file block of the file containing the import declaration.
  4. The scope of an identifier denoting a method receiver, function parameter, or result variable is the function body.
  5. The scope of a constant or variable identifier declared inside a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl for short variable declarations) and ends at the end of the innermost containing block.
  6. The scope of a type identifier declared inside a function begins at the identifier in the TypeSpec and ends at the end of the innermost containing block.

Looking for ShortVarDecl and I found

Short variable declarations may appear only inside functions. In some contexts such as the initializers for “if”, “for”, or “switch” statements, they can be used to declare local temporary variables.

Which took me to the section on if statements

The expression may be preceded by a simple statement, which executes before the expression is evaluated.

Looks like it’s not just variable declarations:

SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .

Back to the strings though, I’m interested in how many times each character appears in a string. The blog post on strings that I linked to in my last post talks about using a range clause

For a string value, the “range” clause iterates over the Unicode code points in the string starting at byte index 0. On successive iterations, the index value will be the index of the first byte of successive UTF-8-encoded code points in the string, and the second value, of type rune, will be the value of the corresponding code point. If the iteration encounters an invalid UTF-8 sequence, the second value will be 0xFFFD, the Unicode replacement character, and the next iteration will advance a single byte in the string.

So I’m going to store a count for every rune I encounter, and a map looks fine for that. This blog post on maps mentions

If the requested key doesn’t exist, we get the value type’s zero value. In this case the value type is int, so the zero value is 0

I’ve also imported unicode because I’m only interested in letters and don’t care what case they are in. I happen to be keeping track of non-useful characters so I have a score (a float(64!) because I’m going to have to do a little math later).

    score := 0.0
    charCount := make(map[rune]int)
    for _, char := range plaintext {
            if unicode.IsLetter(char) {
                    if !unicode.IsLower(char) {
                            char = unicode.ToLower(char)
                    }
                    charCount[char] += 1
            } else if char != ' ' {
                    score += 1
            }
    }

Specifically, I’m looking at the frequencies of common English letters, and comparing it with what you might expect to get (math.Abs handles float64's)

    score += math.Abs(length*freq/100 - float64(charCount[char]))

And that’s about it for today.