row.split(",") is clear, but it creates an Array and a String per field. On a CSV of millions of rows those allocations are about half of the time. string_to_int_range(s, start, end, def) and string_to_float_range(s, start, end, def) parse the number in bytes [start, end) of the text without creating the substring, and always give the same result as string_to_X_or(s.substring(start, end), def).
// 145: CSV grande sin split() por fila — indexOf + string_to_X_range // // `fila.split(",")` crea un Array y un String por campo: en un CSV de millones // de filas esas asignaciones son la mitad del tiempo (medido 2026-10-25, 5M // filas). string_to_int_range / string_to_float_range parsean el número // directo de los bytes [ini, fin) del texto, sin crear el substring. Dan // siempre lo mismo que string_to_X_or(s.substring(ini, fin), def). fn main() -> int { // Un CSV chico armado en memoria (en la vida real: read_file(ruta)). let texto: String = "id,ciudad,valor,cantidad\n1,lima,10.5,3\n2,quito,99.25,1\n3,lima,0.75,10\n" // Estilo split: claro, pero aloca un Array + un String por campo y por fila. var total_split: float = 0.0 let lineas: Array = texto.split("\n") var i: int = 1 while i < lineas.length() { let linea: String = lineas[i] i = i + 1 if linea.length() == 0 { continue } let campos: Array = linea.split(",") total_split = total_split + string_to_float(campos[2]) } // Estilo rango: las comas se ubican con indexOf y los números se parsean // en su lugar. Solo la ciudad, que sí se guarda, se copia con substring. var total: float = 0.0 var unidades: int = 0 var por_ciudad: Map<String, float> = Map.new() let n: int = texto.length() var p: int = texto.indexOf("\n") + 1 // saltar la cabecera while p < n { var fin: int = texto.indexOf("\n", p) if fin < 0 { fin = n } let c1: int = texto.indexOf(",", p) let c2: int = texto.indexOf(",", c1 + 1) let c3: int = texto.indexOf(",", c2 + 1) let ciudad: String = texto.substring(c1 + 1, c2) // El default (0.0 / 0) entra si el campo viene vacío o no es número. let valor: float = string_to_float_range(texto, c2 + 1, c3, 0.0) unidades = unidades + string_to_int_range(texto, c3 + 1, fin, 0) total = total + valor por_ciudad[ciudad] = por_ciudad.get_or(ciudad, 0.0) + valor p = fin + 1 } print("total con split: " + float_to_string(total_split)) print("total con rango: " + float_to_string(total)) print("unidades: " + int_to_string(unidades)) print("lima: " + float_to_string(por_ciudad["lima"])) return 0 }
total con split: 110.5 total con rango: 110.5 unidades: 14 lima: 11.25
How it works
The recipe adds up the same column twice. The split style splits the text into lines and each line into fields. The range style walks the text once: texto.indexOf("\n", p) finds the end of the row and three indexOf(",", …) calls find the commas, all with the absolute offset that indexOf(s, from) returns. The numbers are parsed in place; only the city, which is kept as a map key, is copied with substring.
The default (0.0 or 0) is used when the field is empty or is not a number, and an integer that does not fit in 64 bits also counts as invalid instead of being clamped. When the columns and their types are known in advance, the columnar reader of recipe 146 does the whole pass in C and is the next step up.