Costruisci un cellulare piccolo ed equilibrato


18

Ti vengono dati un sacco di pesi e il tuo compito è quello di costruire un piccolo cellulare bilanciato usando quei pesi.

L'input è un elenco di pesi interi compresi tra 1 e 9, inclusi. Potrebbero esserci dei duplicati.

L'output è un'immagine ascii di un cellulare che, una volta appeso, si bilancerebbe. Forse meglio mostrato dall'esempio:

ingresso

3 8 9 7 5

uscita possibile

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

È necessario utilizzare i caratteri ASCII come mostrato. I segmenti orizzontali e verticali possono essere di qualsiasi lunghezza. Nessuna parte del cellulare può toccare (orizzontalmente o verticalmente) un'altra parte non connessa del cellulare. Tutti i pesi devono essere appesi a un segmento verticale di lunghezza almeno 1 e deve esserci un segmento verticale da cui è appeso l'intero cellulare.

La dimensione di un mobile è il numero totale di +, -e |caratteri necessari per costruirlo. Le taglie più basse sono migliori.

Puoi inserire su un segmento tutte le connessioni che desideri. Per esempio:

ingresso

2 3 3 5 3 9

uscita possibile

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

Il programma vincente è quello che può generare la media più bassa di dimensioni mobili per un set di test di input. Il vero test è super-segreto per prevenire l'hard-coding, ma sarà qualcosa del genere:

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

Anche la fisica è coinvolta?
TU

1
@ S.Mark: immagino che potresti dirlo. Per i disabili fisici, la somma di total_weight_hung_from_point * distance_of_point_from_pivotdeve essere la stessa su entrambi i lati del punto di articolazione.
Keith Randall,

Forse per rendere più semplice l'esame dei diagrammi, fare in modo che una barra sia uguale a circa due trattini? Allo stato attuale, i tuoi diagrammi sembrano sbilanciati.
Thomas O

Risposte:


5

Python 2.

Sto barando un po ':

  • Costruisco solo cellulari con una orizzontale. Ho la sensazione (ma non l'ho provato esso) che il cellulare ottimale nelle condizioni date in realtà sempre non hanno un solo orizzontale. Modifica: non sempre vero; con 2 2 9 1Nabb ha trovato un contro-esempio nei commenti qui sotto:

    Size 18:                Size 16:
       |                        |
    +-++--+-----+            +--++-+
    | |   |     |            |   | |
    2 9   2     1           -+-  9 1
                            | |
                            2 2
    
  • Faccio solo stupidi bruti:

    1. I pesi indicati vengono mescolati casualmente.
    2. Due pesi alla volta vengono posizionati sul cellulare nelle migliori posizioni in modo che rimanga in equilibrio.
    3. Se il cellulare risultante è migliore di quello che avevamo prima, ricordalo.
    4. Risciacquare e ripetere, fino a quando un numero predefinito di secondi è scaduto.

I miei risultati per i tuoi input di esempio; ognuno è stato eseguito per 5 secondi (sono consapevole che questo è ridicolo per i più piccoli - solo passare attraverso tutte le possibili permutazioni sarebbe più veloce). Si noti che poiché esiste un elemento casuale, le esecuzioni successive potrebbero trovare risultati migliori o peggiori.

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

Il codice (dettagliato, in quanto questo non è codice 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()

@Nabb: non sono possibili pesi superiori a 9. Quanto 1 9 2 8genera 1-------8+-9--2; dalla cima della mia testa non riesco a trovare niente di meglio (ma non mi affiderei a quello) - hai qualcosa?
balpha,

1
@balpha: Nevermind, non pensavo direttamente quando ho commentato prima. Ho pensato per qualche motivo che potresti attaccarli come 1-9 e 2-8, ma ovviamente quelle coppie stesse non sono in equilibrio!
Nabb,

Ok, eccone uno che potrebbe effettivamente essere migliore con più livelli:, ad 2 2 9 1esempio (2 + 2) * 3 = 9 + 1 * 3 per 16 dimensioni anziché 2-9+--2----118. Immagino che ci sia una soglia (forse 5 o 6 ) dopo di che una singola riga orizzontale è sempre ottimale.
Nabb,

@Nabb: Sì; questo è davvero un buon contro-esempio.
balpha,

@Nabb, una singola barra con i 2-2-+9-1saldi, con un punteggio di 13 (4*2+2*2 = 9*1+1*3). Quindi non penso che uno sia un buon controesempio.
Keith Randall,

1

Bene, questa è una vecchia domanda, ma l'ho appena vista apparire nella scheda delle domande principali, quindi ecco la mia soluzione (ottimale):

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

Guardando le regole sono abbastanza sicuro che non stia tradendo, anche se sembra che lo sia. Questo produrrà solo tutti i numeri dati in una catena verticale, per un costo totale di 2 * number_of_inputs (che è il minimo possibile perché ogni numero deve avere una barra sopra di esso, indipendentemente dal layout). Ecco un esempio:

./mobile 3 8 9 7 5

produce:

|
3
|
8
|
9
|
7
|
5

Che è ovviamente in perfetto equilibrio.


Inizialmente stavo provando qualcosa di più nello spirito di questa sfida, ma si è scoperto rapidamente che si è comunque ottimizzato per questa struttura


Probabilmente non è chiaro dalla mia descrizione, ma non è possibile collegare a |alla parte inferiore di un peso.
Keith Randall,

@KeithRandall ah ok; con questo in mente, dovrei provare a risolverlo correttamente.
Dave,

1

Ecco una soluzione che bruta forza la più piccola soluzione a fila singola. Il codice scorre su tutte le permutazioni e calcola il centro di massa per ciascuna. Se il centro di massa ha coordinate intere, abbiamo trovato una soluzione.

Dopo aver provato tutte le permutazioni, aggiungiamo un segmento al mix (equivalente a un peso di massa 0) nella nostra attuale serie di pesi e riproviamo.

Per eseguire il programma, fare 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)

che produce queste migliori soluzioni:

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

Questo non è peggio di 1 in più rispetto all'ottimale su nessuno dei casi di test, credo, e lo fa in 5 secondi.

Fondamentalmente, utilizzo un approccio a barra singola. Ordino casualmente l'input, quindi inserisco i pesi sulla barra uno alla volta. Ogni elemento viene messo nella posizione che minimizza il peso in eccesso su entrambi i lati, o la seconda posizione migliore da quella prospettiva, usando il primo 75% delle volte e l'ultimo 25% delle volte. Quindi, controllo se il cellulare è bilanciato alla fine ed è migliore del miglior dispositivo mobile trovato finora. Conservo il migliore, quindi mi fermo e lo stampo dopo 5 secondi di ricerca.

Risultati, in 5 secondi:

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

Codice:

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)

L'unica di queste soluzioni che ritengo non ottimale è la più lunga, che ha questa soluzione, che ho trovato dopo una corsa di 10 minuti:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.