El producto más grande

El producto más grande
Foto de Markus Krisetya en Unsplash

Para aprender un poco de como manejar arreglos de cadenas y caracteres en Swift, realicé un problema que más o menos dice así:

Dada una cadena de mil números encontrar el producto más grande de trece dígitos adjacentes.

Esto quiere decir que de una cadena de 1000 números tomamos los números del primero al 13 (que serían del elemento 0 al 12, por ejemplo), los multiplicamos entre sí y comparamos este resultado con el anterior para ver cuál es mas grande.

En este ejemplo, usamos los siguientes 1,000 números generados de forma aleatoria.

    23377796661007193501261043060813089411852476794737
    80413012317548826352074571145201287743855763207380
    18289493824811970706504947035623696589177036118345
    08135112564412001348912308298836017490080620601646
    14027173677132411381984807991084865311637607974147
    41141784480862471934157122559507779183247514435364
    24426830422303707821688274688511615310311675701320
    24949662378508202440620472821085255510995663583220
    18026535592195291102805186702202481758309181331700
    58053909544294194751760333177437004689480175102869
    71835199860174618721511721055808178058926675992016
    28502351573211951399405947887703568452031251365528
    26425676865779726696440132957483862277919448395602
    89540675110208952633924027929694428260289855175517
    94327112138284503577995929042666691072360845682240
    83612839448793349185063072163303110523917957326031
    32866227211148605460815438688873553945358220466637
    19380581371950031852943450744685897320547645783659
    58402852135348888750866191015544110690933306285745
    45982782368493360446385135850694844047820449322406

Así como la ven, la cadena tiene 20 líneas, cada una empieza con 4 espacios en blanco y termina con un salto de línea.

Para poder eliminar esos espacios en blanco y los saltos de línea \n, usaremos el método replacingOccurrences() del objeto String, que necesita la biblioteca Foundation de swift.

Esta función se usa así replacingOccurrences(of: String, with: String) y devuelve un objeto String. Luego, como tenemos que hacer dos reemplazos, al resultado de la primera sustitución aplicamos nuevamente este método para hacer la segunda.

import Foundation

var serie:String  = """
    23377796661007193501261043060813089411852476794737
    80413012317548826352074571145201287743855763207380
    18289493824811970706504947035623696589177036118345
    08135112564412001348912308298836017490080620601646
    14027173677132411381984807991084865311637607974147
    41141784480862471934157122559507779183247514435364
    24426830422303707821688274688511615310311675701320
    24949662378508202440620472821085255510995663583220
    18026535592195291102805186702202481758309181331700
    58053909544294194751760333177437004689480175102869
    71835199860174618721511721055808178058926675992016
    28502351573211951399405947887703568452031251365528
    26425676865779726696440132957483862277919448395602
    89540675110208952633924027929694428260289855175517
    94327112138284503577995929042666691072360845682240
    83612839448793349185063072163303110523917957326031
    32866227211148605460815438688873553945358220466637
    19380581371950031852943450744685897320547645783659
    58402852135348888750866191015544110690933306285745
    45982782368493360446385135850694844047820449322406
"""
serie = serie
    .replacingOccurrences(of:" ", with:"")
    .replacingOccurrences(of: "\n", with: "")

Ahora que tenemos una sola cadena, vamos a crear un arreglo de mil elementos, convirtiendo cada caracter en un entero y lo agregaremos a este arreglo.

var integers:[Int] = []
for number in serie {
    if let i:Int = Int(String(number)) {
        integers.append(i)
    }
}

Usamos esta condicional if let i:Int = Int(String(number)) porque la función Int() devuelve un opcional (que tal que le pasamos como argumento una letra o un caracter no imprimible) y tenemos que asegurar la conversión. Si tiene éxito, el entero obtenido lo agrega al arreglo.

Ahora necesitamos algunos datos. Por ejemplo, para hacer este programa genérico, el número de datos lo vamos a obtener contando los elementos del arreglo.

let n:Int = integers.count

El número de elementos adjacentes m, lo volvemos una constante.

let m:Int = 13

Y definimos la respuesta al problema inicianizándola en 0.

let answer:Int = 0

Para obtener nuestra respuesta, iniciamos el recorrido de nuestro arreglo de enteros, desde el primer elemento hasta n-m. La razón de este límite es que el último grupo de dígitos adjacentes empieza en el elemento n-m y termina en n, si lo hacemos hasta n el último elemento del último grupo sería n+m y estaría fuera de rango.

Es decir, vamos a recorrer el arreglo desde el elemento 0 hasta el 987, el último grupo de adjacentes va a empezar desde el elemento $987$ y el último $(987+13)$ será el $1,000$.

Luego usamos el método reduce() del objeto Array. Este método reduce(_:_:) aplica combina los elementos del arreglo usando la closure dada. Este método tiene dos argumentos, el primero es el valor inicial (que en este caso es el 1, la identidad multiplicativa) y la closure. Esta función es muy simple, multiplica el primer elemento del arreglo con el valor inicial y se llama repetidamente con el valor anterior y el siguiente elemento del arreglo. Cuando se agotan los elementos del arreglo, se devuelve el valor obtenido.

Al final, comparamos la respuesta con el producto para obtener el valor mayor. Cuando termina el ciclo, obtenemos la respuesta buscada.

for i in 0..<(n-m) {
    let subset:[Int] = Array(integers[i...(i+m-1)])
    let product = subset.reduce(1, {x, y in x*y})
    answer = max(answer, product)
}

Así se ve el programa final.

import Foundation

var serie:String  = """
    23377796661007193501261043060813089411852476794737
    80413012317548826352074571145201287743855763207380
    18289493824811970706504947035623696589177036118345
    08135112564412001348912308298836017490080620601646
    14027173677132411381984807991084865311637607974147
    41141784480862471934157122559507779183247514435364
    24426830422303707821688274688511615310311675701320
    24949662378508202440620472821085255510995663583220
    18026535592195291102805186702202481758309181331700
    58053909544294194751760333177437004689480175102869
    71835199860174618721511721055808178058926675992016
    28502351573211951399405947887703568452031251365528
    26425676865779726696440132957483862277919448395602
    89540675110208952633924027929694428260289855175517
    94327112138284503577995929042666691072360845682240
    83612839448793349185063072163303110523917957326031
    32866227211148605460815438688873553945358220466637
    19380581371950031852943450744685897320547645783659
    58402852135348888750866191015544110690933306285745
    45982782368493360446385135850694844047820449322406
"""
serie = serie
    .replacingOccurrences(of:" ", with:"")
    .replacingOccurrences(of: "\n", with: "")

var integers:[Int] = []
for number in serie {
    if let i:Int = Int(String(number)) {
        integers.append(i)
    }
}

let n:Int = integers.count
let m:Int = 13
var answer:Int = 0
for i in 0..<(n-m) {
    let subset:[Int] = Array(integers[i...(i+m-1)])
    let product = subset.reduce(1, {x, y in x*y})
    answer = max(answer, product)
}

print(answer)

Este es uno de los problemas del Proyecto Euler.

Foto de Markus Krisetya en Unsplash