Volver al inicio

E/S sin asignaciones para concursos de Go

La biblioteca contestio optimiza la entrada-salida para concursos de Go mediante técnicas sin asignaciones. Usa acceso directo a bufio.Reader, parsers genéricos y búferes scratch. Benchmarks demuestran superioridad sobre fmt y strconv.

contestio: 6 trucos de ingeniería para E/S en concursos de Go
Advertisement 728x90

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.

Google AdInline article slot
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<<7 para int8).
  • 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.

Google AdInline article slot

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:

Google AdInline article slot
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 must reducen el código repetitivo de verificación de errores.

— Editorial Team

Advertisement 728x90

Leer después