| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 | // Package domain provides the domain logic for the extended FizzBuzz computation.package domainimport (	"errors"	"fmt"	"math"	"regexp"	"strconv")type (	Empty     struct{}	modulo    int64	moduloMap map[modulo]struct{})const (	Fizz = "fizz"	Buzz = "buzz"	Both = Fizz + Buzz)var (	// RFC3986GenDelims matches the reserved characters in RFC3986 §2.2	//	// See https://www.rfc-editor.org/rfc/rfc3986#section-2.2	RFC3986GenDelims = regexp.MustCompile(`[:|/\?#\[\]@]`)	Void             Empty)// IsValid returns true if the received is in the open interval ]1,limit[,// and is not already present in the existing map.func (m modulo) IsValid(limit modulo, existing moduloMap) bool {	if m <= 1 || m > limit {		return false	}	if _, found := existing[m]; found {		return false	}	return true}// FizzBuzzes is an ordered list of FizzBuzz strings;type FizzBuzzes []string// validateNumbers fails if either of the numbers fails validation.//// A successful return means that limit is >= 1 and int1 and int2 are different and within [1,limit[.func validateNumbers(int1, int2, limit int) error {	existing := make(moduloMap, 2)	l := modulo(limit)	if !l.IsValid(math.MaxInt, existing) {		return errors.New("limit is invalid")	}	m1 := modulo(int1)	if !m1.IsValid(l, existing) {		return errors.New("int1 is invalid")	}	existing[m1] = Void	m2 := modulo(int2)	if !m2.IsValid(l, existing) {		return errors.New("int2 is invalid")	}	return nil}func validateStrings(str1, str2 string) error {	validateString := func(s string) error {		if len(s) == 0 {			return fmt.Errorf("invalid string parameter: empty")		}		if RFC3986GenDelims.MatchString(s) {			return fmt.Errorf("contains a RFC-3986 reserved character")		}		return nil	}	ss := [...]string{str1, str2}	for i, s := range ss {		if err := validateString(s); err != nil {			return fmt.Errorf("validateString(%d): %w", i, err)		}	}	return nil}// FizzBuzz builds a list of strings with numbers in the closed interval [1,limit], where:////   - all multiples of int1 are replaced by str1, except those that are also multiples of int2//   - all multiples of int2 are replaced by str2, except those that are also multiples of int1//   - all multiples of int1 and int2 are replaced by str1str2.func FizzBuzz(int1, int2, limit int, str1, str2 string) (FizzBuzzes, error) {	if err := validateNumbers(int1, int2, limit); err != nil {		return nil, fmt.Errorf("invalid modulo parameters: %w", err)	}	if err := validateStrings(str1, str2); err != nil {		return nil, fmt.Errorf("invalid string parameters: %w", err)	}	both := str1 + str2	fbs := make(FizzBuzzes, 0, limit)	// Arithmetic for loop on append to a preallocated slice is usually faster than	// indexed access as in fbs := make(FizzBuzzes, limit); loop on fbs[i] = <value>.	for i := 1; i <= limit; i++ {		// The switch is more readable than the usual nested "if" solution.		switch {		case i%int1 == 0 && i%int2 == 0:			fbs = append(fbs, both)		case i%int1 == 0:			fbs = append(fbs, str1)		case i%int2 == 0:			fbs = append(fbs, str2)		default:			fbs = append(fbs, strconv.Itoa(i))		}	}	return fbs, nil}
 |