Zbuduj mały i zrównoważony telefon komórkowy

18

Otrzymujesz mnóstwo ciężarów, a Twoim zadaniem jest zbudowanie małego, zrównoważonego telefonu komórkowego przy użyciu tych ciężarów.

Dane wejściowe to lista liczb całkowitych w zakresie od 1 do 9 włącznie. Mogą istnieć duplikaty.

Wyjście to obraz ascii telefonu komórkowego, który po zawieszeniu byłby zrównoważony. Być może najlepiej to pokazuje przykład:

Wejście

3 8 9 7 5

możliwa wydajność

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

Musisz użyć znaków ascii, jak pokazano. Segmenty poziomy i pionowy mogą mieć dowolną długość. Żadna część telefonu komórkowego nie może dotykać (poziomo lub pionowo) innej niepołączonej części telefonu komórkowego. Wszystkie obciążniki muszą być zawieszone na pionowym segmencie o długości co najmniej 1 i musi istnieć pionowy segment, na którym zawieszony jest cały telefon.

Wielkość komórkowego jest całkowita liczba +, -i |znaki wymagane, aby go zbudować. Niższe rozmiary są lepsze.

Możesz umieścić tyle połączeń w segmencie, ile chcesz. Na przykład:

Wejście

2 3 3 5 3 9

możliwa wydajność

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

Zwycięskim programem jest ten, który może wygenerować najniższą średnią wielkość urządzeń mobilnych dla testowego zestawu danych wejściowych. Prawdziwy test jest super tajny, aby zapobiec kodowaniu na stałe, ale będzie on mniej więcej taki:

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
źródło
Fizyka też jest zaangażowana?
TY
1
@ S.Mark: Myślę, że możesz tak powiedzieć. W przypadku osób niepełnosprawnych fizycznie suma total_weight_hung_from_point * distance_of_point_from_pivotmusi być taka sama po obu stronach punktu obrotu.
Keith Randall
Być może, aby ułatwić przeglądanie diagramów, spraw, aby jeden słupek był równy około dwóm łącznikom? W tej chwili twoje diagramy wyglądają na niezrównoważone.
Thomas O

Odpowiedzi:

5

Python 2.

Trochę oszukuję :

  • Buduję tylko telefony komórkowe z jednym poziomym. Mam wrażenie (ale tego nie udowodniłem), że optymalny telefon komórkowy w danych warunkach faktycznie zawsze ma tylko jeden poziom. Edycja: Nie zawsze prawda; z 2 2 9 1Nabb znalazł kontrprzykład w komentarzach poniżej:

    Size 18:                Size 16:
       |                        |
    +-++--+-----+            +--++-+
    | |   |     |            |   | |
    2 9   2     1           -+-  9 1
                            | |
                            2 2
    
  • Po prostu robię głupie brutalne zmuszanie:

    1. Podane wagi są losowo tasowane.
    2. Dwie wagi naraz są umieszczane na telefonie w najlepszych pozycjach, tak aby pozostawał w równowadze.
    3. Jeśli wynikowy telefon komórkowy jest lepszy niż jakikolwiek wcześniej, pamiętaj o tym.
    4. Płucz i powtarzaj, aż upłynie określona liczba sekund.

Moje wyniki dla twoich przykładowych danych wejściowych; każdy był uruchamiany przez 5 sekund (jestem świadomy, że jest to niedorzeczne dla małych - po prostu przejście przez wszystkie możliwe kombinacje byłoby szybsze). Zauważ, że ponieważ istnieje element losowy, kolejne testy mogą znaleźć lepsze lub gorsze wyniki.

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

Kod (pełny, ponieważ to nie jest kod golfowy):

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
źródło
@Nabb: Ciężary większe niż 9 nie są możliwe. Co do 1 9 2 8generuje 1-------8+-9--2; z czubka głowy nie mogę wymyślić nic lepszego (ale nie polegałbym na tym) - masz coś?
balpha
1
@balpha: Nevermind, nie myślałem prosto, kiedy skomentowałem wcześniej. Pomyślałem z jakiegoś powodu, że możesz trzymać je jako 1-9 i 2-8, ale oczywiście te pary same się nie równoważą!
Nabb
Okej, oto jedna, która może być lepsza w przypadku wielu warstw: 2 2 9 1tj. (2 + 2) * 3 = 9 + 1 * 3 dla rozmiaru 16 zamiast 2-9+--2----118. Domyślam się, że istnieje próg (może 5 lub 6 ), po którym pojedynczy poziomy rząd jest zawsze optymalny.
Nabb
@Nabb: Yep; to rzeczywiście dobry kontrprzykład.
balpha
@Nabb, Pojedynczy pasek z 2-2-+9-1saldami, z wynikiem 13 (4*2+2*2 = 9*1+1*3). Więc nie sądzę, że jest to dobry kontrprzykład.
Keith Randall
1

To stare pytanie, ale właśnie zobaczyłem, że pojawia się w zakładce górnych pytań, więc oto moje (optymalne) rozwiązanie:

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

Patrząc na zasady jestem pewien, że to nie oszustwo, choć wydaje się, że tak jest. Spowoduje to po prostu wyprowadzenie wszystkich podanych liczb w łańcuchu pionowym, przy całkowitym koszcie 2 * liczba_wejść (co jest minimalnym możliwym wynikiem, ponieważ każda liczba musi mieć słupek nad nim, bez względu na układ). Oto przykład:

./mobile 3 8 9 7 5

Produkuje:

|
3
|
8
|
9
|
7
|
5

Co oczywiście jest w idealnej równowadze.


Początkowo zamierzałem spróbować czegoś więcej w duchu tego wyzwania, ale szybko okazało się, że i tak zoptymalizowałem tę strukturę

Dave
źródło
Prawdopodobnie nie jest jasne z mojego opisu, ale nie można podłączyć |do dolnej części wagi.
Keith Randall
@KeithRandall ah ok; mając to na uwadze, być może będę musiał spróbować rozwiązać to poprawnie.
Dave
1

Oto rozwiązanie, które brutalnie wymusza najmniejsze jednorzędowe rozwiązanie. Kod iteruje wszystkie permutacje i oblicza środek masy dla każdego z nich. Jeśli środek masy ma współrzędne całkowite, znaleźliśmy rozwiązanie.

Po wypróbowaniu wszystkich permutacji dodajemy segment do mieszanki (równoważny masie masy 0) w naszym bieżącym zestawie wag i ponawiamy próbę.

Aby uruchomić program, wykonaj 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)

który produkuje te najlepsze rozwiązania:

    |
+-+-+-+-+
| | | | |
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
źródło
0

Python 3

Uważam, że nie jest to gorsze niż 1 więcej niż optymalne w każdym z przypadków testowych i robi to w 5 sekund.

Zasadniczo używam podejścia z pojedynczym paskiem. Losowo zamawiam dane wejściowe, a następnie wkładam odważniki do pręta pojedynczo. Każdy element jest albo ustawiony w pozycji, która minimalizuje nadwagę po obu stronach, albo drugą najlepszą pozycję z tej perspektywy, wykorzystując pierwsze 75% czasu, a drugie 25% czasu. Następnie sprawdzam, czy telefon komórkowy jest zrównoważony na końcu i czy jest lepszy niż najlepszy telefon komórkowy znaleziony do tej pory. Przechowuję najlepszy, a następnie zatrzymuję się i drukuję go po 5 sekundach wyszukiwania.

Wyniki w 5-sekundowych seriach:

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

Kod:

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)

Jedyne z tych rozwiązań, które moim zdaniem są nieoptymalne, jest najdłuższe, które ma takie rozwiązanie, które znalazłem po 10 minutach pracy:

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
źródło