去年(2009年),Google Code Jam是第1B回合中的第一个问题:Decision Tree
由于问题似乎是针对类似Lisp的语言而定制的,我们在SO上自发地有一个exciting codegolf,其中一些语言设法用比任何Lisp变种更少的字符解决问题,使用了相当多的不同的技术。
今年的第1B轮问题A(File Fix-it)似乎也适用于特定的语言系列,即Unix shell脚本。所以继续“1B-A传统”是合适的。 :p但哪种语言最终会得到最短的代码?让我们编写代码,看看吧!
问题描述(改编自官方网页):
您将获得 T 测试用例。每个测试用例都包含 N 行,列出了计算机上当前存在的所有目录的完整路径。例如:
/home
/home/awesome
/home/awesome/wheeeeeee
/home/awesome/wheeeeeee/codegolfrocks
/home/thecakeisalie
接下来,您将获得 M 行,其中列出了您要创建的目录的完整路径。它们的格式与前面的示例相同。您可以使用mkdir
命令创建目录,但只有父目录已存在才能创建目录。例如,要创建目录/pyonpyon/fumufumu/yeahyeah
和/pyonpyon/fumufumu/yeahyeahyeah
,您需要使用mkdir
四次:
mkdir /pyonpyon
mkdir /pyonpyon/fumufumu
mkdir /pyonpyon/fumufumu/yeahyeah
mkdir /pyonpyon/fumufumu/yeahyeahyeah
对于每个测试用例,返回您必须调用mkdir
的次数以创建您想要创建的所有目录。
输入
输入包含一个文本文件,其第一行包含整数 T ,即测试用例的数量。该文件的其余部分包含测试用例。
每个测试用例都以包含整数 N 和 M 的行开头,用空格分隔。
下一行 N 行包含计算机上当前存在的每个目录的路径(不包括根目录/
)。这是一个或多个非空小写字母数字字符串的串联,每个字符串前面都有一个/
。
以下 M 行包含您要创建的每个目录的路径。
输出
对于每种情况,请打印包含Case #X: Y
的一行,其中X
是案例编号,Y
是解决方案。
限制
1≤T≤100。
0≤N≤100。
1≤M≤100。
每个路径最多包含100个字符。
每个路径只在计算机上已有的目录列表中或所需目录列表中出现一次。但是,路径可能会出现在两个列表中,如下面的示例#3所示。
如果某个目录位于您计算机上已有的目录列表中,则还会列出其父目录,但根目录/
除外。
输入文件最多为100,000字节。
示例
可以下载更大的样本测试用例here。
输入:
3
0 2
/home/sparkle/pyon
/home/sparkle/cakes
1 3
/z
/z/y
/z/x
/y/y
2 1
/moo
/moo/wheeeee
/moo
输出:
Case #1: 4
Case #2: 4
Case #3: 0
Code Golf
请以解决此问题的任何语言发布您的最短代码。输入和输出可以通过stdin和stdout或您选择的其他文件来处理。 如果您的代码有可能在执行时修改或删除现有文件,请附上免责声明。
Winner将是在2010年第1B轮开始之前存在实施的语言中最短的解决方案(按字节数计算)。因此,您可以自由使用您刚刚编写的语言来提交0字节的解决方案,它不会计算,你可能会得到downvotes。 ^ _ ^
统计
答案 0 :(得分:10)
r=raw_input;c=0;r()
while 1:N,M=map(int,r().split());d={};c+=1;exec"e=r()\nwhile e:d[e]=e,_=e.rsplit('/',1)\n"*(N+M);print'Case #%d:'%c,len(d)-N
此解决方案抛出一个EOFError,但由于这是输出到stderr,因此它在规则范围内。由于我们感兴趣的输出都是stdout,我们可以管道并忽略stderr。
您可能会阅读上述内容(或其他一些帖子)并认为它不起作用。为什么会有一些技巧,我将在这里解释一下。如果你仔细阅读这个问题,它会告诉我们,对于第一个列表中的每个目录,它的所有父目录也都包含在列表中(例如,如果/ home / marcog存在,那么/ home)[1]。这个问题也保证了我们每个列表都没有重复[2]。这使我们可以将第一个列表中的所有目录放入我们从第二个列表中抛出目录的同一个集合中。由于问题保证每个列表没有重复,我们知道第一个列表将导致集合中的N个条目。因此,我们可以通过从最终集合的大小中减去N来得到最终答案。
[1]“接下来的N行分别给出了计算机上已存在的一个目录的路径。此列表将包括计算机上已存在的除根目录之外的每个目录。”
[2]“计算机上已存在的目录列表或您要创建的目录列表中不会出现两次路径。”
答案 1 :(得分:8)
现在单行!
此解决方案为63个字符,但对于具有数千个路径(超过8分钟)的测试用例,将无法在合理的时间内运行,因此我选择不计算它。
n%(~,{'Case #'\)@': '\(~1$+@[]@{\('/':!%@\{[n!++:!]|}/}*,@-n@}/
更快的65个字符的解决方案:
n%(~,{'Case #'\)@': '\(~1$+@[]@{\('/':!%@\{[n!++:!]+}/.&}*,@-n@}/
对marcog的算法感到荣幸!
答案 2 :(得分:4)
perl -naE 'BEGIN{<>}++$i;($n,$m,%d)=@F;for(1..$n+$m){$_=<>;$d{$`}++while/\w\K\b/g}say"Case #$i: ",keys(%d)-$n'
与依赖于解释器命令行选项的高尔夫程序一样,选项'与默认值相关的字节数差异已添加到实际代码长度中。
#! perl -na # activate line mode and autosplit
BEGIN { <> } # skip test case count
# now for each test case:
++$i; # increment test counter
($n,$m,%d) = @F; # read N and M;
# clear out directory hash
for (1..$n+$m) { # for each expected pathname:
$_ = <>; # read it
$d{$`}++ # add to hash...
while /\w\K\b/g # ...all branches from root
}
say "Case #$i: ", keys(%d) - $n
大多数魔法都是从根分支提取。诀窍是使用正则表达式来定位有趣的切割点(即:在每个斜杠之前,以及字符串的结尾),但是使用Perl的$PREMATCH
提取字符串(美元反引号;降价不会让我把它包括在内,而不是通常的模式匹配设施。
\b
找到一个单词边界,它解析为所有单词'(目录')的开头和结尾。我们只希望他们结束,所以我们前置\w
。但是这会从路径中删除最后一个字符,如果我们在同一个数据集中同时获得/foo/bar
和/foo/baz
,则会出现问题。因此,我们会从与\K
的匹配中排除所述字符。如果Perl有一个类似\>
的(Emacs regexes)元字符,那么这一切都不是必需的。
for$i(1..<>){($n,$m,%d)=split$",<>;
for(1..$n+$m){$_=<>;$d{$`}++while/\w\K\b/g}
say"Case #$i: ",keys(%d)-$n}
易读性新线;没有必要或计入。
它使用仅在最新版Perl中找到的功能,因此请使用perl -M5.010或更高版本运行。
答案 3 :(得分:3)
Lua解决方案,172字节:
r,n=io.read,"*n"for i=1,r(n)do a,c,s=r(n),0,{}for i=1,a+r()do k=""for x in r():gmatch"[^/]+"do k=k.."/"..x c=c+(s[k]or 1);s[k]=0 end end print("Case #"..i..": "..(c-a))end
答案 4 :(得分:2)
警告:请确保在空目录中运行,因为这会在每次测试时首先清除其内容。
read t
for((;x++<t;));do
rm -r *
read n m
for((i=n+m;i--;));do
read d
mkdir -p .$d
done
echo Case \#$x: $[`find|wc -l`-n-1]
done
答案 5 :(得分:2)
只是为了好玩,当然没有机会。计数是在删除无关紧要的空白区域之后,我留在这里是为了便于阅读。
namespace System
{
class P
{
static void Main(string[]a)
{
int c = 0, n, m, d, l = 1;
var f = IO.File.ReadAllLines(a[0]);
while (c < int.Parse(f[0]))
{
var o = f[l++].Split(' ');
n = int.Parse(o[0]);
m = int.Parse(o[1]);
var p = new Collections.Generic.HashSet<string>();
while (n-- > 0)
p.Add(f[l++]);
while (m-- > 0)
for (o = f[l++].Split('/'), d = 0; d++ < o.Length; )
if (p.Add(string.Join("/", o, 0, d)))
n++;
Console.Write("Case #{0}: {1}\n", ++c, n);
}
}
}
}
这个最新版本有一个怪癖。它错误地将根目录计为必须创建一次,以补偿变量n在循环开始时为-1而不是所需的0.它起作用,因为根目录保证不在N.
答案 6 :(得分:2)
182 212 247 262 278 字节
1[([){exch}/C{concatstrings}/R{(%lineedit)run}>>begin 1
R{(: )[(Case #)3{=only}repeat<<>>R 1 index add{()<<R]{99 string
cvs C<C>C dup 3 index[<>put}forall pop}repeat[length[sub =}for
用法:$ gs -q -dNODISPLAY -dNOPROMPT thisfile.ps <input >output
答案 7 :(得分:2)
直接翻译为marcog's Python solution的Ruby:
gets.to_i.times{|i|n,m=gets.split.map &:to_i
d={}
(n+m).times{gets.strip.split('/').inject{|a,p|d[a+='/'+p]=a}}
puts"Case ##{i+1}: #{d.size-n}"}
答案 8 :(得分:2)
import Data.List
main=interact$(!1).tail.lines
(l:j)!i=let{[n,m]=map read$words l;(p,k)=splitAt(n+m)j}in"Case #"++show i++": "++show(length(nub$(>>=s)p)-n)++"\n"++k!(i+1)
s(_:p)=map(flip take p)$elemIndices '/'(p++"/")
与其他Haskell解决方案相似(但更短:P)。
错误结束,这是预期的。是否遵循规则比其他解决方案更容易引起争论。输出和错误流确实没有混淆。但是如果stdout被缓冲,则结果永远不会被发送。这对交互式使用是好的(然后只是将控制台输出复制粘贴到文件中),但它主要排除重定向。为了缩短它,./filefixit <input 2>/dev/null
可以解决问题。
通过在第3行插入线路噪声可以避免这个问题:[]!_=""
(8个字节,总共226个)
为清楚起见,与完全缩进和有意义的标识符完全相同的语义:
import Data.List
main = interact $ testsStartingAt 1 . tail . lines
testsStartingAt _ [] = "" -- this line optional
testsStartingAt i (l:ls) = testOutput ++ testsStartingAt (i+1) ls'
where [n,m] = map read $ words l
(paths,ls') = splitAt (n+m) ls
testResult = length (nub $ concatMap splitPath paths) - n
testOutput = "Case #" ++ show i ++ ": " ++ show testResult ++ "\n"
splitPath (_:p) = map (flip take p) $ elemIndices '/' (p ++ "/")
答案 9 :(得分:0)
import java.util.*;enum A{_;{Scanner s=new Scanner(System.in);for(int
t=s.nextInt(),i=0,n,j;i++<t;){Set x=new
HashSet();n=s.nextInt();for(j=s.nextInt();j-->-n;){String b="";for(String
c:s.next().split("/"))x.add(b+=c+'/');}System.out.println("Case #"+i+": "+(x.size()-n-1));}}}
注意:它会输出错误信息但仍能正常工作。
答案 10 :(得分:0)
import Data.List
import Text.Printf
a[]=[]
a(x:z)=(r-n):next where{;[n,m]=map read$words x;t=n+m;r=length$nub$concatMap(f.reverse)$take t z;next=a$drop t z;f""=[];f y=y:f z where(a,b:z)=span(/='/')y}
main=do{z<-getContents;putStr$unlines[printf"Case #%d: %d"x v|(x::Int,v)<-zip[1..]$a$tail$lines z]}
这需要GHC的-XScopedTypeVariables
切换。
可读版本:
import Data.List
import Text.Printf
a [] = []
a (x:xs) = (r-n) : next where
[n,m] = map read $ words x
t = n+m
r = length $ nub $ concatMap (f . reverse) $ take t xs
next = a $ drop t xs
f "" = []
f y = y : f bs where
(a,b:bs) = span (/= '/') y
cleanUp a = unlines [printf "Case #%d: %d" x v | (x::Int,v::Int) <- zip [1..] a]
main = do
z<-getContents
putStr$cleanUp$a$tail$lines z
答案 11 :(得分:0)
158 159 bytes
1({[([){exch}/R{(%lineedit)run}>>begin R{(: )[(Case #)3{=only}repeat<<>>R 1
index +{<><<R]{str cat<C>cat dup 3 index[<>put}forall pop}repeat[length[-
=}for}1)
用法:$ pyon thisfile.pyon <input >output
基于PostScript解决方案。
由于PyonScript开发仍在进行中,因此该代码适用于2010年第1B轮开始时存在的实现:http://github.com/KirarinSnow/PyonScript
答案 12 :(得分:0)
另一个改编的marcog python版本。使用awk -F'[ \]' -f fixit.awk
function p(){if(c++>1)print"Case #"c-2": "length(k)-n}
/\//{for(y=i=1;i<NF;)k[y=y"/"$++i];next}
{p();n=$1;delete k}
END{p()}
要在没有-F
选项的情况下运行它,它会增长15个字符,因为它需要这个部分:
BEGIN{FS=" |/"}
答案 13 :(得分:0)
另一个版本的marcog解决方案,这次是在Scala中。
使用scala
运行,但需要将其放入类中
允许使用scalac
编译。
for(c←1 to readInt){val I=readLine split" "map(_.toInt)
var d=Set("")
for(i←1-I(0)to I(1)){var a="";for(w←readLine split"/"){a+="/"+w;d+=a}}
printf("Case #%d: %d\n",c,d.size-I(0)-2)}
答案 14 :(得分:0)
c=:0
f=:3 :0
c=:>:c
'a b'=:({.,+/)".>{.y
('Case #',(":c),': ',":a-~3 :'#~.,/>\"1<;.1"1 y'>(>:i.b){y)1!:2<2
(>:b)}.y
)
f^:_(}.<;._2 (1!:1)<3)
运行:
script.ijs < gcj-input
仍然,它输出一个额外的行:/