Construye un móvil pequeño y equilibrado

18

Te dan un montón de pesos, y tu tarea es construir un pequeño móvil equilibrado usando esos pesos.

La entrada es una lista de pesos enteros en el rango de 1 a 9, inclusive. Puede haber duplicados.

La salida es una imagen ascii de un móvil que, cuando se cuelga, se equilibraría. Quizás mejor se muestra con el ejemplo:

entrada

3 8 9 7 5

salida posible

         |
   +-----+---------+
   |               |
+--+-+        +----+------+
|    |        |           |
8   ++--+     7           5
    |   |
    9   3

Debe usar los caracteres ascii como se muestra. Los segmentos horizontal y vertical pueden ser de cualquier longitud. Ninguna parte del móvil puede tocar (horizontal o verticalmente) otra parte no conectada del móvil. Todos los pesos deben colgarse de un segmento vertical de longitud de al menos 1, y debe haber un segmento vertical del que se cuelga todo el móvil.

El tamaño de un móvil es el número total de +, -y |caracteres necesarios para su construcción. Los tamaños más bajos son mejores.

Puede poner tantas conexiones en un segmento como desee. Por ejemplo:

entrada

2 3 3 5 3 9

salida posible

           |
   +---+---+-----------+
   |   |               |
+--+-+ 5               9
|  | |
2  | 3
   |
  +++
  | |
  3 3

El programa ganador es el que puede generar el promedio más bajo de tamaños móviles para un conjunto de prueba de entradas. La prueba real es súper secreta para evitar la codificación rígida, pero será algo como esto:

8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7
1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 7 7
3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7
Keith Randall
fuente
Física también involucrada?
TU
1
@ S.Mark: Creo que se podría decir eso. Para los discapacitados físicos, la suma de total_weight_hung_from_point * distance_of_point_from_pivotdebe ser la misma en ambos lados del punto de pivote.
Keith Randall el
¿Quizás para facilitar el examen de los diagramas, hacer que una barra sea igual a aproximadamente dos guiones? Tal como está, sus diagramas se ven fuera de balance.
Thomas O

Respuestas:

5

Python 2.

Estoy engañando un poco :

  • Solo construyo móviles con uno horizontal. Tengo la sensación (pero no lo he probado) que el móvil óptima en las condiciones dadas en realidad siempre no sólo tienen una horizontal. Editar: no siempre es cierto; with 2 2 9 1Nabb ha encontrado un contraejemplo en los comentarios a continuación:

    Size 18:                Size 16:
       |                        |
    +-++--+-----+            +--++-+
    | |   |     |            |   | |
    2 9   2     1           -+-  9 1
                            | |
                            2 2
    
  • Acabo de hacer estúpido forzamiento bruto:

    1. Los pesos dados se barajan al azar.
    2. Se colocan dos pesas a la vez en el móvil en las mejores posiciones para que se mantenga equilibrado.
    3. Si el móvil resultante es mejor que cualquiera que teníamos antes, recuérdalo.
    4. Enjuague y repita, hasta que haya transcurrido un número predefinido de segundos.

Mis resultados para sus entradas de muestra; cada uno se ejecutó durante 5 segundos (soy consciente de que esto es ridículo para los pequeños; solo pasar por todas las permutaciones posibles sería más rápido). Tenga en cuenta que, dado que hay un elemento aleatorio, las ejecuciones posteriores pueden encontrar mejores o peores resultados.

3 8 9 7 5
Tested 107887 mobiles, smallest size 20:
        |
+-+-----+-+--+
| |     | |  |
5 3     7 9  8

2 3 3 5 3 9
Tested 57915 mobiles, smallest size 23:
      |
+--+-++--+-+---+
|  | |   | |   |
3  5 9   3 3   2

8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7
Tested 11992 mobiles, smallest size 50:
                |
+-+-+-+--+-+-+-+++-+-+--+-+-+-+-+
| | | |  | | | | | | |  | | | | |
8 8 8 8  8 8 8 8 8 8 8  7 8 8 8 8

1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 7 7
Tested 11119 mobiles, smallest size 62:
                    |
+-+-+-+-+-+--+-+-+-+++-+-+-+--+-+-+-+-+-+
| | | | | |  | | | | | | | |  | | | | | |
2 7 5 6 6 8  3 2 3 7 9 7 8 1  1 7 9 5 4 4

3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7
Tested 16301 mobiles, smallest size 51:
                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |
4 6 5 7 7 4 6 5 3 5 6 4 7 6 7 5 4

El código (detallado, ya que esto no es código golf):

import time, random

def gcd(a, b):
    while b > 0:
        a, b = b, a % b
    return a

class Mobile(object):
    def __init__(self):
        self.contents = [None];
        self.pivot = 0;

    def addWeights(self, w1, w2):
        g = gcd(w1, w2)
        m1 = w2 / g
        m2 = w1 / g
        mul = 0
        p1 = -1
        while True:
            if p1 < 0:
                mul += 1
                p1 = mul * m1
                p2 = -mul * m2
            else:
                p1 *= -1
                p2 *= -1
            if self.free(p1) and self.free(p2):
                self.add(w1, p1)
                self.add(w2, p2)
                return

    def add(self, w, pos):
        listindex = self.pivot - pos 
        if listindex < 0:
            self.contents = [w] + (abs(listindex) - 1) * [None] + self.contents
            self.pivot += abs(listindex)
        elif listindex >= len(self.contents):
            self.contents += (listindex - len(self.contents)) * [None] + [w]
        else:
            self.contents[listindex] = w

    def at(self, pos):
        listindex = self.pivot - pos
        if 0 <= listindex < len(self.contents):
            return self.contents[listindex]
        return None

    def free(self, pos):
        return all(self.at(pos + d) is None for d in (-1, 0, 1))

    def score(self):
        return 1 + 2 * len(self.contents) - self.contents.count(None)

    def draw(self):
        print self.pivot * " " + "|"
        print "".join("+" if c is not None or i == self.pivot else "-" for i, c in enumerate(self.contents))
        print "".join("|" if c is not None else " " for c in self.contents)
        print "".join(str(c) if c is not None else " " for c in self.contents)

    def assertBalance(self):
        assert sum((i - self.pivot) * (c or 0) for i, c in enumerate(self.contents)) == 0


weights = map(int, raw_input().split())

best = None
count = 0

# change the 5 to the number of seconds that are acceptable
until = time.time() + 5

while time.time() < until:
    count += 1
    m = Mobile()

    # create a random permutation of the weights
    perm = list(weights)
    random.shuffle(perm)

    if len(perm) % 2:
        # uneven number of weights -- place one in the middle
        m.add(perm.pop(), 0)

    while perm:
        m.addWeights(perm.pop(), perm.pop())

    m.assertBalance() # just to prove the algorithm is correct :)
    s = m.score()
    if best is None or s < bestScore:
        best = m
        bestScore = s

print "Tested %d mobiles, smallest size %d:" % (count, best.score())
best.draw()
balpha
fuente
@Nabb: No son posibles pesos superiores a 9. En cuanto 1 9 2 8a genera 1-------8+-9--2; Desde lo más alto de mi cabeza no puedo encontrar nada mejor (pero no confiaría en eso), ¿tienes algo?
balpha
1
@balpha: No importa, no estaba pensando con claridad cuando comenté antes. Pensé por alguna razón que podrías pegarlos como 1-9 y 2-8, ¡pero obviamente esos pares no se equilibran!
Nabb
De acuerdo, aquí hay uno que en realidad puede ser mejor con múltiples capas: 2 2 9 1es decir (2 + 2) * 3 = 9 + 1 * 3 para el tamaño 16 en lugar del 2-9+--2----1cual es 18. Supongo que hay un umbral (tal vez 5 o 6 ) después de lo cual una sola fila horizontal siempre es óptima.
Nabb
@Nabb: Sí; ese es de hecho un buen contraejemplo.
balpha
@Nabb, una barra única con 2-2-+9-1saldos, con una puntuación de 13 (4*2+2*2 = 9*1+1*3). Así que no creo que sea un buen contraejemplo.
Keith Randall
1

Bueno, esta es una pregunta antigua, pero acabo de verla aparecer en la pestaña de preguntas principales, así que aquí está mi solución (óptima):

#include <stdio.h>
#include <limits.h>
#include <math.h>
#include <stdlib.h>

int main(int argc, const char *const *argv) {
    if(argc < 2) {
        fprintf(stderr,
            "Balances weights on a hanging mobile\n\n"
            "Usage: %s <weight1> [<weight2> [...]]\n",
            argv[0]
        );
        return 1;
    }
    int total = argc - 1;
    int values[total];
    int maxval = 0;
    for(int n = 0; n < total; ++ n) {
        char *check = NULL;
        long v = strtol(argv[n+1], &check, 10);
        if(v <= 0 || v > INT_MAX || *check != '\0') {
            fprintf(stderr,
                "Weight #%d (%s) is not an integer within (0 %d]\n",
                n + 1, argv[n+1], INT_MAX
            );
            return 1;
        }
        values[n] = (int) v;
        if(values[n] > maxval) {
            maxval = values[n];
        }
    }
    int maxwidth = (int) log10(maxval) + 1;
    for(int n = 0; n < total; ++ n) {
        int width = (int) log10(values[n]) + 1;
        fprintf(stdout,
            "%*s\n%*d\n",
            (maxwidth + 1) / 2, "|",
            (maxwidth + width) / 2, values[n]
        );
    }
    return 0;
}

Al mirar las reglas, estoy bastante seguro de que no es trampa, aunque parece que sí. Esto solo generará todos los números dados en una cadena vertical, por un costo total de 2 * número_de_insumos (que es el mínimo posible porque cada número debe tener una barra encima, sin importar el diseño). Aquí hay un ejemplo:

./mobile 3 8 9 7 5

Produce:

|
3
|
8
|
9
|
7
|
5

Lo cual, por supuesto, está en perfecto equilibrio.


Originalmente iba a intentar algo más en el espíritu de este desafío, pero rápidamente resultó que de todos modos simplemente se optimizó para esta estructura

Dave
fuente
Probablemente no esté claro en mi descripción, pero no se puede conectar |a la parte inferior de un peso.
Keith Randall
@KeithRandall ah ok; Con eso en mente, podría tener que intentar resolver esto correctamente.
Dave
1

Aquí hay una solución que fuerza bruta a la solución de fila única más pequeña. El código itera sobre todas las permutaciones y calcula el centro de masa para cada una. Si el centro de masa tiene coordenadas enteras, hemos encontrado una solución.

Después de que se hayan intentado todas las permutaciones, agregamos un segmento a la mezcla (equivalente a un peso de masa 0) en nuestro conjunto actual de pesos y reintentos.

Para ejecutar el programa, hazlo python balance.py 1 2 2 4.

#!/usr/bin/env python3
import itertools, sys

# taken from http://stackoverflow.com/a/30558049/436792
def unique_permutations(elements):
    if len(elements) == 1:
        yield (elements[0],)
    else:
        unique_elements = set(elements)
        for first_element in unique_elements:
            remaining_elements = list(elements)
            remaining_elements.remove(first_element)
            for sub_permutation in unique_permutations(remaining_elements):
                yield (first_element,) + sub_permutation

def print_solution(cm, values):
    print(('  ' * cm) + '|')
    print('-'.join(['-' if v == 0 else '+'  for v in values]))
    print(' '.join([' ' if v == 0 else '|'  for v in values]))
    print(' '.join([' ' if v == 0 else str(v) for v in values]))



input = list(map(int, sys.argv[1:]))
mass = sum(input)
while True:
    n = len(input)
    permutations = filter(lambda p: p[0] != 0 and p[n-1] != 0, unique_permutations(input))
    for p in permutations:
        cm = 0
        for i in range(n):
            cm += p[i] * i;
        if (cm % mass == 0):
            print_solution(cm//mass, p)
            sys.exit(0)
    input.append(0)

que produce estas mejores soluciones:

    |
+-+-+-+-+
| | | | |
8 3 9 5 7


    |
+-+-+-+-+-+
| | | | | |
9 2 3 5 3 3

                |
+-+-+-+-+-+-+---+-+-+-+-+-+-+-+-+
| | | | | | |   | | | | | | | | |
8 8 8 8 8 8 8   8 8 8 8 8 8 8 8 7


                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | | | | |
1 1 2 2 3 3 4 4 8 8 5 5 6 6 7 7 7 7 9 9


                  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |
3 4 4 4 4 5 5 5 5 6 7 6 7 7 7 6 6
olivieradam666
fuente
0

Python 3

Creo que esto no es peor que 1 más que óptimo en cualquiera de los casos de prueba, y lo hace en 5 segundos.

Básicamente, uso un enfoque de barra única. Ordeno aleatoriamente la entrada, luego inserto los pesos en la barra de uno en uno. Cada elemento se coloca en la posición que minimiza el exceso de peso en cada lado, o la segunda mejor posición desde esa perspectiva, utilizando el primer 75% del tiempo y el último 25% del tiempo. Luego, verifico si el móvil está equilibrado al final y es mejor que el mejor móvil encontrado hasta ahora. Guardo el mejor, luego lo detengo e imprimo después de 5 segundos de búsqueda.

Resultados, en 5 segundos:

py mobile.py <<< '3 8 7 5 9'
Best mobile found, score 15:
    |    
+-+-+-+-+
| | | | |
8 7 3 5 9
py mobile.py <<< '2 2 1 9'
Best mobile found, score 13:
   |    
+-++-+-+
| |  | |
1 9  2 2
py mobile.py <<< '2 3 3 5 3 9'
Best mobile found, score 18:
      |    
+-+-+-+-+-+
| | | | | |
2 3 3 5 9 3
py mobile.py <<< '8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7'
Best mobile found, score 49:
                |               
+-+--+-+-+-+-+-+++-+-+-+-+-+-+-+
| |  | | | | | | | | | | | | | |
7 8  8 8 8 8 8 8 8 8 8 8 8 8 8 8
\py mobile.py <<< '1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 7 7'
Best mobile found, score 61:
                    |                   
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+--+
| | | | | | | | | | | | | | | | | | |  |
1 7 7 5 4 3 1 9 6 7 8 2 2 9 3 7 6 5 8  4
py mobile.py <<< '3 4 4 4 4 5 5 5 5 6 6 6 6 7 7 7 7'
Best mobile found, score 51:
                |                
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | |
4 4 6 7 7 4 5 7 6 6 5 4 6 3 5 5 7

Código:

import random
import time

class Mobile:
    def __init__(self):
        self.contents = {}
        self.lean = 0

    def usable(self, loc):
        return not any(loc + k in self.contents for k in (-1,0,1))
    def choose_point(self, w):
        def goodness(loc):
            return abs(self.lean + w * loc)
        gl = sorted(list(filter(self.usable,range(min(self.contents.keys() or [0]) - 5,max(self.contents.keys() or [0]) + 6))), key=goodness)
        return random.choice((gl[0], gl[0], gl[0], gl[1]))

    def add(self, w, loc):
        self.contents[loc] = w
        self.lean += w*loc

    def __repr__(self):
        width = range(min(self.contents.keys()), max(self.contents.keys()) + 1)
        return '\n'.join((''.join(' ' if loc else '|' for loc in width),
                          ''.join('+' if loc in self.contents or loc == 0 else '-' for loc in width),
                          ''.join('|' if loc in self.contents else ' ' for loc in width),
                          ''.join(str(self.contents.get(loc, ' ')) for loc in width)))

    def score(self):
        return max(self.contents.keys()) - min(self.contents.keys()) + len(self.contents) + 2

    def my_score(self):
        return max(self.contents.keys()) - min(self.contents.keys()) + 1

best = 1000000
best_mob = None
in_weights = list(map(int,input().split()))
time.clock()
while time.clock() < 5:
    mob = Mobile()
    for insert in random.sample(in_weights, len(in_weights)):
        mob.add(insert, mob.choose_point(insert))
    if not mob.lean:
        if mob.score() < best:
            best = mob.score()
            best_mob = mob

print("Best mobile found, score %d:" % best_mob.score())
print(best_mob)

La única de estas soluciones que creo que es subóptima es la más larga, que tiene esta solución, que encontré después de 10 minutos de ejecución:

Best mobile found, score 60:
                   |                   
+-+-+-+-+-+-+-+-+-+++-+-+-+-+-+-+-+-+-+
| | | | | | | | | | | | | | | | | | | |
3 2 9 4 7 8 1 6 9 8 7 1 6 2 4 5 7 3 5 7
isaacg
fuente