Crie um celular pequeno e equilibrado


18

Você recebe vários pesos e sua tarefa é construir um pequeno celular balanceado usando esses pesos.

A entrada é uma lista de pesos inteiros no intervalo de 1 a 9, inclusive. Pode haver duplicatas.

A saída é uma imagem ascii de um celular que, quando pendurado, seria equilibrado. Talvez seja melhor mostrado pelo exemplo:

entrada

3 8 9 7 5

saída possível

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

Você deve usar os caracteres ascii, como mostrado. Os segmentos horizontais e verticais podem ter qualquer comprimento. Nenhuma parte do celular pode tocar (horizontal ou verticalmente) outra parte não conectada do celular. Todos os pesos devem ser pendurados em um segmento vertical de comprimento pelo menos 1 e deve haver um segmento vertical no qual todo o móvel está pendurado.

O tamanho de um celular é o número total de +, -e |caracteres necessários para construí-lo. Tamanhos mais baixos são melhores.

Você pode colocar quantas conexões desejar em um segmento. Por exemplo:

entrada

2 3 3 5 3 9

saída possível

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

O programa vencedor é aquele que pode gerar a menor média de tamanhos de celular para um conjunto de entradas de teste. O teste real é super secreto para impedir a codificação, mas será algo como isto:

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

Física também envolvida?
VOCÊ

1
@ S.Mark: Eu acho que você poderia dizer isso. Para os deficientes físicos, a soma de total_weight_hung_from_point * distance_of_point_from_pivotdeve ser a mesma nos dois lados do ponto de articulação.
amigos estão dizendo sobre keith

Talvez para facilitar a análise dos diagramas, faça com que uma barra seja igual a dois hífens? Tal como está, seus diagramas parecem desequilibrados.
Thomas O

Respostas:


5

Python 2.

Estou trapaceando um pouco :

  • Construo apenas celulares com uma horizontal. Tenho a sensação (mas ainda não o provei) de que o celular ideal sob as condições especificadas na verdade sempre tem apenas uma horizontal. Editar: nem sempre é verdade; com o 2 2 9 1Nabb encontrou um contra-exemplo nos comentários abaixo:

    Size 18:                Size 16:
       |                        |
    +-++--+-----+            +--++-+
    | |   |     |            |   | |
    2 9   2     1           -+-  9 1
                            | |
                            2 2
    
  • Eu apenas faço força bruta estúpida:

    1. Os pesos dados são embaralhados aleatoriamente.
    2. Dois pesos de cada vez são colocados no celular nas melhores posições, para que fique equilibrado.
    3. Se o celular resultante for melhor que o que tínhamos antes, lembre-se.
    4. Enxágüe e repita, até que um número predefinido de segundos termine.

Meus resultados para suas entradas de amostra; cada um foi executado por 5 segundos (sei que isso é ridículo para os pequenos - apenas passar por todas as permutações possíveis seria mais rápido). Observe que, como existe um elemento aleatório, as execuções subsequentes podem encontrar resultados melhores ou piores.

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

O código (detalhado, como esse não é um código de golfe):

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: pesos maiores que 9 não são possíveis. Quanto a 1 9 2 8isso gera 1-------8+-9--2; Do alto da minha cabeça, não consigo pensar em nada melhor (mas não confiaria nisso) - você tem alguma coisa?
balpha

1
@balpha: Deixa pra lá, não estava pensando direito quando comentei anteriormente. Por algum motivo, pensei que você poderia colocá-los como 1-9 e 2-8, mas obviamente esses pares não se equilibram!
Nabb 30/04

Ok, aqui está um que pode ser realmente melhor com várias camadas:, 2 2 9 1ou seja, (2 + 2) * 3 = 9 + 1 * 3 para 16, em vez de 2-9+--2----118. Acho que há um limite (talvez 5 ou 6 ) após o qual uma única linha horizontal é sempre ideal.
Nabb 30/04

@ Nabb: Sim; esse é realmente um bom contra-exemplo.
balpha

@Nabb, uma única barra com 2-2-+9-1balanças, com uma pontuação de 13 (4*2+2*2 = 9*1+1*3). Portanto, não acho que esse seja um bom contra-exemplo.
Keith Randall

1

Bem, essa é uma pergunta antiga, mas acabei de vê-la na guia de perguntas principais, então aqui está minha solução (ideal):

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

Ao olhar para as regras, tenho certeza de que não é trapaça, embora pareça que é. Isso produzirá apenas todos os números fornecidos em uma cadeia vertical, por um custo total de 2 * número_de_inputs (que é o mínimo possível porque cada número deve ter uma barra acima dela, independentemente do layout). Aqui está um exemplo:

./mobile 3 8 9 7 5

Produz:

|
3
|
8
|
9
|
7
|
5

O que obviamente está em perfeito equilíbrio.


Inicialmente, eu tentaria algo mais no espírito desse desafio, mas rapidamente percebi que ele simplesmente otimizava essa estrutura de qualquer maneira


Provavelmente não está claro na minha descrição, mas você não pode conectar a |à parte inferior de um peso.
Keith Randall

@KeithRandall ah ok; com isso em mente, talvez eu precise resolver isso adequadamente.
Dave

1

Aqui está uma solução que força bruta a menor solução de fileira única. O código itera todas as permutações e calcula o centro de massa de cada uma. Se o centro de massa possui coordenadas inteiras, encontramos uma solução.

Após todas as permutações terem sido tentadas, adicionamos um segmento à mistura (equivalente a um peso de massa 0) em nosso conjunto atual de pesos e tentamos novamente.

Para executar o programa, faça 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 produz as melhores soluções:

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

Isso não é pior que 1 a mais do que o ideal em qualquer um dos casos de teste, acredito, e o faz em 5 segundos.

Basicamente, eu uso uma abordagem de barra única. Ordeno a entrada aleatoriamente e insiro os pesos na barra, um de cada vez. Cada elemento é colocado na posição que minimiza o excesso de peso de ambos os lados, ou a segunda melhor posição dessa perspectiva, usando os primeiros 75% do tempo e os últimos 25% do tempo. Depois, verifico se o celular está equilibrado no final e é melhor que o melhor celular encontrado até agora. Eu guardo o melhor, então paro e imprimo após 5 segundos de pesquisa.

Resultados, em execuções de 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)

A única dessas soluções que acredito ser subótima é a mais longa, que possui essa solução, que encontrei após 10 minutos de execução:

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.