Avatar of pstibrany

pstibrany's solution

to Acronym in the Go Track

Published at Jul 13 2018 · 5 comments
Instructions
Test suite
Solution

Note:

This solution was written on an old version of Exercism. The tests below might not correspond to the solution code, and the exercise may have changed since this code was written.

Convert a phrase to its acronym.

Techies love their TLA (Three Letter Acronyms)!

Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG).

Running the tests

To run the tests run the command go test from within the exercise directory.

If the test suite contains benchmarks, you can run these with the --bench and --benchmem flags:

go test -v --bench . --benchmem

Keep in mind that each reviewer will run benchmarks on a different machine, with different specs, so the results from these benchmark tests may vary.

Further information

For more detailed information about the Go track, including how to get help if you're having trouble, please visit the exercism.io Go language page.

Source

Julien Vanier https://github.com/monkbroc

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

acronym_test.go

package acronym

import (
	"testing"
)

func TestAcronym(t *testing.T) {
	for _, test := range stringTestCases {
		actual := Abbreviate(test.input)
		if actual != test.expected {
			t.Errorf("Acronym test [%s], expected [%s], actual [%s]", test.input, test.expected, actual)
		}
	}
}

func BenchmarkAcronym(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for _, test := range stringTestCases {
			Abbreviate(test.input)
		}
	}
}

cases_test.go

package acronym

// Source: exercism/problem-specifications
// Commit: 5ae1dba Acronym canonical-data: Remove redundant test case
// Problem Specifications Version: 1.3.0

type acronymTest struct {
	input    string
	expected string
}

var stringTestCases = []acronymTest{
	{
		input:    "Portable Network Graphics",
		expected: "PNG",
	},
	{
		input:    "Ruby on Rails",
		expected: "ROR",
	},
	{
		input:    "First In, First Out",
		expected: "FIFO",
	},
	{
		input:    "GNU Image Manipulation Program",
		expected: "GIMP",
	},
	{
		input:    "Complementary metal-oxide semiconductor",
		expected: "CMOS",
	},
}
package acronym

import (
	"bytes"
	"unicode"
)

const testVersion = 1

// abbreviate creates abbreviation of input
func abbreviate(input string) string {
	out := &bytes.Buffer{}

	inWord := false
	wasUppercase := false
	for _, r := range input {
		if unicode.IsUpper(r) {
			if !wasUppercase {
				inWord = true
				wasUppercase = true
				out.WriteRune(unicode.ToUpper(r))
			}
		} else if unicode.IsLetter(r) {
			wasUppercase = false
			if !inWord {
				inWord = true
				out.WriteRune(unicode.ToUpper(r))
			}
		} else {
			wasUppercase = false
			inWord = false
		}
	}
	return out.String()
}

Community comments

Find this solution interesting? Ask the author a question to learn more.
Avatar of zerok

Nice! Didn't know that iterating over a string returned its runes <3

Regarding "out": You can actually just to var out bytes.Buffer without the pointer or the initialisation :-)

Avatar of pstibrany
pstibrany
Solution Author
commented over 3 years ago

Thanks :) Why does var out bytes.Buffer work? All buffer operations are defined on bytes.Buffer pointer. I guess Go will implicitly take variable address in this case?

Btw, I've learned about iterations over runes here: https://blog.golang.org/strings

Avatar of zerok

The method set of any other type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).

You are absolutely correct :/ I don't understand this behaviour either but it's part of the official documentation for the bytes.Buffer type. I also tried it with a little custom struct against Go 1.5 - 1.7 and it worked there as well. To be honest, I'm quite happy that it behaves like that. Pointer methods not being available for non-pointer variables always felt weird to me.

Avatar of zerok

I asked in the Gopher Slack group about this and got directed to the specification on how method calls are handled instead of just the method-set definition:

https://golang.org/ref/spec#Calls

A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m()

I guess that explains it :-)

Avatar of pstibrany
pstibrany
Solution Author
commented over 3 years ago

Yes, when you pointed out that it works that way, I remembered reading about it in The Go Programming Language book... they talk about what "x is addressable" means there in bit more detail.

This works only for variables, including struct fields like p.X and array or slice elements like perim[0]. We cannot call a *Point method on a non-addressable Point receiver, because there’s no way to obtain the address of a temporar y value.

Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literal

(From section 6.2. Methods with a Pointer Receiver).

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?