Esta página lleva la tesis del programa hasta el final: aprende el representante, reconoce la familia entera. El mismo problema de la clase —tres enteros metidos en un arreglo de tamaño fijo, su suma y su máximo— resuelto por los primos de cada familia del Atlas, no solo por los diez lenguajes del núcleo.
Si entendiste la versión de C, la de Zig te resultará familiar aunque no la hayas visto nunca. Ese reconocimiento es exactamente lo que este curso quiere producir.
⚠️ Qué está verificado y qué no. Ruby, Perl y Lua se ejecutan en CI contra el mismo
casos.jsonque el núcleo, igual que las diez implementaciones de la clase (workflow Labs). Los otros 17 primos son material de lectura: su toolchain no está en el workflow, así que están escritos para ser correctos pero sin el sello de la máquina. Verificar tres de veinte no es verificarlos todos.
a b c — tres enteros separados por espaciosuma=<a+b+c> max=<el mayor>| stdin | esperado |
|---|---|
3 1 4 |
suma=8 max=4 |
10 5 2 |
suma=17 max=10 |
1 1 1 |
suma=3 max=1 |
Representantes del núcleo: Python · PHP. Ninguno de esta familia tiene un arreglo de tamaño fijo de verdad: la lista crece y encoge siempre. El "tamaño fijo" es aquí una disciplina del programador, no una garantía del lenguaje.
# Ruby no tiene arreglos de tamaño fijo: Array siempre puede crecer.
arr = STDIN.gets.split.map(&:to_i)
puts "suma=#{arr.sum} max=#{arr.max}"
use List::Util qw(sum max);
# @arr es dinámico: Perl no distingue arreglo fijo de arreglo variable.
my @arr = split ' ', <STDIN>;
printf "suma=%d max=%d\n", sum(@arr), max(@arr);
local a, b, c = io.read("n", "n", "n")
local arr = {a, b, c} -- arr[1], arr[2], arr[3]: Lua numera desde 1
local suma, maximo = 0, arr[1]
for i = 1, #arr do -- el bucle idiomático va de 1 a #arr, no de 0 a n-1
suma = suma + arr[i]
if arr[i] > maximo then maximo = arr[i] end
end
print(string.format("suma=%d max=%d", suma, maximo))
gets stdin linea
set arr [split $linea]
set suma 0
set maximo [lindex $arr 0] ;# lindex sí es base 0
foreach x $arr {
incr suma $x
if {$x > $maximo} {set maximo $x}
}
puts "suma=$suma max=$maximo"
# En R el "arreglo" es la unidad natural: sum() y max() operan sobre el vector entero.
v <- as.integer(strsplit(readLines("stdin", n = 1), " ")[[1]])
cat(sprintf("suma=%d max=%d\n", sum(v), max(v)))
Qué reconocer: los cinco leen la línea y la parten igual, pero solo uno de ellos podría fallar
al portar el código. Lua indexa desde 1: arr[1] es el primer elemento y arr[0] simplemente
no existe (vale nil), la trampa clásica cuando se traduce un bucle for i = 0 venido de C. R
comparte esa base 1 —v[1] es el primero— pero Tcl no: su lindex cuenta desde 0 pese a
parecerse en todo lo demás. Ruby y Perl ni siquiera plantean la pregunta del tamaño: sus arreglos
son dinámicos y punto, así que la aridad tres es una convención que nadie verifica. R lleva el eje
al otro extremo: no recorre nada, porque sum y max están vectorizados y consumen el vector
completo de una vez.
Representantes del núcleo: JavaScript · TypeScript.
import 'dart:io';
void main() {
final campos = stdin.readLineSync()!.trim().split(RegExp(r'\s+'));
// growable: false congela la longitud; añadir un elemento lanza en tiempo de ejecución.
final arr = List<int>.generate(3, (i) => int.parse(campos[i]), growable: false);
final suma = arr.reduce((x, y) => x + y);
final maximo = arr.reduce((x, y) => x > y ? x : y);
print('suma=$suma max=$maximo');
}
// ActionScript corre en el reproductor Flash, sin stdin: se ilustra el arreglo fijo.
package {
public class Arreglo {
public static function resumen(a:int, b:int, c:int):String {
var arr:Vector.<int> = new <int>[a, b, c];
arr.fixed = true; // lo más cercano a un arreglo fijo: la longitud queda bloqueada
var suma:int = 0;
var maximo:int = arr[0];
for (var i:int = 0; i < arr.length; i++) {
suma += arr[i];
if (arr[i] > maximo) maximo = arr[i];
}
return "suma=" + suma + " max=" + maximo;
}
}
}
Qué reconocer: los dos parten del Array elástico de JavaScript y le añaden un candado
en tiempo de ejecución, no en el tipo: growable: false en Dart y fixed = true en el
Vector.<int> de AS3. La diferencia con C o Zig es exactamente esa — aquí el tamaño fijo es una
propiedad del objeto que se comprueba al añadir, no una parte del tipo que impida compilar. AS3
además tipa el contenido (Vector.<int> solo acepta enteros), un paso intermedio entre el array
heterogéneo de JS y el arreglo homogéneo de los lenguajes de sistemas.
Representante del núcleo: Java. En la JVM el array nativo (int[]) sí
tiene longitud fija, fijada al construirlo; lo que cambia entre lenguajes es cuánto lo esconden.
fun main() {
val arr = IntArray(3) // longitud 3, inmutable como longitud; los huecos sí se escriben
readLine()!!.trim().split(Regex("\\s+")).forEachIndexed { i, s -> arr[i] = s.toInt() }
println("suma=${arr.sum()} max=${arr.max()}")
}
object Arreglo extends App {
// Array es el int[] de Java: mutable en contenido, fijo en longitud.
val arr: Array[Int] = scala.io.StdIn.readLine().trim.split("\\s+").map(_.toInt)
println(s"suma=${arr.sum} max=${arr.max}")
}
int[] arr = System.in.newReader().readLine().trim().split(/\s+/)*.toInteger()
println "suma=${arr.sum()} max=${arr.max()}"
(require '[clojure.string :as str])
(let [arr (int-array (map parse-long (str/split (str/trim (read-line)) #"\s+")))
v (vec arr)] ; el vector persistente es la estructura por defecto de Clojure
(println (format "suma=%d max=%d" (reduce + v) (apply max v))))
Qué reconocer: los cuatro acaban sobre el mismo int[] de la JVM, cuya longitud es inmutable
—arr.length es un campo, no un método—. Kotlin lo hace explícito con IntArray(3) y separa ese
tipo de List/MutableList; Scala mantiene la misma frontera entre Array mutable y List
inmutable, y aquí elige el primero justo porque el problema habla de arreglos. Groovy disuelve
la distinción tras def y la coerción automática. Clojure rompe con la familia: int-array existe
para interoperar con Java, pero lo idiomático es el vector persistente [3 1 4], que no tiene
tamaño fijo ni mutación: cada cambio devuelve una estructura nueva compartiendo memoria con la
anterior.
Representante del núcleo: C#.
// Array es la estructura mutable y de longitud fija; List sería inmutable y enlazada.
let arr : int[] = stdin.ReadLine().Trim().Split(' ') |> Array.map int
printfn "suma=%d max=%d" (Array.sum arr) (Array.max arr)
Imports System
Module Arreglo
Sub Main()
Dim campos = Console.ReadLine().Trim().Split(" "c)
Dim arr(2) As Integer ' Dim arr(2) declara el ÚLTIMO índice: son 3 huecos, 0..2
For i = 0 To 2
arr(i) = Integer.Parse(campos(i))
Next
Dim suma = 0
Dim maximo = arr(0)
For Each x In arr
suma += x
If x > maximo Then maximo = x
Next
Console.WriteLine($"suma={suma} max={maximo}")
End Sub
End Module
Qué reconocer: los tres comparten el mismo System.Array del CLR, con Length fijo desde la
construcción y System.Collections.Generic.List(Of T) como la alternativa que sí crece. VB.NET
esconde la trampa de indexación más sutil de toda esta página: Dim arr(2) no declara dos
elementos sino tres, porque el número entre paréntesis es el índice superior, no la cantidad —un
resto histórico de BASIC, donde incluso existía Option Base 1 para empezar a contar en 1. F#
enseña el otro eje: int[] es su estructura mutable de tamaño fijo y int list la inmutable, dos
tipos distintos con módulos distintos (Array.sum frente a List.sum), y el compilador no deja
confundirlos.
Representante del núcleo: C. El arreglo fijo es aquí un bloque contiguo de memoria cuyo tamaño se conoce al compilar.
#include <algorithm>
#include <array>
#include <iostream>
#include <numeric>
int main() {
std::array<int, 3> arr{}; // el 3 va en el TIPO: vive en la pila, sin reservar heap
std::cin >> arr[0] >> arr[1] >> arr[2];
const int suma = std::accumulate(arr.begin(), arr.end(), 0);
const int maximo = *std::max_element(arr.begin(), arr.end());
std::cout << "suma=" << suma << " max=" << maximo << '\n';
}
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
int arr[3]; // arreglo C puro: NSArray existiría, pero es dinámico y de objetos
scanf("%d %d %d", &arr[0], &arr[1], &arr[2]);
int suma = 0, maximo = arr[0];
for (int i = 0; i < 3; i++) {
suma += arr[i];
if (arr[i] > maximo) maximo = arr[i];
}
printf("suma=%d max=%d\n", suma, maximo);
}
return 0;
}
Qué reconocer: ambos son superconjuntos de C y el int arr[3] de la clase compila tal cual
en los dos. C++ añade la distinción que da nombre a esta clase: std::array<int, 3> lleva el tamaño
en el tipo y se asigna en la pila, mientras que std::vector<int> reserva en el heap y
crece — dos tipos separados para dos decisiones distintas de memoria, algo que C deja al
programador. Objective-C se queda en el arreglo C para los enteros porque su NSArray solo guarda
objetos y siempre es dinámico: para meter tres int habría que envolverlos en NSNumber, pagando
una indirección que el problema no pide.
Representantes del núcleo: Go · Rust. Los tres primos distinguen con precisión quirúrgica lo que ocupa un tamaño conocido de lo que se reserva en tiempo de ejecución.
const std = @import("std");
pub fn main() !void {
var buf: [128]u8 = undefined;
const linea = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')).?;
var it = std.mem.tokenizeAny(u8, std.mem.trim(u8, linea, " \r"), " ");
var arr: [3]i64 = undefined; // el tamaño es parte del tipo: [3]i64 y [4]i64 no son el mismo
for (&arr) |*hueco| {
hueco.* = try std.fmt.parseInt(i64, it.next().?, 10);
}
var suma: i64 = 0;
var maximo: i64 = arr[0];
for (arr) |x| {
suma += x;
if (x > maximo) maximo = x;
}
try std.io.getStdOut().writer().print("suma={d} max={d}\n", .{ suma, maximo });
}
import std/[strutils, sequtils, math]
let campos = stdin.readLine().splitWhitespace().map(parseInt)
var arr: array[3, int] # array[3, int] es fijo; seq[int] sería el dinámico
for i in 0 ..< 3:
arr[i] = campos[i]
echo "suma=", arr.sum, " max=", arr.max
import std.stdio, std.array, std.conv, std.algorithm;
void main() {
auto campos = readln().split();
int[3] arr; // arreglo estático: en la pila. int[] sería dinámico, en el heap
foreach (i, ref x; arr) x = campos[i].to!int;
// arr[] toma un slice del estático: vista sin copia sobre los mismos 3 enteros
writefln("suma=%d max=%d", arr[].sum, arr[].maxElement);
}
Qué reconocer: aquí el eje de la clase está escrito en la sintaxis. Zig exige el tamaño en el
tipo: [3]i64 es un tipo distinto de [4]i64, así que pasar un arreglo de la longitud
equivocada ni siquiera compila. Nim marca la misma frontera con dos nombres, array[3, int] fijo
frente a seq[int] dinámico. D distingue arreglo estático (int[3], en la pila) de
dinámico (int[], en el heap) y añade la pieza que los conecta: el slice arr[], una vista
de puntero más longitud sobre esos mismos tres enteros, sin copiarlos — la misma idea que el
&[i32] de Rust y el []int de Go.
Representante del núcleo: SQL. No hay arreglos ni índices: hay relaciones, y la posición se vuelve un dato más.
:- initialization(main, main).
main :-
read_line_to_string(user_input, Linea),
split_string(Linea, " ", "", Partes),
maplist([S, N]>>number_string(N, S), Partes, Nums),
Nums = [_, _, _], % la aridad fija se comprueba unificando, no declarando un tamaño
sum_list(Nums, Suma),
max_list(Nums, Maximo),
format("suma=~d max=~d~n", [Suma, Maximo]).
% Datalog puro no tiene E/S ni agregados: no puede sumar. Se declaran las celdas
% del arreglo como hechos (índice, valor) y se deriva el máximo por negación.
celda(0, 3).
celda(1, 1).
celda(2, 4).
superado(X) :- celda(_, X), celda(_, Y), Y > X.
maximo(X) :- celda(_, X), not superado(X).
Qué reconocer: ninguno de los dos tiene arreglo de tamaño fijo, y esa ausencia es informativa.
Prolog usa listas enlazadas, así que el tamaño no se declara: se comprueba unificando con
el patrón [_, _, _], que falla si llegan dos o cuatro elementos — la aridad como consulta, no
como tipo. Datalog va más lejos: sin listas ni agregados estándar, un arreglo solo se puede modelar
como una relación celda(índice, valor), exactamente lo que hace SQL cuando guardas una secuencia
en una tabla con columna de orden. El índice deja de ser una dirección de memoria y pasa a ser un
atributo, que es justo por qué en esas dos familias la pregunta "¿empieza en 0 o en 1?" pierde todo
su sentido.
Veinte lenguajes, un solo problema, y tres preguntas que cada familia responde distinto: ¿el tamaño está en el tipo o en el objeto?, ¿el arreglo vive en la pila o en el heap?, ¿el primer elemento es el 0 o el 1? Reconocer cuál de las tres te va a morder al portar código es lo transferible.