完美的车牌


33

完美的车牌

从几年前开始,我在开车时给自己做了一个小游戏:检查附近的车牌是否“完美”。这是相对罕见的,但是当您找到它时会感到很兴奋。

要检查车牌是否完美:

  1. 总结字符,其中A = 1,B = 2,... Z = 26。
  2. 取每个连续的数字,并求和;将这些总和相乘。

如果第1部分和第2部分中的值相等,那么恭喜!您已经找到了完美的车牌!

例子

License plate: AB3C4F

Digits -> 3 * 4 
        = 12
Chars  -> A + B + C + F 
        = 1 + 2 + 3 + 6 
        = 12
12 == 12 -> perfect!


License plate: G34Z7T

Digits -> (3 + 4) * 7 
        = 49
Chars  -> G + Z + T 
        = 7 + 26 + 20 
        = 53
49 != 53 -> not perfect!


License plate: 10G61

Digits -> (1 + 0) * (6 + 1)
        = 7
Chars  -> G
        = 7
7 == 7 -> perfect!

挑战

我以长度为5和6的车牌为例,但是此过程对于任何长度的车牌都有效。您的挑战是,对于给定的长度N,返回该长度的完美车牌数。出于挑战的目的,有效的车牌是数字0-9和字符AZ的任意组合。标牌必须同时包含一个字符和一个数字,才能被认为可能是完美的。为了进行检查,这里是我得到的值(尽管我不能100%正确无误,哈哈哈)

N < 2: 0
N = 2: 18
N = 3: 355
N = 4: 8012 

笔记

如果以某种方式使您的语言中的问题更简单,则可以输出给定N的完美车牌比例,至少为2个有效数字。

N < 2: 0
N = 2: 0.0138888...
N = 3: 0.0076088...
N = 4: 0.0047701...

或者,您可以输出等效值mod 256

N < 2: 0
N = 2: 18
N = 3: 99
N = 4: 76

最短的胜利!


2
欢迎光临本站!我认为这是一个很好的挑战,但是额外的允许输出使得很难为答案打分。PPCG寻找客观的获胜标准,而当自由如此之多时,很难做到这一点。这不仅会改变输出格式,而且实际上会改变您允许输出的内容。我建议编辑掉其他选项,只需要输出完美车牌的数量即可N
HyperNeutrino

11
如果只是简单地验证给定车牌是否完美,我个人会更喜欢这个挑战(特别是因为您没有测试用例的确切编号。不过,只要可能的计算结果就可以了)减少。虽然不确定其他人的感觉。但好主意!
MildlyMilquetoast 17-4-3

4
我同意Mistah Figgins的观点。我觉得这样更多的是寻找模式,这仍然是一个有趣的挑战,但是我认为,如果仅仅是验证检查,它可能会吸引更多的答案。不错的挑战!
HyperNeutrino

1
我已经发布了一个与之密切相关的挑战,希望它能引起人们对这个奇妙问题的关注,并简化一点,仅检查(几乎)完美的车牌。
Xcoder先生17年

1
@carusocomputing我尽了最大努力,但是空了出来。我将其发送给我的数学老师,他到目前为止还是空着的
Christopher

Answers:


9

Python 3.6,150个字节

f=lambda n,t=-1,p=-1,a=0:sum(f(n-1,*((t,c+p*(p>=0),a),((t<0 or t)*(p<0 or p),-1,a-c))[c<0])for c in range(-26,10))if n else 0<(t<0 or t)*(p<0 or p)==a

结果:

f(2) = 18
f(3) = 355
f(4) = 8012
f(5) = 218153

非高尔夫版本,并附有说明:

digits=[*range(10)]
letters=[*range(1,27)]

def f(n, dt=-1, dp=-1, lt=0):
    if n:
        for d in digits:
            yield from f(n - 1,
                         dt,
                         d if dp < 0 else dp + d,
                         lt
                         )

        for l in letters:
            yield from f(n - 1,
                         dp if dt < 0 else dt if dp < 0 else dt*dp,
                         -1,
                         lt + l
                         )
    else:
        yield 0 < lt == (dt<0 or dt)*(dp<0 or dp)

问题归结为搜索一棵树,其中树的每个级别对应于牌照号码中的位置,并且每个节点有36个子代(10位数字和26个字母)。该函数对树进行递归搜索,并随即累积数字和字母的值。

n is the number of levels to search. 
dp accumulates the sum of a group of digits.
dt accumulates the product of the digit sums.
lt accumulates the sum of the letter values.

For dp and dt, a value < 0 indicates it is not initialized.

包括高尔夫,将for循环转换为生成器之和:

sum(f(n-1, 
      dt,
      d if dp < 0 else dp + d,
      lt) for d in digits)
+
sum(f(n-1,
      dp if dt<0 else dt if dp<0 else dt*dp,
      -1,
      lt+l) for l in letters)

然后组合发电机。将字母A到Z编码为-1到-26,将数字编码为0到9。所以总和为:

sum(f(n-1, *args) for c in range(-26, 10)),

args在哪里:

((dp if dt<0 else dt if dp<0 else dt*dp, -1, lt-l) if c <0 else
 (dt, d if dp<0 else dp+d, lt))

打高尔夫球的其余部分将函数转换为lambda,缩短变量名称并简化表达式。


这是一个雄辩的解决方案,运行时将是什么?n*n*log(n)或类似的东西?
魔术八达通缸

@carusocomputing谢谢。该解决方案仍会生成给定长度的所有可能排列,因此它具有与其他解决方案相同的复杂性。类似于k ** n,其中k是字母中的符号数(例如10位数字+ 26个字母= 36),n是车牌上的符号数。在n = 5上运行它需要检查36 ^ 5 = 60,466,176个排列,并且花费一到两分钟(记忆可能会加快它的速度,但是会花费很多字节;-))。
RootTwo

6

Dyalog APL,57 56字节

+/(+/0⌈a-9)=×/c*⍨-2-/0,⌈\(+\a×b)×c←2>/0,⍨b←9≥a←↑1↓,⍳⎕⍴36

(假设 ⎕io←0

a所有有效车牌的矩阵(除外00...0),其编码为:数字0-9,字母10-35

b 出现数字的位掩码

c 每组连续数字中最后一位的位掩码


在线尝试 1-4需要4的更多内存,但是也有解决方法!
亚当

4

Python 2中,359个 295字节

相当长;这是微不足道的解决方案。我相信这是正确的,尽管它与挑战中的测试用例不匹配。我得到的解决方案与Dada的答案相符。

import itertools as i,re as r,string as s
print len([''.join(x)for x in i.product(s.lowercase+s.digits,repeat=input())if(lambda t:r.search('\\D',t)and r.search('\\d',t)and reduce(int.__mul__,[sum(map(int,k))for k in r.split('\\D+',t)if k])==sum([k-96 for k in map(ord,t) if k>96]))(''.join(x))])

-64字节,感谢@numbermaniac的建议


1
您可以在c(x)和最后一行中保存大约三个字节。删除96和之间的空格for; 在map(ord,x)和之间if; 在最后一行,在.join(x)和之间for。我认为,如果将功能重新定义为lambda,则还可以节省更多。
numbermaniac

@numbermaniac谢谢!(总共64个字节)
HyperNeutrino

4

Python 2中291个 287 276 273字节

lambda n:sum(1for x in s.product(y+z,repeat=n)if(lambda p,s=set:reduce(int.__mul__,[sum(map(int,i))for i in re.findall(r"\d+",p)],1)==sum(ord(i)-64for i in p if ord(i)>64)and s(p)&s(y)and s(p)&s(z))(''.join(x)))
import re,itertools as s,string as t
y=t.uppercase
z=t.digits

在线尝试!


结果:

0 0
1 0
2 18
3 355
4 8012

3

Perl 5,117个字节

116个字节的代码+ -p标志。

$"=",";@F=(A..Z,0..9);map{$r=1;$r*=eval s/./+$&/gr for/\d+/g;$r+=64-ord for/\pl/g;$\+=!$r*/\pl/*/\d/}glob"{@F}"x$_}{

在线尝试!

感觉不是很理想,但是我现在没主意了。
代码本身效率很低,因为它计算a..z,0..9长度的每个排列n(大约需要1秒的时间n=3,大约需要15秒,n=4而大约需要7分钟n=5)。
该算法非常简单:对于每个可能的大小板n(用glob"{@F}"x$_- glob运算符生成,这都是不可思议的),$r*=eval s/./+$&/gr for/\d+/g;计算每个数字块的乘积,并$r+=64-ord for/\pl/g减去字母的权重。然后,$\如果的$r0!$r)并且标牌中包含数字和字母(/\pl/*/\d/),则我们增加计数器。$\由于-pflag 隐式打印在末尾。

请注意,我获得的数字是n=2 -> 18n=3 -> 355n=4 -> 8012n=5 -> 218153。我很确定这些是正确的,但是我可能会误会,在这种情况下请告诉我,我将删除此答案。


3

APL(Dyalog),71字节

完整的程序主体。N的提示。N≥4需要大量的内存和计算量。

+/((+/⊢⍳∩)∘⎕A=(×/'\d+'S{+/⍎¨⍵.Match}))¨l/⍨∧⌿∨/¨c∘.∊l←,(∊c←⎕DA)∘.,⍣⎕⊂⍬

在线尝试!


2

Scala,265个字节

(n:Int)=>{val i=('A'to'Z')++('0'to'9');Seq.fill(n)(i).flatten.combinations(n).flatMap(_.permutations).map(_.mkString).count(l=>"(?=.*[A-Z])(?=.*\\d)".r.findAllIn(l).size>0&&l.map(_-64).filter(_>0).sum==l.split("[A-Z]").filter(""<).map(_.map(_-48).sum).reduce(_*_))}

说明:

(n:Int) => {
    val i = ('A' to 'Z') ++ ('0' to '9');                       // All license plates available characters.
    Seq.fill(n)(i).flatten                                      // Simulate combination with repetition (each character is present n times)
        .combinations(n)                                        // and generate all combinations of size n (all license plates).
        .flatMap(_.permutations)                                // For each combination, generate all permutations (ex. : Seq('A', '1') => Seq('A', '1') and Seq('1', 'A')), and
        .map(_.mkString)                                        // convert each permutation to String (Seq('A', '1') => "A1").
        .count ( l =>                                           // Then count all strings having :
            "(?=.*[A-Z])(?=.*\\d)".r.findAllIn(l).size > 0 &&   // at least 1 character and 1 digit and
            l.map(_ - 64).filter(_ > 0).sum ==                  // a sum of characters (> 'A' or > 64) equals to
            l.split("[A-Z]").filter(""<)
                .map(_.map(_ - 48).sum)
                .reduce(_*_)                                    // the product of sum of digits groups (split String by letters to get digits groups)
        )
}

注意事项:

  • -64并且-48被用于转化Char(分别为字母和数字)到其Int值('A' - 64 = 1'B' - 64 = 2..., '9' - 48 = 9
  • 如果以字母开头(例如:),l.split("[A-Z]").filter(""<)则使用过滤器消除""值。可能会有更好,更短的解决方案l"A1".split("[A-Z]") => Array("", 1)

测试用例 :

val f = (n:Int) => ...  // assign function
(1 to 5).foreach ( i =>
    println(s"N = $i: ${f(i)}")
)

结果:

N = 1: 0
N = 2: 18
N = 3: 355
N = 4: 8012
N = 5: 218153

n > 4由于必须生成所有组合,因此该功能相当慢。


2

Java,382365字节

  • 节省了17个字节,这要归功于Kevin Cruijssen

打高尔夫球

int h(String s){int m=0;for(int c:s.toCharArray())m+=c-48;return m;}
int g(String t){int d=1,c=0;for(String s:t.split("[^0-9]"))d*=h(s);for(String s:t.split("[^A-Z]"))c+=s.charAt(0)-65;return d==c?1:0;}
int f(String t,int n){int m=0;if(t.length()==n)return g(t);for(int d=48;d<58;)m+=f(t+d++,n);for(int c=65;c<91;)m+=f(t+c++,n);return m;}
int s(int n){return f("",n);}

详细

// return sum of adjecent digits
int h(String s)
{
    int sum = 0;
    for(char c : s.toCharArray()) sum += c-'0';
    return sum;
}

// check if perfect
int g(String t)
{
    int d = 1;
    int c = 0;

    for(String s : t.split("[^0-9]")) d *= h(s);
    for(String s : t.split("[^A-Z]")) c += s.charAt(0)-'A';

    return d == c ? 1 : 0;
}

// tree of enumerations
int f(String t, int n)
{
    // base case
    if(t.length() == n)
    {
        return g(t);
    }

    // enumeration
    int sum = 0;
    for(char d='0'; d<='9'; d++) sum += f(t+d, n);
    for(char c='A'; c<='Z'; c++) sum += f(t+c, n);

    return sum;
}

int s(int n){ return f("",n); }

我认为您需要一个仅n作为输入的函数。
Christian Sievers

@ChristianSievers已修复
Khaled.K

1
您当前的代码需要考虑一些事项:int h(String s){int m=0;for(int c:s.toCharArray())m+=c-48;return m;}int g(String t){int d=1,c=0;for(String s:t.split("[^0-9]"))d*=h(s);for(String s:t.split("[^A-Z]"))c+=s.charAt(0)-65;return d==c?1:0;}int f(String t,int n){int m=0;if(t.length()==n)return g(t);for(int d=48;d<58;)m+=f(t+d++,n);for(int c=65;c<91;)m+=f(t+c++,n);return m;}int s(int n){return f("",n);}365字节)您可以将当前版本与此代码进行比较,以查看我所做的更改(此注释的其余部分内容不足够)。:)
Kevin Cruijssen '17

@KevinCruijssen thx,现在
减少了

2

间隙,416字节

不会赢得代码大小,并且距离固定时间还很远,但是使用数学可以大大提高速度!

x:=X(Integers);
z:=CoefficientsOfUnivariatePolynomial;
s:=Size;

f:=function(n)
 local r,c,p,d,l,u,t;
 t:=0;
 for r in [1..Int((n+1)/2)] do
  for c in [r..n-r+1] do
   l:=z(Sum([1..26],i->x^i)^(n-c));
   for p in Partitions(c,r) do
    d:=x;
    for u in List(p,k->z(Sum([0..9],i->x^i)^k)) do
     d:=Sum([2..s(u)],i->u[i]*Value(d,x^(i-1))mod x^s(l));
    od;
    d:=z(d);
    t:=t+Binomial(n-c+1,r)*NrArrangements(p,r)*
         Sum([2..s(d)],i->d[i]*l[i]);
   od;
  od;
 od;
 return t;
end;

要挤出不必要的空格并获得416字节的一行,请通过以下方法进行管道传输:

sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n

我的旧版“专为Windows XP设计”的笔记本电脑可以f(10)在不到一分钟的时间内完成计算,而在一小时之内就可以完成更多工作:

gap> for i in [2..15] do Print(i,": ",f(i),"\n");od;
2: 18
3: 355
4: 8012
5: 218153
6: 6580075
7: 203255386
8: 6264526999
9: 194290723825
10: 6116413503390
11: 194934846864269
12: 6243848646446924
13: 199935073535438637
14: 6388304296115023687
15: 203727592114009839797

怎么运行的

假设我们首先只想知道适合该模式的完美车牌的数量LDDLLDL,其中L表示字母和 D数字。假设我们有一个l数字列表,以 l[i]给出字母可以给出值的方式的数量i,并类似地列出d了从数字中得到的值。那么具有共同价值的完美车牌的数量i为正 l[i]*d[i],通过求和将所有完美车牌的数量与我们的模式相加i。让我们来表示通过求和的操作l@d

现在,即使获取这些列表的最佳方法是尝试所有组合和计数,我们也可以对字母和数字进行独立处理,26^4+10^3而不是26^4*10^3 只浏览适合该模式的所有标牌,而是查看案例。但是我们可以做得更好:l这里只是系数的列表, (x+x^2+...+x^26)^k其中where k是字母数4

类似地,我们得到了一系列的方法来求和 k作为的系数的方式(1+x+...+x^9)^k。如果有多于一个数字,我们需要将相应的列表与一个运算符组合起来,该运算符的d1#d2位置应i为所有d1[i1]*d2[i2]where 的和i1*i2=i。这就是Dirichlet卷积,如果我们将列表解释为Dirchlet级数的系数,则这只是乘积。但是我们已经将它们用作多项式(有限幂级数),并且没有很好的方法来解释它们的运算。我认为这种不匹配是难以找到简单公式的部分原因。让我们无论如何在多项式上使用它,并使用相同的符号#。当一个操作数是单项式时,很容易计算:p(x) # x^k = p(x^k)。再加上它是双线性的,这提供了一种很好的(但不是很有效的)方法来计算它。

请注意,k字母的值最多为26k,而k 单个数字的值最多为9^k。因此,我们经常会在d多项式中得到不必要的高次幂。为了摆脱它们,我们可以计算模x^(maxlettervalue+1)。这样可以大大提高速度,尽管我没有立即注意到,但它甚至可以帮助打高尔夫球,因为我们现在知道的度数不比的度数dl,这简化了决赛的上限Sum。通过在 (请参阅注释)mod的第一个参数中进行计算,我们可以得到更好的加速Value,而#在较低级别上进行整个计算可以实现令人难以置信的加速。但是我们仍在努力成为打高尔夫球的合理答案。

因此,我们有了ld并且可以使用它们来计算具有pattern的完美车牌的数量LDDLLDL。与图案相同的数字LDLLDDL。通常,我们可以根据需要更改不同长度的数字游程的顺序, NrArrangements并提供多种可能性。虽然数字之间必须有一个字母,但其他字母不是固定的。在Binomial计算这些可能性。

现在,仍然需要通过所有可能的方式来获得游程数字的长度。 r遍历所有运行次数,c所有总数的位数以及带有 summands的p所有分区。cr

我们所看到的分区总数比的分区总数少两个n+1,并且分区函数的增长如图所示 exp(sqrt(n))。因此,尽管仍然有简单的方法可以通过重用结果(以不同的顺序遍历分区)来缩短运行时间,但从根本上来说,我们需要避免分别查看每个分区。

快速计算

注意(p+q)@r = p@r + q@r。就其本身而言,这仅有助于避免某些乘法。但是,(p+q)#r = p#r + q#r这意味着我们可以通过对应于不同分区的简单加法多项式进行组合。我们不能仅仅将它们全部添加,因为我们仍然需要知道与哪个对象有关。l进行@组合,必须使用哪些因子以及#仍然可以进行哪些扩展。

让我们用相同的总和和长度组合与分区相对应的所有多项式,并已经考虑了多种分配数字游程长度的方法。与我在评论中推测的不同,如果我确定不会使用该最小值,则无需关心最小的使用值或使用频率。

这是我的C ++代码:

#include<vector>
#include<algorithm>
#include<iostream>
#include<gmpxx.h>

using bignum = mpz_class;
using poly = std::vector<bignum>;

poly mult(const poly &a, const poly &b){
  poly res ( a.size()+b.size()-1 );
  for(int i=0; i<a.size(); ++i)
    for(int j=0; j<b.size(); ++j)
      res[i+j]+=a[i]*b[j];
  return res;
}

poly extend(const poly &d, const poly &e, int ml, poly &a, int l, int m){
  poly res ( 26*ml+1 );
  for(int i=1; i<std::min<int>(1+26*ml,e.size()); ++i)
    for(int j=1; j<std::min<int>(1+26*ml/i,d.size()); ++j)
      res[i*j] += e[i]*d[j];
  for(int i=1; i<res.size(); ++i)
    res[i]=res[i]*l/m;
  if(a.empty())
    a = poly { res };
  else
    for(int i=1; i<a.size(); ++i)
      a[i]+=res[i];
  return res;
}

bignum f(int n){
  std::vector<poly> dp;
  poly digits (10,1);
  poly dd { 1 };
  dp.push_back( dd );
  for(int i=1; i<n; ++i){
    dd=mult(dd,digits);
    int l=1+26*(n-i);
    if(dd.size()>l)
      dd.resize(l);
    dp.push_back(dd);
  }

  std::vector<std::vector<poly>> a;
  a.reserve(n);

  a.push_back( std::vector<poly> { poly { 0, 1 } } );
  for(int i=1; i<n; ++i)
    a.push_back( std::vector<poly> (1+std::min(i,n+i-i)));
  for(int m=n-1; m>0; --m){
    //    std::cout << "m=" << m << "\n";
    for(int sum=n-m; sum>=0; --sum)
      for(int len=0; len<=std::min(sum,n+1-sum); ++len){
        poly d {a[sum][len]} ;
        if(!d.empty())
          for(int sumn=sum+m, lenn=len+1, e=1;
              sumn+lenn-1<=n;
              sumn+=m, ++lenn, ++e)
            d=extend(d,dp[m],n-sumn,a[sumn][lenn],lenn,e);
      }
  }
  poly let (27,1);
  let[0]=0;
  poly lp { 1 };
  bignum t { 0 };
  for(int sum=n-1; sum>0; --sum){
    lp=mult(lp,let);
    for(int len=1; len<=std::min(sum,n+1-sum); ++len){
      poly &a0 = a[sum][len];
      bignum s {0};
      for(int i=1; i<std::min(a0.size(),lp.size()); ++i)
        s+=a0[i]*lp[i];
      bignum bin;
      mpz_bin_uiui( bin.get_mpz_t(), n-sum+1, len );
      t+=bin*s;
    }
  }
  return t;
}

int main(){
  int n;
  std::cin >> n;
  std::cout << f(n) << "\n" ;
}

这使用了GNU MP库。在debian上,安装libgmp-dev。用编译g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx。该程序从stdin获取其参数。对于计时,请使用echo 100 | time ./pl

最后a[sum][length][i]给出了sum 数字length可以给出数字的方式i。在计算过程中,在m循环的开始,它给出了大于的数字可以实现的方法数量m。一切始于 a[0][0][1]=1。请注意,这是我们为较小的值计算函数所需的数字的超集。因此,几乎可以同时计算出所有值n

没有递归,因此我们有固定数量的嵌套循环。(最深的嵌套级别为6。)n在最坏的情况下,每个循环都会经过许多线性的值。因此,我们只需要多项式时间。如果我们仔细看一下嵌套ij循环extend,就会发现j该形式的上限N/i。那应该只给出j循环的对数因子。在最里面的循环f (与sumn etc)中是相似的。另外请记住,我们使用快速增长的数字进行计算。

另请注意,我们存储 O(n^3)这些数字。

通过实验,我在合理的硬件(i5-4590S)上获得了以下结果: f(50)需要1秒和23 MB,f(100)需要21秒和166 MB,f(200)需要10分钟和1.5 GB,f(300)需要1小时和5.6 GB。这表明时间复杂度要好于O(n^5)


由于这是一场标准高尔夫挑战赛,因此需要对这一答案进行打高尔夫。抱歉。
Rɪᴋᴇʀ

1
@Riker虽然我不认为我的代码开头太冗长,但是我打了一些高尔夫球,并承担了压缩空白时确定其大小的负担。
Christian Sievers

1
@carusocomputing恐怕情况会更糟。我分别处理在数位行之间分配数位的每种情况,例如有一组三位数字,或者有一组二位数字和一位数字,或者有三位数字,但是对于n=5,没有用两位数和两位数组成的情况,因为那样的话,我们没有足够的字母来分隔数字。这是三个外部for循环的作用:遍历所有有用的数字分区<n。(我刚刚意识到我也允许n数字。幸运的是,另一个优化将其计为0)。
Christian Sievers'Apr

1
@carusocomputing注意,对于数字<n/2所有分区都是有用的。并且其余的计算仍然需要花费非恒定时间。要查看发生了什么,您可以Print(p,"\n");for p...循环主体的开头添加。-我有一个减少使用一个循环的想法,但这只会帮助代码大小。
Christian Sievers

2
通过将mod(已经很有用)移入Value并将其更改为,我获得了惊人的速度Value(d mod x^(1+QuoInt(s(l)-1,i-1)),x^(i-1))。仅此一项,就可以f(15)在80秒内完成计算。
Christian Sievers

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.