Esta página lleva la tesis del programa hasta el final: aprende el representante, reconoce la familia entera. El mismo problema de la clase —invertir una cadena— resuelto por los primos de cada familia del Atlas, no solo por los diez lenguajes del núcleo.
Si entendiste la versión de Python, la de Ruby 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.
invertido=<la palabra al revés>| stdin | esperado |
|---|---|
hola |
invertido=aloh |
Ada |
invertido=adA |
abc |
invertido=cba |
Representantes del núcleo: Python · PHP. En esta familia la cadena es un objeto con métodos propios, y casi siempre existe uno que hace el trabajo entero.
w = STDIN.gets.strip
puts "invertido=#{w.reverse}"
chomp(my $w = <STDIN>);
print "invertido=", scalar reverse($w), "\n";
local w = io.read("l")
print("invertido=" .. w:reverse())
gets stdin w
puts "invertido=[string reverse [string trim $w]]"
w <- readLines("stdin", n = 1)
cat(sprintf("invertido=%s\n", paste(rev(strsplit(w, "")[[1]]), collapse = "")))
Qué reconocer: aquí se ve de golpe si la cadena es un tipo con identidad propia o solo una
secuencia disfrazada. Ruby y Lua la tratan como objeto y le piden reverse directamente. Perl la
trata como escalar: reverse es una función de lista, y hace falta scalar para forzarla al modo
cadena —el sigilo $ manda sobre la operación—. Tcl es el extremo del todo es cadena: string
reverse no es un método sino un subcomando del comando string. R rompe el patrón porque no tiene
tipo carácter: hay que descomponer la cadena en un vector de un carácter cada uno, invertir el
vector y volver a pegarlo.
Representantes del núcleo: JavaScript · TypeScript.
import 'dart:io';
void main() {
final w = stdin.readLineSync()!.trim();
print('invertido=${w.split('').reversed.join()}');
}
// ActionScript corre en el reproductor Flash, sin stdin: se ilustra la inversión.
package {
public class Cadena {
public static function invertir(w:String):String {
return "invertido=" + w.split("").reverse().join("");
}
}
}
Qué reconocer: ninguno de los dos tiene un método reverse sobre String, igual que JavaScript.
El rodeo es siempre el mismo —split para convertir la cadena en lista, invertir la lista, join
para volver a cadena—, y delata que en esta familia la cadena es inmutable y solo el arreglo
admite reordenarse en sitio. Dart marca la diferencia con reversed, que devuelve una vista perezosa
en vez de mutar, mientras que el reverse() de ActionScript modifica el arreglo original.
Representante del núcleo: Java. Todos comparten la misma java.lang.String
inmutable; lo que cambia es qué añade cada lenguaje encima.
fun main() {
val w = readLine()!!.trim()
println("invertido=${w.reversed()}")
}
object Cadena extends App {
val w = scala.io.StdIn.readLine().trim
println(s"invertido=${w.reverse}")
}
def w = System.in.newReader().readLine().trim()
println "invertido=${w.reverse()}"
(require '[clojure.string :as str])
(println (str "invertido=" (str/reverse (str/trim (read-line)))))
Qué reconocer: Java necesita new StringBuilder(w).reverse().toString() porque String no tiene
el método; los cuatro primos lo añaden por fuera sin tocar la clase original —extensión en Kotlin,
conversión implícita a StringOps en Scala, metaclase en Groovy, función libre en Clojure—. Es el
mismo objeto de la JVM visto con cuatro estrategias distintas para ampliarlo. Scala además revela que
para él la cadena es una colección: reverse es el mismo método que usarías sobre una lista.
Representante del núcleo: C#.
let w = stdin.ReadLine().Trim()
let invertido = w.ToCharArray() |> Array.rev |> System.String
printfn "invertido=%s" invertido
Module Cadena
Sub Main()
Dim w = Console.ReadLine().Trim()
Dim c = w.ToCharArray()
Array.Reverse(c)
Console.WriteLine("invertido=" & New String(c))
End Sub
End Module
Qué reconocer: los tres pasan por el mismo puente, ToCharArray, porque en el CLR String es
inmutable y Char[] no: la cadena hay que abrirla a arreglo para poder reordenarla. La diferencia
está en el estilo: Array.Reverse de VB.NET muta el arreglo en sitio y no devuelve nada, mientras
Array.rev de F# devuelve uno nuevo y deja intacto el original. Misma biblioteca base, dos contratos
opuestos sobre quién puede modificar qué.
Representante del núcleo: C. Aquí la cadena no es un tipo: es memoria contigua.
#include <iostream>
#include <string>
#include <algorithm>
int main() {
std::string w;
std::cin >> w;
std::reverse(w.begin(), w.end());
std::cout << "invertido=" << w << '\n';
}
#import <Foundation/Foundation.h>
int main(void) {
@autoreleasepool {
NSString *entrada = [[NSString alloc]
initWithData:[[NSFileHandle fileHandleWithStandardInput] availableData]
encoding:NSUTF8StringEncoding];
NSString *w = [entrada stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSMutableString *r = [NSMutableString stringWithCapacity:w.length];
for (NSInteger i = (NSInteger)w.length - 1; i >= 0; i--) {
[r appendFormat:@"%C", [w characterAtIndex:i]];
}
printf("invertido=%s\n", r.UTF8String);
}
return 0;
}
Qué reconocer: en C la cadena es un char* terminado en \0 y se invierte intercambiando bytes
con dos índices. C++ conserva ese modelo pero lo envuelve en std::string, que sabe su longitud
—por eso w.begin() y w.end() bastan y std::reverse es el mismo algoritmo genérico que usarías
sobre un vector—. Objective-C se va al otro extremo: NSString es un objeto con codificación
explícita, y la distinción entre NSString y NSMutableString está en el tipo, no en la
disciplina del programador.
Representantes del núcleo: Go · Rust. Compilados, sin máquina virtual, y muy conscientes de que una cadena son bytes.
const std = @import("std");
pub fn main() !void {
var buf: [256]u8 = undefined;
const linea = (try std.io.getStdIn().reader().readUntilDelimiterOrEof(&buf, '\n')).?;
const w = std.mem.trim(u8, linea, " \r\t");
const out = std.io.getStdOut().writer();
try out.writeAll("invertido=");
var i: usize = w.len;
while (i > 0) : (i -= 1) try out.writeByte(w[i - 1]);
try out.writeByte('\n');
}
import std/strutils
let w = stdin.readLine().strip()
var r = newStringOfCap(w.len)
for i in countdown(w.high, 0):
r.add(w[i])
echo "invertido=", r
import std.stdio, std.string, std.algorithm;
void main() {
auto w = readln().strip().dup;
w.reverse();
writeln("invertido=", w);
}
Qué reconocer: los tres dicen en voz alta lo que Python esconde: una cadena es un arreglo de
bytes y w[i] devuelve un byte, no un carácter. Zig ni siquiera construye la cadena invertida —la
escribe byte a byte al flujo de salida, sin reservar memoria—; Nim pide la capacidad por adelantado
con newStringOfCap para no reasignar; D necesita .dup porque readln devuelve una cadena
inmutable y reverse exige un char[] mutable. Esa es la razón por la que en esta familia invertir
texto no ASCII rompe: hay que decidir si se invierten bytes, puntos de código o grafemas, y ningún
lenguaje lo decide por ti.
Representante del núcleo: SQL. Se describe qué se quiere, no cómo recorrerlo.
:- initialization(main, main).
main :-
read_line_to_string(user_input, Linea),
string_chars(Linea, Cs),
reverse(Cs, Rs),
string_chars(Invertido, Rs),
format("invertido=~w~n", [Invertido]).
% Datalog puro no tiene cadenas ni recursión sobre caracteres: se declara la posición
% de cada carácter como un hecho y la regla invierte el índice.
letra(1, "h").
letra(2, "o").
letra(3, "l").
letra(4, "a").
largo(4).
invertida(J, C) :- letra(I, C), largo(N), J = N - I + 1.
Qué reconocer: string_chars es reversible —el mismo predicado descompone la cadena en lista y la
reconstruye según qué argumento venga sin ligar—, algo que ninguna función de las familias anteriores
puede hacer. Datalog muestra el límite del enfoque declarativo aplicado a texto: sin recursión sobre
la estructura de la cadena, la única forma de hablar de "orden" es declararlo como dato, exactamente
igual que SQL, que necesita una columna de posición o un CTE recursivo porque una tabla no tiene orden
propio.
Veinte lenguajes, un solo problema, y una pregunta que cada uno responde distinto: ¿qué es una cadena? Un objeto con métodos, una colección de caracteres, un arreglo de bytes, una lista de símbolos o una relación de posiciones. La sintaxis se olvida; esa decisión de diseño no.