Создайте эстетически приятное дерево делителей

43

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

Правила интервалов

Чтобы разнести узлы на дереве, у нас есть следующие правила:

  • Все узлы на заданной глубине от корня должны находиться на одной строке текста в выводе.
  / \ NOT / \  
 / \ / 3
2 3 2
  • Для левых узлов входящая ветвь должна быть в верхнем правом углу, если узел представляет собой однозначное число, иначе чуть выше последней цифры. Пример:
 / А ТАКЖЕ /
3 720
  • Для правых узлов входящая ветвь должна быть в верхнем левом углу, если узел представляет собой однозначное число, иначе чуть выше первой цифры. Пример:
\ А ТАКЖЕ \
 7 243
  • Для исходящих левых ветвей ветвь должна начинаться на один пробел слева от числа. Пример:
  275
 /
11
  • Для исходящих правых ветвей ветвь должна начинаться на один пробел справа от номера. Пример:
275
   \
   25
  • Любые два узла на одном уровне дерева должны иметь как минимум два пробела между ними. В то же время любые два поддерева на одном и том же уровне дерева должны иметь как можно меньше промежутков между ними.
Это дерево не работает, потому что ** поддерево ** слишком близко.

        504           
       / \          
      / \         
     / \        
    / \       
   21 24     
  / \. / \    
 / \. / \   
3 7. 4 6  
        , / \ / \
        .2 2 2 3

Хотя у этого дерева достаточно места между его ветвями.

         504           
        / \          
       / \         
      / \        
     / \       
    / \      
   21 ... 24     
  / \ ... / \    
 / \ ... / \   
3 7 ... 4 6  
        ... / \ / \ 
        ... 2 2 2 3
  • Если любые два поддерева расположены слишком близко друг к другу на дереве, их можно разделить, добавив еще один ряд ветвей /\в дерево над родительскими элементами .
   441                              
  / \ Последняя строка еще не заполнена, и у нас уже нет свободного места.
 21 21
/ \ / \

Добавить еще один ряд веток

     441                              
    / \ Почти, но 7 и 3 слишком близко друг к другу.
   / \ Еще один ряд должен сделать это.
  21 21
 / \ / \
3 7 3 7

Добавить еще один ряд веток

      441
     / \ И мы закончили.
    / \
   / \
  21 21
 / \ / \
3 7 3 7

Примеры

В качестве полного примера, дерево делителей 24 будет выглядеть так:

     24
    /  \
   /    \
  4      6
 / \    / \
2   2  2   3

4 и 6 - пара делителей, ближайшая к квадратному корню из 24. 4 слева, потому что она меньше. На следующей строке номер 2 слева от 3, потому что он меньше.

Дерево делителей для 63 должно выглядеть так:

  63        and NOT like this        63
 /  \                               /  \
7    9                             3   21
    / \                               /  \
   3   3                             7    3

В неправильном дереве 3 и 21 не являются парой делителей, ближайших к квадратному корню из 63, а 3 и 7 не отсортированы должным образом. Расположение филиала на 21 правильно, хотя.

Для 42 вы должны иметь:

    42      and NOT        42
   /  \                   /  \
  6    7                 21   2
 / \                    /  \
2   3                  3    7

Давайте посмотрим на 720. Обратите внимание, что нам нужно пять уровней ветвей от 720так, чтобы 24и 30поддеревья были правильно разнесены. Кроме того , обратите внимание , что 24и 30имеют два уровня ветвей , так как 4и 6есть дочерние узлы , которые необходимо правильное расстояние и дочерние узлы 30должны быть на том же уровне, что и дочерние узлы 24.

           720
          /   \
         /     \
        /       \
       /         \
      /           \ 
     24           30
    /  \         /  \
   /    \       /    \
  4      6     5      6
 / \    / \          / \
2   2  2   3        2   3

Соревнование

  • Ваша задача состоит в том, чтобы создать правильно расположенное эстетически приятное дерево делителей для ввода n, где nположительное целое число больше 1.
  • Ваш вывод может содержать начальные и конечные пробелы и начальные и конечные переводы строк, но в противном случае он должен соответствовать приведенным выше правилам пробелов.
  • Ваш вывод может быть: текстовый рисунок, изображение (другие форматы, которые будут добавлены, если это необходимо).
  • Для изображений убедитесь, что узлы вашего дерева расположены правильно и что узлы с одинаковой высотой в дереве имеют одинаковую высоту на изображении.
  • Это код гольф. Наименьшее количество байтов (или эквивалент) выигрывает.

Благодарим Стью Гриффина за то, что он подумал об этой идее, и большое спасибо Питеру Тейлору, Мартину Эндеру, Мего и Эсу Я за помощь в переписывании спецификации. Как обычно, любые предложения или исправления приветствуются. Удачи и хорошего гольфа!

Больше тестовых случаев:

2

  4
 / \
2   2

    20
   /  \
  4    5
 / \
2   2

  323
 /   \
17   19

                        362880
                       /      \
                      /        \
                     /          \
                    /            \
                   /              \
                  /                \
                 /                  \
                /                    \
               /                      \
              /                        \
            576                        630
           /   \                      /   \
          /     \                    /     \
         /       \                  /       \
        /         \                /         \
       /           \              /           \
      /             \            /             \
     24             24          21             30
    /  \           /  \        /  \           /  \
   /    \         /    \      /    \         /    \
  4      6       4      6    3      7       5      6
 / \    / \     / \    / \                        / \
2   2  2   3   2   2  2   3                      2   3

              1286250
             /       \
            /         \
           /           \
          /             \
         /               \
      1050               1225
     /    \             /    \
    /      \           /      \
   /        \         /        \
  30        35       35        35
 /  \      /  \     /  \      /  \
5    6    5    7   5    7    5    7
    / \
   2   3
Sherlock9
источник
Спасибо за этот вызов. Теперь я могу визуализировать эти вещи, не рисуя их каждый раз: D
Конор О'Брайен
Должно ли дерево выглядеть как примеры, или я могу использовать встроенную функцию Mathematica? Похоже , что это , но с разложением.
JungHwan Мин
@JHM Я знал, что должен был сохранить тег графического вывода . Да, вы можете использовать этот встроенный. Я отредактирую вызов.
Sherlock9

Ответы:

29

Python 2 , 711 651 575 559 554 547 539 540 530 522 байта

После четырех месяцев попыток написать этот ответ, врезаться в стену, оставить ее на несколько недель, промыть, повторить, я наконец-то закончил правильный ответ ASCII на этот вызов. Осталось только поиграть в гольф, поэтому предложения по игре в гольф приветствуются. Попробуйте онлайн!

Гольф: -60 байт от переименования некоторых часто используемых функций и изменения способа возврата результата. -73 байта от изменения того, как проверяются высоты поддеревьев, как вычисляются интервальные переменные и как возвращается результат. -3 байта от isdigit()замены FlipTack . -16 байтов играли в гольф, что isdigit()замена еще дальше и замена "" с E. -5 байтов от незначительных улучшений и перехода с Python 3 на Python 2. -7 байтов от изменения способа возврата результата. -8 байтов от небольшого изменения к тому, как Aопределяется, изменяя, как Tопределяется, и добавляя W, используя гипотезу, что любое поддерево, по крайней мере, на одну более длинную ветвь, чем его аналог, в целом обязательно длиннее, чем его аналог , удаляяQв целом, и редактирование, как результат возвращается. -10 байт от использования A<10вместо L(S(A))<2для Aи B. -8 байт от изменения значения Hпо умолчанию до, [0]поскольку код избегает проблемы изменяемых аргументов по умолчанию, никогда не Hизменяя, не изменяя способ qопределения, используя (B>9)вместо 1-(B<10), pполностью удаляя и создавая Fвместо него p+q-M.

Исправление ошибок: Гипотеза неверна, контрпример в 11**9 = 2357947691. +1 байт

G=range;L=len;E=" "
def t(n,H=[0]):
 A=max(z*(n%z<1)for z in G(1,int(n**.5)+1));B=n/A;Z=str(n);M=L(Z)
 if A<2:return[Z]
 T=max([i for i in G(L(w))if"/"not in w[i]]for w in(t(A),t(B)));V=H[1:]or[T[k+1]-T[k]-1for k in G(L(T)-1)];x=t(A,V);y=t(B,V);P=x[0].rindex(str(A)[-1])+(A<10);q=y[0].index(str(B)[0])+(B>9);F=L(x[0])-P+q-M;h=H[0]or(F+M%2+2)/2or 1;return[E*(P+J)+(J<h and"/"+E*(2*h+M-2*J-2)+"\\"or Z)+E*(L(y[0])-q+J)for J in G(h,-1,-1)]+[(E*(2*h-F)).join(I<L(w)and w[I]or E*L(w[0])for w in(x,y))for I in G(max(L(x),L(y)))]

объяснение

Вся функция может быть сведена к четырем шагам:

  1. Определите самую большую пару делителей n, Aи B.
  2. Сделайте поддеревья Aи B, перерисовывая по мере необходимости.
  3. Определите количество пробелов, которые должны идти между поддеревьями.
  4. Нарисуйте и верните новое дерево делителей.

Я буду проходить каждый шаг по порядку.

Шаг 1. Это самый простой шаг, прямо скажем. Проверяйте каждое число zот 1 до квадратного корня на делимость на nи берите наибольшее, zи n//zэто соответствует. Возврат только str(n)если nпростое число (или A==1или B==n)

Шаг 2. Нарисуйте поддеревья Aи Bи получите количество /\ветвей между узлами в поддеревьях. Для этого мы получаем индексы каждого шага с цифрами, получаем первые различия индексов и снова вычитаем 1. Как только у нас есть высоты, мы сравниваем их, чтобы получить наибольшее, и перерисовываем поддеревья с новыми высотами.

У меня есть подозрение, что поддерево, которое в целом выше, всегда имеет ответвления, длина которых равна или равна разветвлениям на более коротком поддереве, и я могу использовать это для игры в коде, но у меня пока нет доказательств этого. Контрпример в 11**9 = 2357947691.

Шаг 3. Для написания этого шага потребовались месяцы. Шаг 2 занял несколько дней, чтобы написать и отладить, но поиск правильных формул для пробелов занял много лет. Я посмотрю, смогу ли я сжать то, что я понял, в несколько абзацев. Обратите внимание, что часть кода в этом объяснении с тех пор вышла из реального кода.

Во- первых, p, q, h, P, Q, sи M. pэто количество символов от конца левой ветви /до правого конца левого поддерева. qэто количество символов от левого конца правого поддерева до конца правой ветви /. hэто число ветвей между корнем и поддеревьями. Pи Qявляются просто противоположностями pи qполезны для размещения пространств вокруг /\ветвей вплоть до корня n. sэто количество пробелов, которые добавляются между двумя поддеревьями. Mсамый простой; это длина n. Положите графически:

            M
           ---
           720           
 |        /   \          
 |       /     \         
h|      /       \        
 |     /         \       
 |    /           \      
   P    p    s   q   Q   
------______---____------
     24           30     
    /  \         /  \    
   /    \       /    \   
  4      6     5      6  
 / \    / \          / \ 
2   2  2   3        2   3

Формула для определения pтакова: p = len(x[0]) - x[0].rindex(str(A)[-1]) - (A<10)длина, минус нулевой индекс последнего символа в A, минус поправка для однозначного As.

Формула для определения qтакова: q = y[0].index(str(B)[0]) + (B>9)индекс первого символа в B плюс поправка для нумерации минус коррекция для однозначных Bs (объединенная в одну коррекцию для многозначных Bs).

Формула для определения hзаключается в следующем: h = H[0] or (p+q+M%2+2-M)//2 or 1. Либо мы берем предопределенное, Hчто означает, что мы перерисовываем дерево (from_the_left + from_the_right + parity_space + 2 - len(root)) // 2), либо используем минимальное количество уровней ветвления, 1.

Формула для определения sзаключается в следующем: s = 2*h+M-p-q. Мы вычитаем pи qиз числа промежутков между ветвями корня в их самом широком 2*h + M.

Шаг 4. И, наконец, мы собрали все вместе. Сначала мы создаем корень, [" "*(P+h)+Z+" "*(Q+h)]затем добавляем ветви к поддеревьям [" "*(P+J)+"/"+" "*(2*h+M-2*J-2)+"\\"+" "*(Q+J)for J in G(h)][::-1]и, наконец, добавляем наши правильно расположенные поддеревья [(" "*(2*h+M-p-q)).join([(I<L(w)and w[I]or" "*L(w[0]))for w in(x,y)])for I in G(max(L(x),L(y)))].

И вуаля! У нас есть эстетически приятное дерево делителей!

Ungolfing:

def tree(n, H=[0]):
    A = max(z for z in range(1, int(n**.5)+1) if n%z<1)
    B = n/A
    Z = str(n)
    M = len(Z)
    if A < 2:
        return [Z]

    # redraw the tree so that all of the numbers are on the same rows
    x = tree(A)
    y = tree(B)
    for W in [x, y]:
        T = [i for i in range(len(W)) if "/" not in W[i]]
    V = H[1:] or [T[k+1]-T[k]-1 for k in range(len(T)-1)]
    x = tree(A, V)
    y = tree(B, V)

    # get the height of the root from the two trees
    P = x[0].rindex(str(A)[-1]) + (A < 10)
    p = len(x[0]) - P
    q = y[0].index(str(B)[0]) + (B > 9)
    Q = len(y[0]) - q
    h = hs[0] or (p+q+M%2+2-M)/2 or 1

    # and now to put the root down
    R = []
    s = 2*h+M-p-q
    for I in range(max(len(x),len(y))):
        c = I<len(x) and x[I] or " "*len(x[0])
        d = I<len(y) and y[I] or " "*len(y[0])
        R += c + " "*s + d,
    for J in range(h, -1, -1):
        if J<h:
            C = "/" + " "*(2*h+M-2*J-2) + "\\"
        else:
            C = Z
        R += [" "*(P+J) + C + " "*(Q+J)]
    return R
Sherlock9
источник
Может ли isdigitбыть ваш чек '/'<x[i].strip()[0]<':'?
FlipTack
14

Mathematica, 96 86 81 79 78 байт

Спасибо @MartinEnder за 2 байта.

TreeForm[If[PrimeQ@#,#,#0/@(#2[#,#2/#]&[Max@Nearest[Divisors@#,#^.5],#])]&@#]&

Вывод выглядит так:

введите описание изображения здесь

объяснение

Max@Nearest[Divisors@#,#^.5]

Сформировать список делителей ввода. Найдите ближайший к квадратному корню элемент ввода. ( Maxдля выравнивания вывода)

#2[#,#2/#]&

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

#0/@

Повторите процесс.

If[PrimeQ@#,#, ... ]

Если вход прост, не делайте ничего.

TreeForm

Отформатируйте вывод.

Изменить: более эстетически приятная версия (258 байт)

TreeForm[#/.{a_,_,_}:>a,VertexRenderingFunction->(#2~Text~#&),VertexCoordinateRules->Cases[#,{_,_},Infinity,Heads->True]]&@(If[PrimeQ@#,{##},{##}@@#0@@@({{#,#3-#4{1,√3}/2,#4/2},{#2/#,#3-#4{-1,√3}/2,#4/2}}&[Max@Nearest[Divisors@#,√#],##])]&[#,{0,0},1])&

Вывод выглядит так:

введите описание изображения здесь

Юнг Хван Мин
источник
3
Sqrt@#-> #^.5(конечно, тогда вы не можете использовать инфиксную нотацию для, Nearestно тогда вы можете использовать Max@).
Мартин Эндер
5
Это следует правилам, но это дерево далеко не эстетично xD
Beta Decay
2
Красота в глазах смотрящего
Нельсон
1
Я не уверен, что это действительно. В отличие от примеров, узлы в каждой строке расположены неравномерно. Кроме того, линии не соединяются с правильной цифрой.
Mego
1
@Mego Хорошо, OP сказал, что это действительно.
Р. Кап
3

Древесный уголь , 302 байта

≔⟦⟦N⁰θ⁰¦⁰⟧⟧θFθ«≔§ι⁰ζ≔⌈E…·²Xζ·⁵∧¬﹪ζκκη¿η«F⟦η÷ζη⟧«≔⟦κ⊕§ι¹Iκ⁰¦⁰⟧κ⊞ικ⊞θκ»⊞υι»»≔…⁰⌈Eθ§ι¹ηF⮌竧≔ηι⊕⌈⟦⁰⌈Eυ∧⁼§κ¹ι÷Σ⟦¹§§κ⁵¦⁴‹⁹§§κ⁵¦⁰§§κ⁶¦³‹⁹§§κ⁶¦⁰±L§κ²⟧²⟧FυF²§≔κ⁺³λ⁺⁺§ηι∨⊖L§§κ⁺⁵벦¹§§κ⁺⁵λ⁺³λ»Fυ«§≔§ι⁵¦³⁻⁻§ι³§η§ι¹∨⊖L§§ι⁵¦²¦¹§≔§ι⁶¦³⁻⁺⁺§ι³L§ι²§η§ι¹‹⁹§§ι⁶¦⁰»F⊕Lη«Fθ«F⁼§κ¹ι«←⸿M§κ³→F‹⁵Lκ«↙P↙§ηι↗»§κ²↓F‹⁵LκP↘§ηι»»M⊕§ηι↓

Попробуйте онлайн! Ссылка на подробную версию кода. Поскольку подробная версия очень многословна, он транслитерирует основной алгоритм на JavaScript:

u = []; // predefined variable, used as list of branches
q = [[+s, 0, s, 0, 0]]; // list of nodes starts with the root.
for (i of q) { // iterate nodes, includes new nodes
    z = i[0]; // get node value
    h = Math.max(...[...Array(Math.floor(z ** 0.5) + 1).keys()].slice(2).filter(
        k => z % k < 1)); // find largest factor not above square root
    if (h) {
        for (k of [h, z / h]) {
            k = [k, i[1] + 1, `${k}`, 0, 0]; // create child node
            i.push(k); // add each child to parent (indices 5 and 6)
            q.push(k); // and to master nodelist
        }
        u.push(i);
    }
}
h = new Array(Math.max(...q.map(i => i[1]))); // list of branch heights
for (i = h.length; i --> 0; ) {
    // find branch height needed to space immediate children apart at this depth
    h[i] = 1 + Math.max(...u.map(k => k[1] == j && // filter on depth
        1 + k[5][3] + (k[5][0] > 9) + k[6][2] + (k[6][0] > 9) - k[2].length
        >> 1)); // current overlap, halved, rounded up
    // calculate the new margins on all the nodes
    for (k of u) {
        k[3] = h[i] + (k[5][2].length - 1 || 1) + k[5][3]; // left
        k[4] = h[i] + (k[6][2].length - 1 || 1) + k[6][4]; // right
    }
}
// calculate the absolute left margin of all the nodes under the root
for (i of u) {
    i[5][3] = i[3] - h[i[1]] - (i[5][2].length - 1 || 1);
    i[6][3] = i[3] + i[2].length + h[i[1]] - (i[6][0] > 9);
}
// print the nodes (sorry, no transliteration available)
Нил
источник