验证河内塔解决方案


29

如果你不知道 河内塔是,我将简要解释一下:三根杆和一些圆盘的尺寸各不相同。最初,所有光盘都按顺序排列在第一塔上:最大的光盘在底部,最小的光盘在顶部。目的是将所有圆盘移至第三杆。听起来容易吗?要注意的是:您不能将光盘放置在比另一张光盘小的光盘上。您一次只能拿一个光盘将它们移动到另一根棒上,并且您只能将光盘放在棒上,而不是放在桌子上,这是一个卑鄙的混蛋。

ascii示例解决方案:

  A      B      C
  |      |      |      
 _|_     |      |      
__|__    |      |


  A      B      C
  |      |      |      
  |      |      |      
__|__   _|_     |


  A      B      C
  |      |      |      
  |      |      |      
  |     _|_   __|__


  A      B      C
  |      |      |      
  |      |     _|_     
  |      |    __|__      

挑战

有三个杆,分别称为A,B和C。(如果有帮助,您也可以分别称呼它们1,2和3)。一开始,所有n个圆盘都位于杆A(1)上。

您的挑战是验证河内塔的解决方案。您需要确保:

  1. 最后,所有n个圆盘都位于杆C(3)上。
  2. 对于处于任何给定状态的任何给定光盘,其下方都没有较小的光盘。
  3. 没有明显的错误,例如尝试将光盘从空的杆中取出或将光盘移至不存在的杆中。

(解决方案不一定是最佳的。)

输入值

您的程序将收到两个输入:

  1. 光盘数量n(整数)
  2. 所采取的移动,将由以下一组元组组成:(塔以从中取出当前最上面的光盘),(塔以从中取出该光盘)每个元组都指一个移动。您可以选择它们的表示方式。例如,类似下面的方式,我在上面的ascii中绘制了n = 2的解决方案。(我将在测试用例中使用第一个,因为它很容易在眼睛上看到):

    “ A-> B; A-> C; B-> C”

    [(“ A”,“ B”),(“ A”,“ C”),(“ B”,“ C”)]

    [(1,2),(1,3),(2,3)]

    “ ABACBC”

    [1,2,1,3,2,3]

输出量

  • 真实,如果可以在“挑战”下找到条件。

  • 虚假,如果他们不这样做。

测试用例:

真正:

n=1, "A->C"

n=1, "A->B ; B->C"

n=2, "A->B ; A->C ; B->C"

n=2, "A->C ; C->B ; A->C ; B->C"

n=2, "A->C ; A->B ; C->B ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C ; B->C"

假:

@MartinEnder建议的第三名,@ Joffan建议的第七名

n=1, "A->B"

n=1, "C->A"

n=2, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=2, "A->B ; A->C ; C->B"

n=2, "A->C ; A->B ; C->B ; B->A"

n=2, "A->C ; A->C"

n=3, "A->B ; A->D; A->C ; D->C ; A->C"

n=3, "A->C ; A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->B ; B->C ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; C->B"

n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C"

n=4, "A->B ; A->B ; A->B ; A->C ; B->C ; B->C ; B->C"

这是代码高尔夫,最短的解决方案胜出。适用标准规则和漏洞。不含电池。


是不是也没关系,如果第二个输入可以用你的方法来表示,而是用数字来代替字母(即A=1B=2C=3等等)?
卡普

1
我可以将输入零索引吗?
Rohan Jhunjhunwala

1
如果从空的或不存在的杆中取出磁盘时抛出错误,可以吗?
R. Kap

1
我们可以假设不会有像这样的不动A->A吗?
马丁·恩德

2
@Kobi您必须检查一下,moving discs to nonexistant rods.是的,它是D
edc65 '16

Answers:


7

视网膜84 80字节

-5字节感谢Martin Ender

~
 ~$'
$
ABC
{`^(.)(.*)( ~+)\1
$3$2$1
}`^(\W+)(\w)(.*)(?<=\1~+|\w)\2
$3$1$2
^AB 

在线尝试!(加上5个字节用于逐行测试)

该代码模拟了完整的游戏。

  • 输入为ACABCBACBABCAC~~~
    ~~~表示三张光盘。
  • 前四行将输入转换为游戏格式:ACABCBACBABCAC ~~~ ~~ ~ABC
    最初,A杆具有所有3个圆盘,而B和C杆为空。
  • 接下来,我们有两个步骤的循环:
    • 以该行中的第一个字母表示下一个源棒。找到该杆,将最后一张光盘放进去。取出字母,然后将光盘移至鲜明位置(将其拾起)。
      在第一个示例中,第一步之后,文本将如下所示: ~CABCBACBABCAC ~~~ ~~ABC
    • 在第二阶段,我们找到目标杆,然后将圆盘移到那里。我们验证杆是空的,还是顶部有更大的圆盘:ABCBACBABCAC ~~~ ~~AB ~C
  • 最后,我们确认A和B杆是空的-这意味着所有圆盘都在C中(最后一行还有多余的空间)。

哇,真是令人印象深刻
Rohan Jhunjhunwala

17

视网膜167个 165 157 150 123字节

这似乎完全是一个挑战,应该使用一个正则表达式来解决...(尽管标题为“ Retina”,这只是一个香草的.NET正则表达式,匹配有效输入)。

^(?=\D*((?=(?<3>1+))1)+)((?=A(?<1-3>.+)|B(?<1-4>.+)|C(?<1-5>.+)).(?=A.*(?!\3)(\1)|B.*(?!\4)(\1)|C.*(?!\5)(\1)).)+(?!\3|\4)1

输入格式是格式的指令列表AB,后跟n一进制数字1。没有分隔符。输出1有效和0无效。

在线尝试!(前两个字符启用换行分隔的测试套件。)

替代解决方案,相同的字节数:

^(?=\D*((?=(?<3>1+))1)+)((?=A(?<1-3>.+)|B(?<1-4>.+)|C(?<1-5>.+)).(?=A.*(?!\3)(\1)|B.*(?!\4)(\1)|C.*(?!\5)(\1)).)+(?<-5>1)+$

这可以通过可能使用被缩短111111代替ABC但是我得考虑以后。将程序分为几个阶段可能更短,但是挑战在哪里呢?;)

说明

该解决方案大量使用.NET的平衡组。有关完整的解释,请参见我在Stack Overflow上的文章,但要点是,.NET中的捕获组是堆栈,其中每个新捕获都将推入另一个子字符串,并且还可以再次从此类堆栈中弹出。这使您可以计算字符串中的各种数量。在这种情况下,它使我们可以将三个棒直接实现为三个不同的捕获组,其中每个光盘都由一个捕获表示。

为了使圆盘在杆之间移动,我们使用了怪异的(?<A-B>...)语法。通常,这会从堆栈中弹出一个捕获B,并将A弹出的捕获与该组开始之间的字符串推入堆栈。因此(?<A>a).(?<B-A>c)与匹配abcA留空,并B带有b(与相对c)。但是,由于.NET可变长度后视,捕获的内容(?<A>...)(?<B-A>...)重叠的内容都是可能的。无论出于何种原因,如果是这样,则将两组的交集推到上B。我已在此答案中有关平衡组的“高级部分”中详细介绍了此行为。

转到正则表达式。标尺ABC对应于group 345在正则表达式中。让我们从初始化rod开始A

^                 # Ensure that we start at the beginning of the input.
(?=               # Lookahead so that we don't actually move the cursor.
  \D*             # Skip all the instructions by matching non-digit characters.
  (               # For each 1 at the end of the input...
    (?=(?<3>1+))  # ...push the remainder of the string (including that 1)
                  # onto stack 3.
  1)+
)

因此,例如,如果输入以结尾111,则第3组/杆A现在将保存捕获列表[111, 11, 1](顶部在右侧)。

代码的下一部分具有以下结构:

(
  (?=A...|B...|C...).
  (?=A...|B...|C...).
)+

该循环的每次迭代处理一条指令。第一个交替将圆盘从给定的杆上拉到临时组上,第二个交替将那个圆盘放在另一个给定的杆上。稍后我们将看到这是如何工作的以及如何确保此举有效。

首先,从源棒上取下光盘:

(?=
  A(?<1-3>.+)
|
  B(?<1-4>.+)
|
  C(?<1-5>.+)
)

这使用了我上面描述的怪异的群交行为。需要注意的是组34并且5会一直持有的子串1的,其长度对应于光盘的大小字符串的结尾秒。现在(?<1-N>.+),我们将顶部的光盘弹出堆栈N,并将此子字符串与匹配项的交集推入.+堆栈1。由于.+始终必须覆盖弹出的整个捕获N,因此我们知道这只是移动捕获。

接下来,我们将该光盘从堆栈1放到与第二个杆相对应的堆栈上:

(?=
  A.*(?!\3)(\1)
|
  B.*(?!\4)(\1)
|
  C.*(?!\5)(\1)
)

请注意,我们不必清理堆栈1,我们可以将光盘保留在那里,因为在再次使用堆栈之前,我们会在上面放一张新光盘。这意味着我们可以避免(?<A-B>...)语法,只需将字符串复制到即可(\1)。为了确保该移动有效,我们使用负前瞻(?!\N)。这样可以确保,从我们要匹配当前光盘的位置开始,就不可能匹配已经在堆栈中的光盘N。仅当a)\N由于堆栈完全为空而永远不匹配或b)the disc on top of stackN时,才会发生这种情况is larger than the one we're trying to match with \ 1`时,。

最后,所有剩下的就是确保一)我们已经匹配的所有指令和b)杆AB是空的,这样所有的光盘已被移动到C

(?!\3|\4)1

我们只是简单地检查了两者\3都不\4匹配(只有两者都为空,因为任何实际的光盘都可以匹配的情况),然后我们可以匹配一个,1这样我们就不会省略任何指令。


14

Java“仅” 311 272 263 261 260 259 256字节

@Frozn注意到较旧的调试功能以及一些巧妙的高尔夫技巧,因此节省了39个无数字节。

高尔夫版

int i(int n,int[]m){int j=0,k=0,i=n;Stack<Integer>t,s[]=new Stack[3];for(;j<3;)s[j++]=new Stack();for(;i-->0;)s[0].push(i);for(;k<m.length;k+=2)if((t=s[m[k+1]]).size()>0&&s[m[k]].peek()>t.peek())return 0;else t.push(s[m[k]].pop());return s[2].size()<n?0:1;}

在每个步骤中不带解释和漂亮的打印堆栈

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package codegolf;

/**
 *
 * @author rohan
 */
import java.util.Arrays;
import java.util.Stack;
public class CodeGolf {
    //golfed version
    int i(int n,int[]m){int j=0,k=0,i=n;Stack<Integer>[] s=new Stack[3];for(;j<3;j++)s[j]=new Stack();for(;i-->0;)s[0].push(i);for(;k<m.length;System.out.println(Arrays.toString(s)),k+=2)if(!s[m[k+1]].isEmpty()&&s[m[k]].peek()>s[m[k+1]].peek())return 0;else s[m[k+1]].push(s[m[k]].pop());return s[2].size()==n?1:0;}
    /** Ungolfed
        * 0 as falsy 1 as truthy
        * @param n the number of disks
        * @param m represents the zero indexed stacks in the form of [from,to,from,to]
        * @return 0 or 1 if the puzzle got solved, bad moves result in an exception
        */
    int h(int n, int[] m) {
        //declarations
        int j = 0, k = 0, i = n;
        //create the poles
        Stack<Integer>[] s = new Stack[3];
        for (; j < 3; j++) {
            s[j] = new Stack();
        }
        //set up the first tower using the "downto operator
        for (; i-- > 0;) {
            s[0].push(i);
        }
    //go through and perform all the moves
        for (; k < m.length; System.out.println(Arrays.toString(s)), k += 2) {
            if (!s[m[k + 1]].isEmpty() && s[m[k]].peek() > s[m[k + 1]].peek()) {
                return 0;//bad move
            } else {
                s[m[k + 1]].push(s[m[k]].pop());
            }
        }
        return s[2].size() == n ? 1 : 0;// check if all the disks are done
    }
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
    //test case
        System.out.println( new CodeGolf().h(3,new int[]{0,2,0,1,2,1,0,2,1,0,1,2,0,2})==1?"Good!":"Bad!");
    }

}

非高尔夫版本具有一项功能,可以像这样在每个步骤中打印出堆栈的样子。

[[2, 1], [], [0]]
[[2], [1], [0]]
[[2], [1, 0], []]
[[], [1, 0], [2]]
[[0], [1], [2]]
[[0], [], [2, 1]]
[[], [], [2, 1, 0]]
Good!

怎么System.out.println(Arrays.toString(s))办?
Frozn

它将漂亮地打印堆栈。像这样[[2,1,0],[] []]
Rohan Jhunjhunwala

哎呀@Frozn,现在已删除了调试功能
Rohan Jhunjhunwala

我知道,只是想知道为什么它存在:)您还可以替换&&&
Frozn

@Frozn我不能悲哀地替换掉它,因为我依靠短路行为来避免偷看一个空堆栈。感谢39字节的减少
Rohan Jhunjhunwala

9

Python 2,186 167 158 135 127 115个 110 102字节

n,m=input()
x=[range(n),[],[]]
for a,b in m:p=x[a].pop();e=x[b];e and 1/(p>e[-1]);e+=p,
if x[0]+x[1]:_

在STDIN上以以下格式输入:

(1,[(0,1),(1,2)])

即,一个包含光盘数量的Python元组和一个Python元组列表 (from_rod,to_rod)。与Python中一样,括号是可选的。标尺为零索引。

例如,此测试用例:

n=2; "A->B ; A->C ; B->C"

将给出为:

(2,[(0,1),(0,2),(1,2)])

如果溶液是有效的,输出没有和退出时的退出代码0。如果它是无效,抛出异常并退出以1为退出代码抛出IndexError如果移动到一个不存在的杆或试图采取圆盘断没有碟片的杆,ZeroDivisionError如果碟片放在较小碟片的顶部,或者NameError末端的第一或第二杆上还有碟片。

感谢@KarlKastor,节省了13个字节!

@xnor节省了8个字节!


1
检查每堆的检查似乎太复杂了。您是否仅检查移动的磁盘是否大于移动到的堆的顶部磁盘?
xnor

@xnor谢谢,这应该工作。立即添加。

5

Python 2.7版,173个 158 138 130 127 123字节:

r=range;a,b=input();U=[r(a,0,-1),[],[]]
for K,J in b:U[J]+=[U[K].pop()]if U[J]<[1]or U[K]<U[J]else Y
print U[-1]==r(a,0,-1)

通过stdin接受输入,格式为(<Number of Discs>,<Moves>),其中<Moves>给出的数组包含每个动作对应的元组,每个元组包含一对逗号分隔的整数。例如,测试用例:

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C" 

该帖子中给出的内容为:

(3,[(0,2),(0,1),(2,1),(0,2),(1,0),(1,2),(0,2)]) 

到我的程序。IndexError如果不满足第三个条件,则输出a,如果不满足NameError第二个条件,并且不满足False第一个条件,则输出a。否则输出True


两件事:变量Y从未在您的代码中定义(我认为应该是J),U[J]+=[Y,[U[K].pop()]][U[J]<[1]or U[K]<U[J]]并且比3个字符短stmt1 if cond else stmt2
jermenkoo

@jermenkoo好吧,我使用Y类似这样的变量是在NameError不满足第二个条件时引发。如果我要更改YJ,则NameError不会提出。出于这个原因,我也不能这样做U[J]+=[Y,[U[K].pop()]][U[J]<[1]or U[K]<U[J]],因为这将提高NameError 所有的时间,不只是当第二条件不满足。
R. Kap

好,谢谢您的解释!
jermenkoo

5

VBA,234个 217 213 196字节

Function H(N,S)
ReDim A(N)
While P<Len(S)
P=P+2:F=1*Mid(S,P-1,1):T=1*Mid(S,P,1)
E=E+(T>2):L=L+T-F
For i=1 To N
If A(i)=F Then A(i)=T:Exit For
E=E+(A(i)=T)+(i=N)
Next
Wend
H=L+9*E=2*N
End Function

移动的输入格式是数字为偶数的字符串(012)。调用在电子表格中,= H([光盘数],[移动字符串])

阵列A保持各种圆盘的杆位置。举动只是将第一次出现的“ From”杆号更新为“ To”杆号。如果您首先遇到“至”杆盘,或者没有“来自”杆盘,则此举无效。A的总“杆值”保持在L中,该值必须以2N结尾。错误累积为E中的负数。

与其他解决方案一样,禁止将光盘从塔架“移动”到同一塔架。我可以再禁止6个字节。

结果

函数结果在第一列中(最后n = 3种情况是我使用额外的杆进行加法运算)。

TRUE    1   02
TRUE    1   0112
TRUE    2   010212
TRUE    2   02210212
TRUE    2   020121101202
TRUE    3   02012102101202
TRUE    4   010212012021010212102012010212

FALSE   1   01
FALSE   1   20
FALSE   2   02012102101202
FALSE   2   010221
FALSE   2   02012110
FALSE   2   0202
FALSE   3   0202012102101202
FALSE   3   0201210112101202
FALSE   3   02012102101221
FALSE   3   0103023212
FALSE   4   0102120120210102121020120102
FALSE   4   01010102121212

2

php,141字节

<?php $a=$argv;for($t=[$f=range($a[++$i],1),[],[]];($r=array_pop($t[$a[++$i]]))&&$r<(end($t[$a[++$i]])?:$r+1);)$t[$a[$i]][]=$r;echo$t[2]==$f;

命令行脚本,将输入作为高度,然后将一系列数组索引(索引为0)用于1个或2个高度最短的测试用例,例如1 0 2或2 0 1 0 2 1 2。
在真实情况下回显1,在错误情况下不回声。
发出2条通知和1条警告,因此需要在使这些通知静音的环境中运行。


1

JavaScript(ES6),108

n=>s=>!s.some(([x,y])=>s[y][s[y].push(v=s[x].pop())-2]<v|!v,s=[[...Array(s=n)].map(_=>s--),[],[]])&s[2][n-1]

输入格式:具有2个参数的函数

  • arg 1,数值,环数
  • arg 2,字符串数组,每个字符串2个字符'0','1','2'

输出:如果可以,则返回1;如果无效,则返回0;如果不存在标尺,则返回异常

少打高尔夫球和解释

n=>a=>(
  // rods status, rod 0 full with an array n..1, rod 1 & 2 empty arrays
  s = [ [...Array(t=n)].map(_=>t--), [], [] ],
  // for each step in solution, evaluate function and stop if returns true
  err = a.some( ([x,y]) => {
    v = s[x].pop(); // pull disc from source rod
    // exception is s[x] is not defined
    if (!v) return 1; // error source rod is empty
    l = s[y].push(v); // push disc on dest rod, get number of discs in l
    // exception is s[y] is not defined
    if(s[y][l-2] < v) return 1; // error if undelying disc is smaller
  }),
  err ? 0 // return 0 if invalid move
  : s[2][n-1]; // il all moves valid, ok if the rod 2 has all the discs
)

测试 说明:需要Test函数的第一行才能将问题中给出的输入格式转换为我的函数期望的输入

F=
n=>s=>!s.some(([x,y])=>s[y][s[y].push(v=s[x].pop())-2]<v|!v,s=[[...Array(s=n)].map(_=>s--),[],[]])&s[2][n-1]

Out=x=>O.textContent+=x+'\n'

Test=s=>s.split`\n`.map(r=>[+(r=r.match(/\d+|.->./g)).shift(),r.map(x=>(parseInt(x[0],36)-10)+''+(parseInt(x[3],36)-10))])
.forEach(([n,s],i)=>{
  var r
  try {
    r = F(+n)(s);
  } 
  catch (e) {
    r = 'Error invalid rod';
  }
  Out(++i+' n:'+n+' '+s+' -> '+r)
})

Out('OK')
Test(`n=1, "A->C"
n=1, "A->B ; B->C"
n=2, "A->B ; A->C ; B->C"
n=2, "A->C ; C->B ; A->C ; B->C"
n=2, "A->C ; A->B ; C->B ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C ; B->C"`)

Out('\nFail')
Test( `n=1, "A->B"
n=1, "C->A"
n=2, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=2, "A->B ; A->C ; C->B"
n=2, "A->C ; A->B ; C->B ; B->A"
n=2, "A->C ; A->C"
n=3, "A->B ; A->D; A->C ; D->C ; A->C"
n=3, "A->C ; A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->B ; B->C ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; C->B"
n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C"
n=4, "A->B ; A->B ; A->B ; A->C ; B->C ; B->C ; B->C"`)
<pre id=O></pre>

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.