哈希冲突:“否”表示“是”


63

此Code Golf的灵感来自于最近的每日WTF文章《您无法正确处理!,其字符串比较写为:

String yes = "YES";
if ((delay.hashCode()) == yes.hashCode())

想象一下,如果Java的String.hashCode方法恰好以这种方式实现,将会给Steve的团队带来麻烦"YES".hashCode() == "NO".hashCode()。因此,我在这里提出的挑战是:

h用一个字符串参数和一个整数返回值h("YES")(等于)以尽可能少的字符编写一个哈希函数(我称它为 ) h("NO")

当然,使用像这样的函数将是微不足道的def h(s): return 0,它会为每个字符串造成哈希冲突。为了使这一挑战更加有趣,您必须遵守以下附加规则:

在由三个或更少的大写ASCII字母()组成的其他 18 277个可能的字符串中^[A-Z]{0,3}$,必须没有哈希冲突。

澄清(由Heiko Oberdiek指出):输入字符串可能包含以外的字符A-Z,并且您的代码必须能够散列任意字符串。(但是,您可以假定输入字符串,而不是空指针或其他数据类型的对象。)但是,^[A-Z]{0,3}$只要不匹配,则返回值是多少都没有关系。这是一个整数。

此外,混淆此功能的意图:

您的代码不得在字符或字符串文字中包含任何字母“ Y”,“ E”,“ S”,“ N”或“ O”(大写或小写)。

当然,这种限制并不适用于语言的关键字,这样elsereturn等都是精品。


4
这仍然无济于事,我们仍然可以使用ASCII的数字值YESNO来检查此特定异常。
乔Z.

1
阅读这篇文章时,我可能不记得“由于某些
Antonio Ragagnin 2014年

Answers:


7

GolfScript:19个字符(命名函数为24个字符)

26base.2107=59934*+

这是功能的主体。将其分配给命名函数又h需要五个字符:

{26base.2107=59934*+}:h;

(如果您不介意将代码副本放在堆栈上,则可以省略最后的分号。)

散列函数的核心是26base,其计算总和(26 ñ - ķ · 一个ķ ; ķ = 1 .. Ñ),其中Ñ是在输入的字符数和一个ķ表示的ASCII码ķ第输入字符。对于由大写ASCII字母组成的输入,这是一个无冲突的哈希函数。其余代码将结果与2107(的哈希码NO)进行比较,如果相等,则将59934加到得出2701 + 59934 = 62041(哈希码)YES

有关示例输出,请参见此在线演示以及测试案例。


您是如何测试的?我刚发现一堆碰撞。范例:h('DXP') == h('KK') == 65884
nneonneo 2014年

(Python中相当于你写什么,因为我的测试目的:lambda w:sum(ord(c)*26**i for i,c in enumerate(reversed(w*9)))%102983
nneonneo

@nneonneo:显然,还不够好。我以为我生成了一个由三个或三个以下字母组成的完整输入集,对所有输入进行散列并检查散列集是否比输入集少一个元素。显然,我的测试工具在某个地方有一个错误。:-(我会恢复到原来的19字符版本,直到/除非我可以修复较短的。
ILMARI Karonen

54

32位Python 2.x(19)

hash(w*9)%537105043

RSA使用半素数模数,因此使其安全,因此将其与我的哈希算法一起使用肯定会使其变得更好!1个

这是一个纯数学函数,适用于所有字符串(地狱,适用于任何可散列的Python对象),并且不包含任何条件或特殊情况!python-32在大多数都已安装2的系统上,通常可以将32位Python称为。

我已经对此进行了测试,它为18279个3个字母以内的大写字符串返回了18278个不同的值。将此分配给一个函数又需要11个字节:

h=lambda w:hash(w*9)%537105043

h('YES') == h('NO') == 188338253

64位Python 2.x(19)

hash(w*2)%105706823

与上述相同。


为了得出这些数字,使用了一些模块化数学。我一直在寻找的功能f和模量n,使得hash(f('YES')) % n == hash(f('NO')) % n。这等同于测试该n划分d = hash(f('YES')) - hash(f('NO')),即我们只需要检查的因素d为合适的值n

理想情况n是在20000 ** 2附近,以减少发生生日悖论冲突的机会。找到一个合适的n结果是反复试验的,要考虑所有因素d(通常不多)和对该函数的不同选择f。请注意,尽管反复试验只是必要的,因为我想n尽可能地减小(用于打高尔夫球)。如果这不是必需的,我可以选择d通常足够大的模数作为我的模数。

还要注意,您不能仅使用f(s) = s(标识函数)来实现此技巧,因为字符串的最右边的字符XOR与最终的哈希基本上具有线性关系(实际上是一种关系)(其他字符以更加非线性的方式起作用))。因此,字符串的重复确保了字符串之间的差异被放大,从而消除了仅更改最右边字符的影响。


1这是胡说八道。
2 Python字符串哈希取决于主要版本(2 vs 3)和位(32位vs 64位)。它不依赖于平台AFAIK。


你有我的投票权。:D
cjfaure 2014年

不幸的是,由于新的哈希随机化功能,这在最新版本的Python上不起作用。
dan04 '04

@ dan04:奇怪,我想我已经指定这仅适用于Python2.x。我再次编辑了它。
nneonneo 2014年

我可以知道你是怎么找到这些魔术数字的吗?我认为hash('YES'*9)34876679一个因素,而hash('NO'*9)34876679+537105043一个因素。但是您怎么知道那 537105043是一个好的模数呢?即它没有发生其他碰撞?
Antonio Ragagnin 2014年

@AntonioRagagnin:将其添加到答案中。
nneonneo 2014年

38

Perl,53 49 40字节

sub h{hex(unpack H6,pop)-20047||5830404}

测试:

h('YES') = 5830404
h('NO')  = 5830404
Keys:   18279
Values: 18278

对于哈希值YESNO相同,并且有18279串^[A-Z]{0,3}$,这是除了为唯一的无碰撞的碰撞YESNO

取消高尔夫:

sub h {
    hex(unpack("H6", pop())) - 20047 || 5830404;
    # The argument is the first and only element in the argument array @_.
    # "pop" gets the argument from array @_ (from the end).
    # The first three bytes of the argument or less, if the argument
    # is shorter, are converted to a hex string, examples:
    #   "YES" -> "594553"
    #   "NO"  -> "4e4f"
    # Then the hex string is converted to a number by function "hex":
    #   0x594553 = 5850451
    #   0x4e4f   =   20047
    # The value for "NO" is subtracted, examples:
    #   case "YES": 5850451 - 20047 = 5830404
    #   case "NO":    20047 - 20047 =       0
    # If the argument is "NO", the subtraction is zero, therefore
    # 5830404 is returned, the result of "YES".
}

# Test
my %cache;
sub addcache ($) {$cache{$_[0]} = h($_[0])}

# Check entries 'YES' and 'NO'
addcache 'YES';
addcache 'NO';
print "h('YES') = $cache{'YES'}\n";
print "h('NO')  = $cache{'NO'}\n";

# Fill cache with all strings /^[A-Z]{0-3}$/
addcache '';
for my $one (A..Z) {
    addcache $one;
    for (A..Z) {
        my $two = "$one$_";
        addcache $two;
        for (A..Z) {
            my $three = "$two$_";
            addcache $three;
        }
    }
}
# Compare number of keys with number of unique values
my $keys = keys %cache;
my %hash;
@hash{values %cache} = 1 x $keys;
$values = keys %hash;
print "Keys:   $keys\n";
print "Values: $values\n";

旧版本,49字节

由于新算法略有不同,因此我保留了旧版本。

sub h{($_=unpack V,pop."\0"x4)==20302?5457241:$_}

测试:

h('YES') = 5457241
h('NO')  = 5457241
Keys:   18279
Values: 18278

取消高尔夫:

sub h {
    $_ = unpack('V', pop() . ($" x 4);
        # pop():  gets the argument (we have only one).
        # $" x 4: generates the string "    " (four spaces);
        #   adding the four spaces ensures that the string is long
        #   enough for unpack's template "V".
        # unpack('V', ...): takes the first four bytes as
        #   unsigned long 32-bit integer in little-endian ("VAX") order.
    $_ == 20302 ? 5457241 : $_;
        # If the hash code would be "NO", return the value for "YES".
}

编辑:

  • 使用"\0"的填充字节相比,可以节省4个字节$"

在哪里5457241以及20047从何而来?您如何计算这些数字?提前致谢。
AL

@ n.1:YES十六进制为594553。0x594553 =5850451。NO以十六进制表示4e4f。0x4e4f = 20047.
nneonneo

7

的Python:63

一个非常la脚的解决方案:

def h(s):
 try:r=int(s,36)
 except:r=0
 return(r,44596)[r==852]

它通过将字母数字字符串解释为以36为底的数字,然后为其他所有内容返回0来工作。有一个显式的特殊情况,检查返回值852(NO),然后返回44596(YES)。


3
除非我误会:这是代码高尔夫球,否则您可以假定输入是准确的。您可以抛开try:整个第三行。您还可以通过将每条逻辑线放在同一条实际行上,并用分号(def h(s):r=int(s,36);return(r,44596)[r==852])分隔来节省几分费用
Undergroundmonorail

1
@undergroundmonorail:散列函数的字符串参数不是在问题的限制。对于特定类型的字符串(最多三个大写字母),哈希函数的返回值存在限制。但是,不要紧,如果返回值是整数,则为其他字符串返回什么。
Heiko Oberdiek 2014年

3
我喜欢在那里的数组的布尔索引
kratenko 2014年

6

纯Bash,29个字节(功能主体)

h()(echo $[n=36#$1,n-852?n:44596])

这只是将输入字符串当作36的基数,然后转换为十进制,然后处理特殊NO情况。

输出:

$ h A
10
$ h B
11
$ h CAT
15941
$小时
44596
$ h是
44596
$ h ZZZ
46655
$

5

Ruby,51个字节

h=->s{d=s.unpack('C*').join;d=~/896983|^7879$/?0:d}

测试代码:

h=->s{d=s.unpack('C*').join;d=~/896983|^7879$/?0:d}

puts 'YES : '+h.call('YES').to_s # 0
puts 'NO : '+h.call('NO').to_s # 0
puts 'NOX : '+h.call('NOX').to_s # 787988
puts 'FNO : '+h.call('FNO').to_s # 707879
puts ''

values = Hash[]
n = 0
('A'..'Z').each{|c|
    values[c] = h.call(c)
    ('A'..'Z').each{|c2|
        values[c+c2] = h.call(c+c2)
        ('A'..'Z').each{|c3|
            values[c+c2+c3] = h.call(c+c2+c3)
            n += 1
        }
    }
}
puts 'tested '+n.to_s
duplicate = Hash.new()

values.each{|k, e|
    if duplicate.has_key?(e)
        puts 'duplicate : "'+k+'" = "'+duplicate[e].to_s+'" ('+e.to_s+')'
    else
        duplicate[e] = k
    end
}

输出:

YES : 0
NO : 0
NOX : 787988
FNO : 707879

tested 17576
duplicate : "YES" = "NO" (0)

5

Javascript(ES6)54个字节

f=s=>[x.charCodeAt()for(x of s)].join('')^7879||897296
f('YES'); // 897296
f('NO'); // 897296
f('MAYBE'); // -824036582

5

爪哇-94 77

int h=new BigInteger(s.getBytes()).intValue();return Math.abs(h-(h^5835548));

展开:

int hashCode(String s) {
    int h = new BigInteger(s.getBytes()).intValue();
    return Math.abs(h - (h ^ 5835548));
}

叙述-适用于f(s) = BigInteger(s.getBytes())

  • f("YES") xor f("NO") = 5835548
  • 所以 f("YES") xor 5835548 = f("NO")
  • f("YES") - (f("YES") xor 5835548) = f("NO") - (f("NO") xor 5835548)我说的对吗?

您不能内联BigInteger吗?
mafu 2014年

@mafutrct-是的!谢谢。
OldCurmudgeon 2014年

5

CJam,15个字节

q42b_*81991617%

用作下面的GolfScript解决方案。在线尝试。


GolfScript,17个字节

42base.*81991617%

这种方法基于nneonneoIlmari Karonen的答案。

这个怎么运作

42base    # Interpret the input string as a base 42 number.
          # "YES" is [ 89 69 83 ] in ASCII, so it becomes 42 * (42 * 89 + 69) + 83 = 159977.
          # "NO" is [ 78 79 ] in ASCII, so it becomes 42 * 78 + 79 = 3355.
          #
.*        # Square. "YES" becomes 25592640529, "NO" becomes 11256025.
          #
81991617% # "YES" becomes 11256025.

选择算法

我们从开始{b base}:h,即输入字符串被认为是base-b数字。只要b > 25h就是有感染力的。

如果h以以下方式修改,则字符串“ YES”和“ NO”会发生冲突:{x base n}:h,其中n是的除数"YES" h "NO" h -

不幸的是,这意味着我们还可以得到,例如碰撞,YETNP。为避免这种情况,我们必须在获取模数之前以非线性方式修改base-b数。

在GolfScript中完成此操作的最短方法是将base-b数与其自身相乘(即平方)。h现在{base b .* n %}:h

剩下要做的就是为b和找到合适的值n。我们可以通过蛮力实现:

for((b=26;b<100;b++)){
    P=($(golfscript <<< "['YES' 'NO']{$b base.*}/-" | factor | cut -d\  -f 2-))

    for n in $(for((i=0;i<2**${#P[@]};i++)){
        for((n=1,j=0;j<${#P[@]};n*=${P[j]}**((i>>j)&1),j++)){ :;};echo $n;} | sort -nu);{
            [[ $n -ge 18277 && $(echo -n '' {A..Z}{,{A..Z}{,{A..Z}}} |
                golfscript <(echo "' '/[{$b base.*$n%}/].&,")) = 18278 ]] &&
            echo $b $n && break
    }
}

可能的最短值b n是:

37 92176978
42 81991617

测试中

$ echo -n '' {A..Z}{,{A..Z}{,{A..Z}}} |
     golfscript <(echo '{42base.*81991617%}:h;" "/{.`"\t"+\h+puts}/') |
     sort -k 2n |
     uniq -Df 1
"NO"    11256025
"YES"   11256025

3

JavaScript(ES6)-38个字符(33个字符的函数体)

h=s=>(a=btoa(s))=="WUVT"|a=="Tk8="||+s

测试用例:

var l = console.log;
l(  h("YES")  );                // 1
l(  h("NO")  );                 // 1
l(  h("ABC")  );                // NaN     
l(  h("WIN")  );                // NaN
l(  h("YES") === h("NO")  );    // true
l(  h("ABC") === h("WIN")  );   // false
l(  h("WIN") === h("YES")  );   // false

l(  NaN === NaN  );             // false

说明:

首先,让我向您介绍NaNJavaScript中的“不是数字”。它是一个数字:

typeof NaN  // number

就像:

typeof 42   // number

它的特殊属性是它永远不会等于自己1如果字符串是YESNO,则返回NaN其他字符串。

因此,这不会违反规则,因为任何其他字符串;)都不会发生哈希冲突(NaN !== NaN如上面的测试用例所示)。

我的梦想实现了:在代码长度上击败Bash,Perl和Ruby!

非高尔夫代码:

h =  // h is a function 
s => // s = string argument

( ( a = btoa(s) )  ==  "WUVT" | a == "Tk8=" )
        ^-- returns some value stored in `a`

如果该值为"WUVT"or "Tk8=",则返回1。否则,返回

+s // parseInt(s, 10)

会是NaN


2
NaN可能是数字,但从任何意义上说,它都不是“整数”。
圣保罗Ebermann

2
@PaŭloEbermann从维基,“一个整数是一个,其没有小数部分写入”。问题没有明确指出整数必须为^\d+$。JS确实将其NaN视为数字。您可以将其乘以数字,然后与数字一样加,除,减。它是JavaScript的特殊属性。使用它没有害处。这就是我们所说的弯曲规则;)
Gaurang Tandon

1
我可以使用Object.is()并声称它仍然是碰撞…
user2428118 2014年

1
@ user2428118感谢您带来Object.is。我从来不知道 但我想请您注意,OP使用相等运算符(==)进行比较,这将确保除“ YES”或“ NO”以外的任何字符串都不会发生哈希冲突。
Gaurang Tandon

2
忽略声称的事实NaN碰撞好像很便宜,不计,该解决方案具有弦的碰撞NA,通过NPYEQ通过YET
nderscore

2

Python 92

n=int("".join(map(str,map(ord,raw_input()))))    # hashing function
print n if 1+(n**2-904862*n)/7067329057 else-1   # input validation

哈希函数将ASCII字符的序数值连接起来,print语句确保两个所需的输入发生冲突。


2

ECMAScript 6(30字节)

我试图避免使用变量赋值,return和function关键字,这看起来像是一种避免所有废话的好方法(从某种意义上来说,它也像函数编程一样)。与其他解决方案不同,它不依赖btoaatob,它不是ECMAScript 6,而是HTML5。0+是必需的,因此它可以解析任意字符串。

a=>parseInt(0+a,36)-852||43744

1
真好!我不知道他们为parseInt添加了其他基础。但是,您可以削减很多字节。:)a=>parseInt(0+a,36)-852||43744
nderscore 2014年

@nderscore:感谢您的建议。它确实大大改善了我的脚本。
Konrad Borowski14年

2

Java-45(或62?)

考虑到需要使用Java运行程序,我不知道如何公平地评分,是否需要包含函数定义?随时适当地编辑和调整我的分数。目前,我的评分方式与@OldCurmudgeon答案相同。int h(String t){}根据需要添加17 :

int h=t.hashCode();return h*h*3%1607172496;

脱开测试线束:

import static org.junit.Assert.*;

import java.util.*;

import org.junit.Test;

public class YesNo {
  @Test
  public void testHashValue() {
    YesNo yesNo = new YesNo();
    Set<Integer> set = new HashSet<>();

    assertEquals(yesNo.hash("YES"), yesNo.hash("NO"));

    set.add(yesNo.hash(""));
    for(char i = 'A'; i <= 'Z'; i++) {
      set.add(yesNo.hash("" + i));
      for(char j = 'A'; j <= 'Z'; j++) {
        set.add(yesNo.hash("" + i + j));
        for(char k = 'A'; k <= 'Z'; k++) {
          set.add(yesNo.hash("" + i + j + k));
        }
      }
    }
    assertEquals(18278, set.size());
  }

  int hash(String toHash) {
    int hashValue=toHash.hashCode();
    return hashValue*hashValue*3%1607172496;
  }
}

1

更宽松的是...

输送机,145个字符

 I
>#<
 26*)2**\88
 >========*
 ^    \ \+-
 ^=====#==<
5**222P:
5======<
5***26*)*(\P\:@e25*:*)4*,F
>==============#=========
             P,F

基本上,该程序在char上执行某种基于26的操作。之后,它检查哈希值是否等于12999(是的哈希码为YES),如果是,则打印404(是否的哈希码),否则它将只打印哈希码。

传送带是我目前正在测试阶段使用的一种语言,但可以在此处找到解释器以及一些示例和源代码:https : //github.com/loovjo/Conveyor


0

C#4.5(112字节)

int h(string s){int code=s.Select((v,i)=>((int)v)<<(2*(i-1))).Sum();return(code|1073742225)|(code|-2147483569);}

在C#中,undergroundmonorail尝试的工作版本(?)。将字符串中的字节组合为一个32位整数(最多可使用4个字符),然后将结果分别与“ YES”和“ NO”的结果进行或运算,然后将这些运算符或运算在一起。

尽管它可能会在某些时候发生冲突,但对于除“是”和“否”之外的任何^ [AZ] {2,3} $,都不应发生这种情况。


您的哈希函数将有更多的冲突。您的“哈希函数”实际上忽略了串联中的许多位。仅那些位不同的所有字符串对将具有相同的哈希码。
圣保罗Ebermann

0

无可奉告-31(功能内容:26)

'=|*==|,,|+|"#|[|,  |+|-%3|]*|:

非常简单的解决方案。;)适用于所有UTF-8字符串。

说明: '显然是功能。首先,它检查*(输入的)是否等于|,,|+|"#||NO|)。如果是,则返回|, |+|-%3||YES|)-否则,仅返回*


2
我从来没有使用过No Comment,是否可以像通常使用不透明的Golfscript,J或APL答案那样来解释您的解决方案?
卡亚2014年

@Kaya哦,是的,很抱歉,我将编辑帖子。
cjfaure 2014年

1
无需道歉,我只是很好奇它的工作原理。
卡亚2014年

0

C 54

h(char *c){int d=*(int*)c-20302;return d*(d-5436939);}

将字符串转换为整数-“ NO”,然后将其乘以相同的值+“ NO”-“ YES”,则对于“ NO”和“ YES”,将得到0,对于指定范围内的任何其他字符串,则为非零。

Windows 7机器上的所有值(如果有字节顺序问题)。



-1

CoffeeScript-36

应返回1YESNO,以及任何乱码废话atob产生的一切,这不是一个base64字符串。

h=(s)->_=atob s;_ in["`D","4"]&&1||_

等效的JavaScript(不是来自CS编译器的JS代码):

function h( s ) {
    var _ = atob( s );

    if( _ === "`D" || _ === "4" )
        return 1;
    else
        return _;
}

3
“函数应具有整数返回值”-我想当_输入不是“ YES”或“ NO”时,您的函数将返回。
Gaurang Tandon 2014年

-1

这是一个超级la脚的人。如此行不通

Python 2.7-79个字节

def h(s):n=sum(100**i*ord(c)for i,c in enumerate(s));return (n-7978)*(n-836989)

首先,我们得到(每个字符的ascii值)* 100 ^(该字符在字符串中的位置)的总和。然后,我们将(结果-7978)和(结果-836989)相乘,得到最终答案。7978和836989是第一位的“是”和“否”的结果,因此对于“是”和“否”,我们乘以0。

这不应该有任何冲突吗?我不想针对18000个可能的反例进行测试,但是如果发生意外碰撞,我可以在其上100再抛出0,然后真的不应该发生任何碰撞。

失望的是我不能使用 lambda为此,但是我不想重复两次计算,因此不得不将其保存到变量中。

请不要让这场胜利。这太la脚了,我不值得。


不满足“没有其他冲突”的要求:18277字符串集中只有18012个唯一的哈希不应该发生冲突。
dan04 2014年

@丹妈,再给我一次
undergroundmonorail

1
@dan我无法正常工作。也许算法有天生的错误。我不想删除它,因为别人可能知道什么是错的,但我会挂上一张纸条
undergroundmonorail

这对我有用,h = lambda s:(hash(s)+997192582)*(hash(s)-480644903)
Lucas

就像定义一个类似于您的哈希函数一样,但具有99 ** i * int(c,36)的方法一样
Lucas
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.