Créez un mobile petit et équilibré


18

On vous donne un tas de poids, et votre tâche est de construire un petit mobile équilibré en utilisant ces poids.

L'entrée est une liste de poids entiers compris entre 1 et 9 inclus. Il peut y avoir des doublons.

La sortie est une image ascii d'un mobile qui, une fois accroché, s'équilibrerait. Peut-être le mieux montré par l'exemple:

contribution

3 8 9 7 5

sortie possible

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

Vous devez utiliser les caractères ascii comme indiqué. Les segments horizontaux et verticaux peuvent être de n'importe quelle longueur. Aucune partie du mobile ne doit toucher (horizontalement ou verticalement) une autre partie non connectée du mobile. Tous les poids doivent être suspendus à un segment vertical d'une longueur d'au moins 1, et il doit y avoir un segment vertical à partir duquel tout le mobile est suspendu.

La taille d'un mobile est le nombre total de +, -et les |caractères nécessaires pour le construire. Les tailles inférieures sont meilleures.

Vous pouvez mettre autant de connexions sur un segment que vous le souhaitez. Par exemple:

contribution

2 3 3 5 3 9

sortie possible

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

Le programme gagnant est celui qui peut générer la moyenne la plus basse des tailles mobiles pour un ensemble de test d'entrées. Le vrai test est super secret pour empêcher le codage en dur, mais ce sera quelque chose comme ceci:

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

La physique aussi impliquée?
VOUS le

1
@ S.Mark: Je suppose que vous pourriez dire cela. Pour les handicapés physiques, la somme de total_weight_hung_from_point * distance_of_point_from_pivotdoit être la même des deux côtés du point de pivot.
Keith Randall

Peut-être pour faciliter l'examen des diagrammes, faire en sorte qu'une barre soit égale à environ deux tirets? En l'état, vos diagrammes semblent déséquilibrés.
Thomas O

Réponses:


5

Python 2.

Je triche un peu :

  • Je ne construis que des mobiles avec un horizontal. J'ai le sentiment (mais je ne l'ai pas prouvé) que le mobile optimal dans les conditions données n'a en fait toujours qu'une seule horizontale. Edit: Pas toujours vrai; avec 2 2 9 1Nabb a trouvé un contre-exemple dans les commentaires ci-dessous:

    Size 18:                Size 16:
       |                        |
    +-++--+-----+            +--++-+
    | |   |     |            |   | |
    2 9   2     1           -+-  9 1
                            | |
                            2 2
    
  • Je fais juste un forçage brutal stupide:

    1. Les poids donnés sont mélangés de manière aléatoire.
    2. Deux poids à la fois sont placés sur le mobile dans les meilleures positions de sorte qu'il reste équilibré.
    3. Si le mobile résultant est meilleur que celui que nous avions auparavant, souvenez-vous-en.
    4. Rincez et répétez jusqu'à ce qu'un nombre prédéfini de secondes soit écoulé.

Mes résultats pour vos entrées d'échantillon; chacun a été exécuté pendant 5 secondes (je suis conscient que c'est ridicule pour les petits - simplement parcourir toutes les permutations possibles serait plus rapide). Notez qu'étant donné qu'il existe un élément aléatoire, les exécutions ultérieures peuvent trouver des résultats meilleurs ou pires.

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

Le code (verbeux, car ce n'est pas du golf de code):

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()

@Nabb: des poids supérieurs à 9 ne sont pas possibles. Quant à 1 9 2 8elle génère 1-------8+-9--2; du haut de ma tête, je ne peux rien trouver de mieux (mais je ne compterais pas là-dessus) - avez-vous quelque chose?
balpha

1
@balpha: Nevermind, ne pensait pas bien quand j'ai commenté plus tôt. J'ai pensé pour une raison quelconque que vous pouviez les coller en 1-9 et 2-8, mais évidemment ces paires elles-mêmes ne s'équilibrent pas!
Nabb le

D'accord, en voici une qui pourrait être meilleure avec plusieurs couches:, 2 2 9 1c'est-à-dire (2 + 2) * 3 = 9 + 1 * 3 pour une taille de 16 au lieu de 2-9+--2----118. Je suppose qu'il y a un seuil (peut-être 5 ou 6 ), après quoi une seule ligne horizontale est toujours optimale.
Nabb le

@Nabb: Yep; c'est en effet un bon contre-exemple.
balpha

@Nabb, une barre unique avec des 2-2-+9-1soldes, avec un score de 13 (4*2+2*2 = 9*1+1*3). Je ne pense donc pas que l'on soit un bon contre-exemple.
Keith Randall,

1

Eh bien, c'est une vieille question, mais je viens de la voir apparaître dans l'onglet supérieur des questions alors voici ma solution (optimale):

#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;
}

En regardant les règles, je suis presque sûr que ce n'est pas de la triche, même si c'est comme si. Cela produira simplement tous les nombres donnés dans une chaîne verticale, pour un coût total de 2 * nombre_d'entrées (ce qui est le minimum possible car chaque nombre doit avoir une barre au-dessus, quelle que soit la disposition). Voici un exemple:

./mobile 3 8 9 7 5

Produit:

|
3
|
8
|
9
|
7
|
5

Ce qui est bien sûr en parfait équilibre.


J'allais à l'origine essayer quelque chose de plus dans l'esprit de ce défi, mais il s'est vite avéré qu'il s'est tout de même optimisé pour cette structure


Probablement pas clair d'après ma description, mais vous ne pouvez pas connecter un |au bas d'un poids.
Keith Randall

@KeithRandall ah ok; dans cet esprit, je devrais peut-être essayer de résoudre ce problème correctement.
Dave

1

Voici une solution qui force brutalement la plus petite solution à une seule ligne. Le code parcourt toutes les permutations et calcule le centre de masse pour chacune. Si le centre de masse a des coordonnées entières, nous avons trouvé une solution.

Après avoir essayé toutes les permutations, nous ajoutons un segment au mélange (équivalent à un poids de masse 0) dans notre ensemble actuel de poids et réessayons.

Pour exécuter le programme, faites 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)

qui produit ces meilleures solutions:

    |
+-+-+-+-+
| | | | |
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

0

Python 3

Cela ne fait pas pire que 1 de plus qu'optimal sur l'un des cas de test, je crois, et cela en 5 secondes.

Fondamentalement, j'utilise une approche à barre unique. Je commande au hasard l'entrée, puis j'insère les poids sur la barre un par un. Chaque élément est soit placé dans la position qui minimise le poids excessif de chaque côté, soit la deuxième meilleure position de ce point de vue, en utilisant l'ancien 75% du temps et le dernier 25% du temps. Ensuite, je vérifie si le mobile est équilibré à la fin, et est meilleur que le meilleur mobile trouvé jusqu'à présent. Je stocke le meilleur, puis je m'arrête et je l'imprime après 5 secondes de recherche.

Résultats, en 5 secondes:

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

Code:

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 seule de ces solutions qui, je crois, n'est pas optimale est la plus longue, qui a cette solution, que j'ai trouvée après une course de 10 minutes:

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.