Desarrollo
Menú
Gamersmafia es ahora un archivo de solo lectura.

Advent of Code 2022

Falta apenas una semana para Advent of Code, por lo que he decidido crear este hilo para invitaros a participar.

¿Qué es Advent of Code?

Advent of Code es un evento de programación/resolución de puzzles cuyo funcionamiento es como el de un calendario de adviento.

Desde el día 1 hasta el 25 de diciembre se libera un nuevo puzzle de programación que te va contando una historia. Te pedirán una respuesta (habitualmente numérica o una cadena) al problema que se plantea en base a unos datos de entrada.

Cada problema consta siempre de 2 partes a resolver.

¿Qué se necesita para participar?

Únicamente necesitas registrarte en la web.

Puedes usar el lenguaje de programación que prefieras (incluso puedes usar papel y lápiz o una excel).

¿Cuál es el objetivo?

De cara a los leaderboards solo importa conseguir el resultado correcto en el menor tiempo posible. Lo limpia o sucia que sea la solución o la eficiencia del algoritmo da igual.

No obstante, al margen del leaderboard, el objetivo puede ser algo muy subjetivo:

  • Habrá quien prefiera resolver cada problema lo más rápido posible.
  • Otros preferirán hacer el algoritmo más eficiente posible.
  • También puede que desees poner en práctica algoritmos o patrones de diseño que conozcas o que estés aprendiendo.
  • ¡Incluso hay gente que luego hace animaciones con Javascript/Unity/un simple terminal para representar la resolución del problema de manera gráfica! Incluso hubo alguien que decidió resolver un problema usando Factorio

¿Cuándo se libera cada problema?

Cada día a las 00:00 EST/UTC-5, 6:00 hora española, una hora menos en Canarias:

¿Es muy difícil?

La dificultad de Advent of Code va subiendo progresivamente según avanza el evento.

Los primeros días casi todo el mundo puede resolver los problemas. Conforme pasan los días, estos son cada vez más duros y va haciendo falta más conocimientos de distinta índole para progresar.

¿Hay tiempo límite para resolver cada puzzle?

No. Si algún día te bloqueas, puedes resolverlo más adelante sin ningún problema.

¡Incluso puedes probar a resolver puzzles de años pasados para ir entrenando!

Leaderboards privados

Una vez registrado, si usas el siguiente código en esta página, podéis uniros a este leaderboard privado con otros usuarios de Gamersmafia para ir comparando vuestro progreso:

1552540-76b02474

Más info: advent of code - about

PD: El año pasado participé por primera vez y me lo pasé muy bien, os lo recomiendo.

Hf & gl!

Informativo
#1

Gracias Flash. Voy a estar de viaje parte de diciembre, pero si avisas aquí cuando empiece prometo hacer al menos un puzzle. :-)

Comentario #1 por slnc:
Gracias Flash. Voy a estar de viaje parte de diciembre, pero si avisas aquí cuando empiece prometo hacer al menos un puzzle. :-)

claro! avisaré por aquí

Faltan apenas 12h y media!!

Comentario #1 por slnc:
Gracias Flash. Voy a estar de viaje parte de diciembre, pero si avisas aquí cuando empiece prometo hacer al menos un puzzle. :-)

guiño guiño, codo codo, patada voladora

5 minutos!!

¿Qué representa el primer campo después de 1), 2)? ¿El número de intentos?

Parte 1

input = File.open('aoc1_input.txt').read
max = 0
input.split("\n\n").each do |elf|
  cur = elf.split("\n").collect { |b| b.to_i }.sum
  max = cur if cur > max
end
puts "max: #{max}"

Parte 2

input = File.open('aoc1_input.txt').read
loads = []
input.split("\n\n").each do |elf|
  loads << elf.split("\n").collect { |b| b.to_i }.sum
end
puts "max: #{loads.sort.reverse[0..2].sum}"

Comentario #5 por slnc:

¿Qué representa el primer campo después de 1), 2)? ¿El número de intentos?

Parte 1

input = File.open('aoc1_input.txt').read
max = 0
input.split("\n\n").each do |elf|
  cur = elf.split("\n").collect { |b| b.to_i }.sum
  max = cur if cur > max
end
puts "max: #{max}"

Parte 2

input = File.open('aoc1_input.txt').read
loads = []
input.split("\n\n").each do |elf|
  loads << elf.split("\n").collect { |b| b.to_i }.sum
end
puts "max: #{loads.sort.reverse[0..2].sum}"

es una puntuación que se da en función de las personas que haya en el leaderboard y cuanto tardas en resolver el puzzle. Se calcula mas o menos asi:

Si hay 2 personas, el primero que resuelve una de las dos partes se lleva 2 puntos, el segundo 1 punto. Como ahora mismo solo somos dos, y yo he resuelto primero parte a y parte b, pues en total yo me he llevado 4.

El número de puntos aumenta por cada persona que haya en el leaderboard hasta un máximo de 100. es decir, si hay 100 personas, el primero en solucionar se lleva 100 puntos, el último 1, el 101 se lleva 0. Se ajusta automáticamente si alguien entra en el leaderboard. es decir, yo tengo 4 puntos ahora, pero si entra alguien que ha tardado mas que yo en resolver, yo paso a tener 6 puntos y lo mismo pasaría con tus puntos.

Mi algoritmo (está ajustado para que funcione tanto para parte A como B):

public class CalorieCounting {

	private List<Integer> elvesCalories = new ArrayList<>();

	public CalorieCounting(List<String> inputs) {

		Integer currentElveCalories = 0;

		for(String calorie : inputs) {
			if(StringUtils.isBlank(calorie)) {
				elvesCalories.add(currentElveCalories);
				currentElveCalories = 0;
			} else {
				currentElveCalories += Integer.parseInt(calorie);
			}
		}

		elvesCalories.add(currentElveCalories);

	}

	public int solve(int numberOfElves) {

		AtomicInteger totalCalories = new AtomicInteger(0);
		elvesCalories.stream().sorted(Collections.reverseOrder()).limit(numberOfElves).forEach(elveCalories -> totalCalories.getAndAdd(elveCalories));
		return totalCalories.get();

	}

}

¡Qué elegante es tu código!

Comentario #6 por Flash-Hack:
#5 es una puntuación que se da en función de las personas que haya en el leaderboard y cuanto tardas en resolver el puzzle. Se calcula mas o menos asi:

Si hay 2 personas, el primero que resuelve una de las dos partes se lleva 2 puntos, el segundo 1 punto. Como ahora mismo solo somos dos, y yo he resuelto primero parte a y parte b, pues en total yo me he llevado 4.

El número de puntos aumenta por cada persona que haya en el leaderboard hasta un máximo de 100. es decir, si hay 100 personas, el primero en solucionar se lleva 100 puntos, el último 1, el 101 se lleva 0. Se ajusta automáticamente si alguien entra en el leaderboard. es decir, yo tengo 4 puntos ahora, pero si entra alguien que ha tardado mas que yo en resolver, yo paso a tener 6 puntos y lo mismo pasaría con tus puntos.

Mi algoritmo (está ajustado para que funcione tanto para parte A como B):

public class CalorieCounting {

	private List<Integer> elvesCalories = new ArrayList<>();

	public CalorieCounting(List<String> inputs) {

		Integer currentElveCalories = 0;

		for(String calorie : inputs) {
			if(StringUtils.isBlank(calorie)) {
				elvesCalories.add(currentElveCalories);
				currentElveCalories = 0;
			} else {
				currentElveCalories += Integer.parseInt(calorie);
			}
		}

		elvesCalories.add(currentElveCalories);

	}

	public int solve(int numberOfElves) {

		AtomicInteger totalCalories = new AtomicInteger(0);
		elvesCalories.stream().sorted(Collections.reverseOrder()).limit(numberOfElves).forEach(elveCalories -> totalCalories.getAndAdd(elveCalories));
		return totalCalories.get();

	}

}

¡Qué elegante es tu código!

Entendido, gracias. :-) ¡A ver si se anima alguien más!

Creo que es más Ruby que yo. Está diseñado para ser bello, en palabras de su autor.

Comentario #7 por slnc:
#6 Entendido, gracias. :-) ¡A ver si se anima alguien más!

Creo que es más Ruby que yo. Está diseñado para ser bello, en palabras de su autor.

ojalá se apunte más gente al leaderboard si! además mola poder comparar soluciones!

Interesante
#9

El día 2 primero lo he resuelto con un churro de código muy feo, y luego me he puesto a limpiarlo a ver que podía hacer.

Análisis del problema

Me he dado cuenta de que en vez de resolver el churro como lo hice inicialmente con un montón de switches, en realidad podía convertirlo todo a una matriz de resultados.

Para el problema A:

Elfo \ Yo Rock Paper Scissors
Rock 1+3 = 4 2+6 = 8 3+0 = 3
Paper 1+0 = 1 2+3 = 5 3+6 = 9
Scissors 1+6 = 7 2+0 = 2 3+3 = 6

Para el problema B:

Elfo \ Yo Lose Draw Win
Rock 3+0 = 3 1+3 = 4 2+6 = 8
Paper 1+0 = 1 2+3 = 5 3+6 = 9
Scissors 2+0 = 2 3+3 = 6 1+6 = 7

Una vez visto esto, vi claro que el esqueleto del algoritmo es igual en ambos casos y que solo cambia el peso de cada jugada.

Solución

Igual es un poco over-engineered, pero me gusta tratar de aplicar patrones de diseño en los puzzles mas sencillos (se prestan a experimentar con ellos).

En este caso he optado por Strategy Pattern mezclado con Template Method (en realidad es más lo segundo que lo primero).

RockPaperScissorsStrategy

Esta clase abstracta contiene una lógica que es común para ambos puzzles:

public abstract class RockPaperScissorsStrategy {

	public final static String ROUND_PATTERN = "(A|B|C) (X|Y|Z)";
	private final static Pattern PATTERN = Pattern.compile(ROUND_PATTERN);

	@Getter
	private List<Round> rounds = new ArrayList<>();

	public RockPaperScissorsStrategy(List<String> inputs) {

		Map<String, Integer> elvePlayIndex = new HashMap<>();
		Map<String, Integer> playerPlayIndex = new HashMap<>();

		// Round hands to index translation
		elvePlayIndex.put("A", 0);
		elvePlayIndex.put("B", 1);
		elvePlayIndex.put("C", 2);
		playerPlayIndex.put("X", 0);
		playerPlayIndex.put("Y", 1);
		playerPlayIndex.put("Z", 2);

		for(String round : inputs) {

			Matcher matcher = PATTERN.matcher(round);
			matcher.find();

			rounds.add(new Round(elvePlayIndex.get(matcher.group(1)),
								playerPlayIndex.get(matcher.group(2))));

		}

	}

	public int solve() {

		int[][] resultMatrix = initializeResultMatrix();

		int score = 0;
		for(Round round : rounds) {
			score += resultMatrix[round.getElve()][round.getPlayer()];
		}

		return score;
	}

	abstract int[][] initializeResultMatrix();
}

El constructor convierte los datos del fichero a un listado de rondas. En cada ronda guardo el índice de la jugada, de tal forma que por parte del elfo R=0, P=1 y S=2 y por mi parte X=0, Y=1 y Z=2

El método solve() llama primero a un método abstracto initializeResultMatrix() que cada estrategia inicializará de la manera que le corresponda. A continuación recorre todas las rondas y va sumando los resultados en función de matriz de resultados.

Round

Simplemente guarda el índice de la jugada a seleccionar en la matriz de resultados:

@Getter
@AllArgsConstructor
public class Round {

	private int elve;
	private int player;

}

Finalmente, solo queda crear dos estrategias diferentes, cargando en cada una su matriz de resultados:

RockPaperScissorsBasicStrategy

public class RockPaperScissorsBasicStrategy extends RockPaperScissorsStrategy {

	public RockPaperScissorsBasicStrategy(List<String> inputs) {
		super(inputs);
	}

	@Override
	public int[][] initializeResultMatrix() {
		return new int[][] {{4,8,3},{1,5,9},{7,2,6}};
	}

}

RockPaperScissorsAdvancedStrategy

public class RockPaperScissorsAdvancedStrategy extends RockPaperScissorsStrategy {

	public RockPaperScissorsAdvancedStrategy(List<String> inputs) {
		super(inputs);
	}

	@Override
	public int[][] initializeResultMatrix() {
		return new int[][] {{3,4,8},{1,5,9},{2,6,7}};
	}

}

Conclusiones

  • Seguramente podría ahorrarme las subclases strategy y simplemente pasar por argumento de entrada al método solve() el array de pesos, pero a cambio implicaría meter checkeos para verificar que el array que me pasan es un array 3x3 válido.
  • Mi algoritmo se acerca más al template method que a un strategy, porque la estrategia realmente no cambia en los dos algoritmos, solo cambian los pesos asignados.
  • Me habría gustado encontrar una forma más limpia de traducir los valores A-B-C, X-Y-Z, sin tener que recurrir a un mapa, pero probablemente me habría implicado hacer uso de switches o algún tipo de lambda compleja, ¿alguna idea?
Interesante
#10

Me habría gustado encontrar una forma más limpia de traducir los valores A-B-C, X-Y-Z, sin tener que recurrir a un mapa, pero probablemente me habría implicado hacer uso de switches o algún tipo de lambda compleja, ¿alguna idea?

Depende de tu objetivo con este problema: ¿Es encontrar una solución con menos caracteres? ¿Es minimizar complejidad? ¿Es escribir código fácil de modificar sin introducir bugs? ¿Es aplicar patrones de diseño sea como sea?

Mi “thought process” con la primera parte ha sido: hay 9 combinaciones, la complejidad está en traducir A, B, C, e X, Y, Z a lo que representan, necesito enumerar los casos, son pocos, voy a traducirlo a código. Al escribir el código no se me han ocurrido soluciones obvias para comprimir las 9 nueve líneas de score_per_result y dado que esto es write-once code lo he dejado así.

Al leer la segunda parte he entendido que podía darle la vuelta a la primera solución y es lo que he hecho.

Tenía un 85% de confianza de que ambas soluciones fuesen a dar la solución correcta a la primera y el hecho de que la han dado para mí valida la elección de optimizar para código fácil, para mí, de mantener.

Primera parte

input = File.read('aoc2_input.txt')

score_per_result = {
  'A X' => 3,
  'A Y' => 6,
  'A Z' => 0,
  'B X' => 0,
  'B Y' => 3,
  'B Z' => 6,
  'C X' => 6,
  'C Y' => 0,
  'C Z' => 3,
}

score_per_shape = {
  'X' => 1,
  'Y' => 2,
  'Z' => 3,
}

total = 0
input.split("\n").each do |l|
  total += score_per_result.fetch(l) + score_per_shape.fetch(l[2])
end
puts total

Segunda parte

score2_per_result = {
  'X' => 0,
  'Y' => 3,
  'Z' => 6,
}

# A = rock, B = paper, C = scissors
# X = lose, Y = tie, Z = win
# 1 = rock, 2 = paper, 3 = scissors
score2_per_shape = {
  'A X' => 3,
  'A Y' => 1,
  'A Z' => 2,
  'B X' => 1,
  'B Y' => 2,
  'B Z' => 3,
  'C X' => 2,
  'C Y' => 3,
  'C Z' => 1,
}

total = 0
input.split("\n").each do |l|
  total += score2_per_result.fetch(l[2]) + score2_per_shape.fetch(l)
end
puts total

Había escrito un comentario enorme explicando días 3, 4 y 5 but…

Así que me limitaré a dejar por aquí mis resoluciones:

El de hoy (día 06) ha sido demasiado sencillo, miedo me da lo que nos vayan a poner mañana:

public class TuningTrouble {

	public long solve(String input, int expectedMarkerSize) {

		Deque<Character> currentMarker = new LinkedList<>();
		Set<Character> uniqueCharacters = new HashSet<>();

		int i = 0;
		while((uniqueCharacters.size() != expectedMarkerSize) && (i < input.length())) {

			Character currentCharacter = input.charAt(i++);
			currentMarker.addLast(currentCharacter);
			uniqueCharacters.add(currentCharacter);

			if(currentMarker.size() > expectedMarkerSize) {

				Character oldCharacter = currentMarker.pollFirst();

				// Remove non duplicated characters
				if(!currentMarker.contains(oldCharacter)) {
					uniqueCharacters.remove(oldCharacter);
				}
			}

		}

		return i;

	}
}

Para la parte 1, expectedMarkerSize debe valer 4 y para la parte 2, 14.

Interesante
#13

El día 7 ha sido uno de los que más me ha gustado en lo que va de año.

Me he creado una clase que permite modelar lol que es un fichero:
https://github.com/Flashky/advent-of-code-2022/blob/master/src/main/java/com/adventofcode/flashk/day07/File.java

Y luego la clase que parsea los datos de entrada para construir un árbol de directorios y ficheros y evaluar las respuestas:
https://github.com/Flashky/advent-of-code-2022/blob/master/src/main/java/com/adventofcode/flashk/day07/DeviceSpace.java

Informativo
#14

Comentario #13 por Flash-Hack:
El día 7 ha sido uno de los que más me ha gustado en lo que va de año.

Me he creado una clase que permite modelar lol que es un fichero:
https://github.com/Flashky/advent-of-code-2022/blob/master/src/main/java/com/adventofcode/flashk/day07/File.java

Y luego la clase que parsea los datos de entrada para construir un árbol de directorios y ficheros y evaluar las respuestas:
https://github.com/Flashky/advent-of-code-2022/blob/master/src/main/java/com/adventofcode/flashk/day07/DeviceSpace.java

Estoy quedándome atrás, Flash, por lo que te comenté el otro día por chat, pero sigo leyendo tus entradas e intentaré ponerme al día. :-)

Comentario #14 por slnc:
#13 Estoy quedándome atrás, Flash, por lo que te comenté el otro día por chat, pero sigo leyendo tus entradas e intentaré ponerme al día. :-)

sin problema! disfruta de las vacaciones! ten en cuenta que advent of code se puede hacer en cualquier momento, incluso cuando ha finalizado el evento. Yo estos días de puente tampoco estoy teniendo demasiado tiempo para pasarme por aquí.

Comentario #15 por Flash-Hack:
#14 sin problema! disfruta de las vacaciones! ten en cuenta que advent of code se puede hacer en cualquier momento, incluso cuando ha finalizado el evento. Yo estos días de puente tampoco estoy teniendo demasiado tiempo para pasarme por aquí.

Oh, eso me deja más tranquilo, gracias. :-)

Los más activos en General
Subforos