E/S sin asignaciones para concursos en Go: Seis soluciones de ingeniería
Los desarrolladores de Go en programación competitiva suelen chocar con los límites de las herramientas estándar de E/S. fmt.Fscan asigna memoria en cada llamada, bufio.Scanner está bloqueado en modo escaneo y el análisis manual con bufio.Reader implica escribir un montón de código repetitivo. La biblioteca contestio soluciona esto trabajando directamente con el búfer interno de bufio.Reader, ofreciendo E/S sin asignaciones para todas las operaciones.
Seis soluciones clave combinan la velocidad del análisis manual, la facilidad de fmt y la flexibilidad de bufio. Maneja tipos de entrada mixtos —números, cadenas, incluso análisis carácter por carácter— sin cambios de modo.
Acceso directo al búfer: _nextToken sin copias
El truco principal es la función _nextToken, que devuelve un slice de bytes del búfer interno de bufio.Reader. Sin asignaciones ni copias. Aún puedes usar métodos de bufio.Reader como ReadString, Peek y ReadByte.
var age int
var name string
ScanInt(br, &age) // vía contestio
name, _ = br.ReadString('\n') // método estándar
name = strings.TrimSpace(name)
Las funciones de alto nivel como ScanInt y ScanWord se llevan bien con las llamadas de bajo nivel. Nada de "modo escáner" rígido: lee los datos en el orden que quieras.
Analizador universal de enteros con genéricos
Una sola función genérica _parseInt[T Int] maneja int8, uint16, int64, uint64. El compilador genera código especializado para cada tipo:
- Omite el manejo de signo para tipos sin signo.
- Incrusta constantes de rango (
absMin = 1<<7paraint8). - Bucle de dígitos personalizado hasta 20 dígitos, con fallback a
ParseUint.
func _parseIntT Int (T, error) {
unsigned := ^T(0) >= 0
// manejo de signo, bucle de dígitos, verificaciones de rango
}
El ensamblador muestra código ajustado y optimizado sin verificaciones extras. Versatilidad sin penalizaciones de rendimiento.
Salida sin asignaciones: Búfer temporal para datos dinámicos
Para escribir, usa bufio.Writer con un array scratch [32]byte adjunto. Si el búfer principal está muy lleno, rellena el búfer temporal y hace flush con un solo Write.
func _writeAppendFunc(bw *Writer, appendVal func([]byte, T) []byte, v T) error {
var buf []byte
if bw.Available() < len(bw.scratch) {
buf = bw.scratch[:0]
} else {
buf = bw.AvailableBuffer()
}
buf = appendVal(buf, v)
_, err := bw.Write(buf)
return err
}
Resultado: salida sin asignaciones para cualquier tipo soportado.
Reflexión ligera para cualquier tipo
Las funciones tipadas como ScanInt[T], ScanFloat[T], ScanWord[T] forman la API principal. Para flexibilidad, ScanAny(br, &product, &price) usa una tabla de analizadores por reflect.Kind:
var _parseAnyTab = []_parseAnyFunc{
reflect.Int: _parseIntToAny[int],
reflect.Int8: _parseIntToAny[int8],
reflect.Float32: _parseFloatToAny[float32],
reflect.String: _parseWordToAny[string],
}
La reflexión solo despacha: verifica el tipo del argumento y llama al analizador correcto. Pasa punteros a variables reutilizables fuera de los bucles: sin asignaciones.
var x int
for i := 0; i < N; i++ {
x = data[i]
PrintAny(bw, op, &x)
}
Etiquetas Must: Pánico o errores vía etiquetas de compilación
La entrada de concursos suele ser limpia, pero las verificaciones son esenciales. Olvídate de if err != nil { panic(err) } después de cada llamada: usa funciones privadas _must.
func _scanSliceT any (int, error) {
return _must(_scanSliceCommon(br, parse, a))
}
En must.go (//go:build must), _must entra en pánico ante errores salvo io.EOF. En nomust.go, devuelve error. El compilador inlinea la versión correcta.
go run -tags=must main.go # pánico
go run main.go # errores
Utilidad inline para soluciones autónomas
Plataformas como Codeforces prohíben paquetes externos. contestio-inline analiza el AST e incrusta solo las funciones contestio usadas en main.go.
contestio-inline main.go # incrusta
contestio-inline -clear main.go # restaura
Requiere un import preciso import . "github.com/aaa2ppp/contestio". La herramienta verifica la compilación, respalda archivos y soporta -tags=must.
Beneficios del inline:
- Sin dependencias.
- Solo código necesario (grafo de dependencias).
- Tu código de solución queda intacto.
Benchmarks: 1M números, búfer de 4KB
| Método | Tiempo lectura, ms | Asignaciones | Tiempo escritura, ms | Asignaciones |
|--------|-------------------|--------------|----------------------|--------------|
| fmt.Fscan/Fprint | 473 | 1 005 000 | 109 | 965 000 |
| Scanner+Atoi | 106 | 0 | - | - |
| strconv.AppendInt | - | - | 37 | 2 600 |
| contestio | 62 | 0 | 39 | 0 |
Pruebas: Intel Core i3-7100, Go 1.24.2, Windows.
Lecciones clave
- Lectura/escritura sin asignaciones vía acceso directo al búfer.
- Genéricos generan código óptimo por tipo de entero.
- Búfer temporal permite salida sin asignaciones.
- Reflexión solo para despacho, no serialización.
- Etiquetas
mustreducen el código repetitivo de verificación de errores.
— Editorial Team
Aún no hay comentarios.