建立美观的除数树


43

美观的除数树是输入的除数的树n,对于任何合数m,具有的两个孩子的节点对除数是最接近的平方根m。左节点应为的较小除数,m右节点应为的较大除数m。树中的素数不应有子节点。您的树可能是文字艺术或图像的形式。文字艺术输出的规则如下。

间距规则

为了使树上的节点间隔开,我们有以下规则:

  • 从根到给定深度的节点应全部位于输出中的同一行文本上。
  / \不/ \  
 / \ / 3
2 3 2
  • 对于左节点,如果节点是一个数字,则输入分支应该在右上角,否则在最后一个数字的正上方。例:
 /与/
3720
  • 对于右边的节点,如果节点是一位数字,则输入分支应该在左上角,否则在第一位数字的正上方。例:
\ AND \
 7243
  • 对于传出的左分支,分支应在数字左侧开始一个空格。例:
  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在左边,因为它较小。在下一行,数字3左边的数字2,因为它较小。

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到这五个层次的分支,以便正确地与2430子树隔开。另外,请注意2430具有两个分支级别,因为46具有需要正确间距的子节点,并且的子节点30需要与的子节点处于同一级别24

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

挑战

  • 您的任务是为input建立正确间隔的美观的除数树n,其中n正整数大于1。
  • 您的输出可能包含前导和尾随空格以及前导和尾随换行符,但必须符合上述给定的间距规则。
  • 您的输出被允许为:文字艺术,图像(如果需要,可以添加其他格式)。
  • 对于图像,请确保树的节点间距适当,并且树中相同高度的节点在图像中的相同高度。
  • 这是代码高尔夫。最少的字节数(或等效字节)获胜。

感谢Stewie Griffin对这个想法的思考,并非常感谢Peter Taylor,Martin Ender,Mego和EᴀsᴛᴇʀʟʏIʀᴋ在重写规范方面的帮助。像往常一样,任何建议或更正均深表感谢。祝你好运,打高尔夫球!

更多测试用例:

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

感谢您的挑战。我现在可以形象地看到这些东西而不必每次都绘制它们:D
Conor O'Brien

树是否需要看起来像示例,还是可以使用内置的Mathematica函数?看起来像这样,但是具有因式分解。
JungHwan Min

@JHM我知道我应该保留图形输出标签。是的,您可以使用内置的。我将编辑挑战。
Sherlock16年

Answers:


29

Python 2中711 651 575 559 554 547个 539 540 530 522字节

在尝试写出这个答案四个月后,撞到墙上,搁置了一个星期,冲洗,重复,我终于完成了应对该挑战的适当ASCII艺术答案。剩下的就是打高尔夫球了,因此非常欢迎打高尔夫球。在线尝试!

高尔夫:重命名一些常用功能并更改返回结果的方式,为-60个字节。-73字节,用于更改子树的高度检查方式,间距变量的计算方式以及结果的返回方式。从FlipTack isdigit()替换后的-3个字节。-16字节的高尔夫球,isdigit()进一步替换并用E。-5个字节(从微小的改进到从Python 3到Python 2的更改)。-7个字节(从修改返回结果的方式)。-8个字节,从很小的更改到如何A定义,更改如何T定义以及添加W(使用以下假设),即任何子树的分支比其对应者至少长一个,其整体必须比其对应者更长,因此删除Q一起编辑结果的返回方式。使用A<10代替L(S(A))<2for A和-10个字节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. 确定最大除数对的nAB
  2. 制作的子树A,并B根据需要重新绘制。
  3. 确定子树之间应该有多少空格。
  4. 绘制并返回新的除数树。

我将按顺序进行每个步骤。

步骤1.坦率地说,这是最简单的步骤。检查z1和平方根之间的每个数字是否可整除n并获取最大的zn//z匹配的数。仅str(n)n素数(A==1B==n)返回

步骤2.绘制和的子树,AB获得子树中/\节点之间的分支数。为此,我们获取其中包含数字的每个步骤的索引,获取索引的第一个差,然后再次减去1。一旦有了高度,我们就比较它们以获得最大的高度,并用新的高度重新绘制子树。

我暗中怀疑总体上较高的子树始终具有与较短子树上的分支一样长的分支,并且可以用它来编写代码,但是我尚无此证明。中的反例11**9 = 2357947691

第3步。这一步花了几个月的时间。第2步花费了几天的时间进行编写和调试,但是找到合适的间距公式需要花费很多时间。我将看看是否可以将我想出的内容压缩成几段。请注意,此说明中的某些代码此后已从实际代码中排除。

首先,pqhPQsMp是从左分支/的末端到左子树的右末端的字符数。q是从右子树的左端到右分支的末尾的字符数/h是根和子树之间的分支数。P并且Q是刚刚倒数pq,并放置在周围空间有用/\树枝到根ns是两个子树之间相加的空格数。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)))]

等等!我们拥有一棵美观的除数树!

开球:

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

您的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(当然,您不能为此使用infix表示法,Nearest但可以使用Max@)。
Martin Ender

5
它遵循规则,但是那棵树远没有达到美观的 xD
Beta Decay

2
美丽在情人眼中:)
尼尔森

1
我不确定这是否有效。与示例不同,每行上的节点间距不均匀。此外,线路没有连接到正确的数字。
Mego

1
@Mego好吧,OP 表示它是有效的。
R. Kap

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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.