Создайте маленький и сбалансированный мобильный


18

Вам дают кучу весов, и ваша задача - создать небольшой сбалансированный мобильный телефон, используя эти веса.

Входные данные представляют собой список целых весов в диапазоне от 1 до 9 включительно. Там могут быть дубликаты.

Вывод представляет собой изображение ASCII мобильного телефона, который при зависании будет сбалансирован. Возможно, лучше всего показать на примере:

вход

3 8 9 7 5

возможный вывод

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

Вы должны использовать символы ascii, как показано на рисунке. Горизонтальные и вертикальные сегменты могут быть любой длины. Никакая часть мобильного телефона не может касаться (по горизонтали или вертикали) другой неподключенной части мобильного телефона. Все веса должны быть подвешены на вертикальном отрезке длиной не менее 1, и должен быть вертикальный отрезок, на котором подвешен весь мобильный телефон.

Размер мобильного телефона является общим количеством +, -и |символами , необходимым для создания его. Чем меньше размеры, тем лучше.

Вы можете разместить столько соединений в сегменте, сколько захотите. Например:

вход

2 3 3 5 3 9

возможный вывод

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

Победившая программа - та, которая может генерировать наименьшее среднее число мобильных размеров для тестового набора входных данных. Настоящий тест является суперсекретным, чтобы предотвратить жесткое кодирование, но это будет что-то вроде этого:

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

Физика тоже участвует?
ВЫ

1
@ С.Марк: Я думаю, вы могли бы сказать это. Для людей с физическими недостатками сумма total_weight_hung_from_point * distance_of_point_from_pivotдолжна быть одинаковой с обеих сторон от точки поворота.
Кит Рэндалл

Возможно, чтобы облегчить изучение диаграмм, сделать так, чтобы один столбец равнялся примерно двум дефисам? В таком виде ваши диаграммы выглядят неуравновешенными.
Томас О

Ответы:


5

Python 2.

Я немного обманываю :

  • Я строю мобильные телефоны только с одной горизонталью. У меня есть чувство (но я не доказал это), что оптимальный мобильный телефон в данных условиях фактически всегда имеет только одну горизонталь. Изменить: не всегда верно; с 2 2 9 1Nabb нашел контрпример в комментариях ниже:

    Size 18:                Size 16:
       |                        |
    +-++--+-----+            +--++-+
    | |   |     |            |   | |
    2 9   2     1           -+-  9 1
                            | |
                            2 2
    
  • Я просто делаю глупое грубое принуждение

    1. Указанные веса перемешиваются случайным образом.
    2. Два веса за один раз размещаются на мобильном телефоне в лучших положениях, так что он остается сбалансированным.
    3. Если получающийся в результате мобильный телефон лучше, чем любой, который у нас был раньше, запомните это.
    4. Промойте и повторяйте, пока не истечет заданное количество секунд.

Мои результаты для ваших примеров входных данных; каждый из них запускался в течение 5 секунд (я знаю, что для маленьких это смешно - просто пройти через все возможные перестановки будет быстрее). Обратите внимание, что, поскольку есть случайный элемент, последующие прогоны могут найти лучшие или худшие результаты.

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

Код (многословный, так как это не код гольф):

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: Более высокий вес, чем 9, невозможен. Что касается 1 9 2 8этого порождает 1-------8+-9--2; из головы не могу придумать ничего лучшего (но я бы на это не полагался) - у вас есть что-нибудь?
Балфа

1
@balpha: Неважно, я не думал прямо, когда я прокомментировал ранее. Я почему-то подумал, что вы можете поставить их как 1-9 и 2-8, но очевидно, что сами эти пары не сбалансированы!
Набб

Хорошо, вот тот, который на самом деле может быть лучше с несколькими слоями: 2 2 9 1т.е. (2 + 2) * 3 = 9 + 1 * 3 для размера 16 вместо 2-9+--2----118. Я бы предположил, что есть порог (может быть 5 или 6 ) после которого один горизонтальный ряд всегда оптимален.
Набб

@Nabb: Да; это действительно хороший контрпример.
Балфа

@Nabb, один бар с 2-2-+9-1балансами, со счетом 13 (4*2+2*2 = 9*1+1*3). Так что я не думаю, что это хороший контрпример.
Кит Рэндалл

1

Ну, это старый вопрос, но я только что увидел, что он появился на вкладке главных вопросов, так что вот мое (оптимальное) решение:

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

Глядя на правила, я почти уверен, что это не обман, хотя кажется, что это так. Это будет просто выводить все заданные числа в вертикальной цепочке, для общей стоимости 2 * number_of_inputs (что является минимально возможным, потому что у каждого числа должна быть полоса над ним, независимо от того, какой макет). Вот пример:

./mobile 3 8 9 7 5

Производит:

|
3
|
8
|
9
|
7
|
5

Что, конечно, в идеальном балансе.


Изначально я собирался попробовать что-то еще в духе этой задачи, но быстро оказалось, что она все равно оптимизировалась под эту структуру


Вероятно, не ясно из моего описания, но вы не можете подключить |к нижней части веса.
Кит Рэндалл

@KeithRandall ах хорошо; Имея это в виду, я мог бы попытаться решить это должным образом.
Дейв

1

Вот решение, которое грубой силой наименьшее решение одной строки. Код перебирает все перестановки и вычисляет центр масс для каждого. Если центр масс имеет целочисленные координаты, мы нашли решение.

После того, как все перестановки были опробованы, мы добавляем сегмент в микс (эквивалентный весу массы 0) в нашем текущем наборе весов и повторяем попытку.

Чтобы запустить программу, сделайте 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)

который производит эти лучшие решения:

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

Я полагаю, что это не хуже, чем на 1 больше, чем оптимально в любом из тестовых случаев, и делает это за 5 секунд.

В основном, я использую подход с одним баром. Я случайным образом упорядочиваю входные данные, а затем вставляю веса на линейку по одному. Каждый элемент либо помещается в положение, которое минимизирует избыточный вес с обеих сторон, либо во второе лучшее положение с этой точки зрения, используя первые 75% времени и последние 25% времени. Затем я проверяю, сбалансирован ли мобильный телефон в конце, и он лучше, чем лучший мобильный телефон, найденный до сих пор. Я сохраняю лучший, затем останавливаю и распечатываю его через 5 секунд поиска.

Результаты за 5 секунд:

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

Код:

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)

Единственное из этих решений, которое я считаю неоптимальным, - самое длинное, у которого есть это решение, которое я нашел после 10-минутного прогона:

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.