Bash中的Java String.hashCode()实现

时间:2018-02-19 08:00:39

标签: bash

我试图在Bash中添加String.hashCode()函数。我无法弄清楚这个错误。

这是我的示例实现

function hashCode(){ #similar function to java String.hashCode()
foo=$1
echo $foo
h=0
for (( i=0; i<${#foo}; i++ )); do
    val=$(ord ${foo:$i:1})
    echo $val
    if ((31 * h + val > 2147483647)) 
    then
        h=$((-2147483648 + (31 * h + val) % 2147483648 ))

    elif ((31 * h + val < -2147483648))
    then
        h=$(( 2147483648 - ( 31 * h + val) % 2147483648 )) 
    else
        h=$(( 31 * h + val))
    fi
done
printf %d $h
}

function ord() { #asci to int conversion
    LC_CTYPE=C printf %d "'$1"
}

Java函数看起来像这样

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

字符串“__INDEX_STAGING_DATA__0_1230ee6d-c37a-46cf-821c-55412f543fa6”的预期输出为“1668783629”,但输出为-148458597

注意 - 必须处理java int overflow和underflow。

2 个答案:

答案 0 :(得分:2)

Vinujan,您的代码正在使用您包含的算法散列给定字符串。您不需要ord函数,因为您可以使用printf -v val "%d" "'${foo:$i:1}"将文字转换为ASCII值(除非您需要LC_CTYPE=C来表示字符集差异。)

例如,只需对代码进行微调,就会对字符串进行哈希处理&#34; hello&#34;正确:

#!/bin/bash

function hashCode() {
    local foo="$1"
    local -i h=0
    for ((i = 0; i < ${#foo}; i++)); do

        printf -v val "%d" "'${foo:$i:1}"  # val is ASCII val

        if ((31 * h + val > 2147483647))   # hash scheme
        then
            h=$((-2147483648 + (31 * h + val) % 2147483648 ))
        elif ((31 * h + val < -2147483648))
        then
            h=$(( 2147483648 - ( 31 * h + val) % 2147483648 )) 
        else
            h=$(( 31 * h + val))
        fi
    done
    printf "%d" $h    # final hashCode in decimal
}

hash=$(hashCode "$1")

printf "\nhashCode: 0x%02x (%d decimal)\n" $hash $hash

示例使用/输出

$ bash hashcode.sh hello

hashCode: 0x5e918d2 (99162322 decimal)

你看起来有问题的地方就是哈希本身的算法。例如,像password这样的较长字符串将导致您的方案返回一个看起来可疑的负64位值,例如:

$ bash hashcode.sh password

hashCode: 0xffffffffb776462d (-1216985555 decimal)

这可能是你想要的哈希,我没有什么比较算法。仔细看看,如果仍有问题,请编辑问题并准确描述问题/错误等。当您运行脚本并将该输出添加到您的问题时,您将获得。

编辑哈希函数以获得更好的行为

如果没有算法实现,我唯一能做的就是重新计算你提供的算法,使其在计算超过INT_MAX/INT_MIN时表现得更好。看看你现有的算法,它似乎使问题变得更糟,因为遇到大数而不是平滑值以确保它们保持在界限范围内。

坦率地说,在超过/低于这些限制时,在减少值INT_MIN之前,您似乎忽略了INT_MAX或将h添加到modulo 2147483648。 (例如,你忘记了减法和加法周围的括号)简单地将它添加到哈希算法似乎会产生更好的行为和你想要的输出。

我还将哈希计算的结果保存在hval中,这样就不会在每个循环中多次计算,例如

function hashCode() {
    local foo="$1"
    local -i h=0
    for ((i = 0; i < ${#foo}; i++)); do

        printf -v val "%d" "'${foo:$i:1}"  # val is ASCII val

        hval=$((31 * h + val))

        if ((hval > 2147483647))   # hash scheme
        then
            h=$(( (hval - 2147483648) % 2147483648 ))
        elif ((hval < -2147483648))
        then
            h=$(( (hval + 2147483648) % 2147483648 ))
        else
            h=$(( hval ))
        fi
    done
    printf "%d" $h    # final hashCode in decimal
}

新值

请注意"hello"的哈希值保持不变(正如您所期望的那样),但"password"的值现在表现更好,并返回预期的效果,而不是某些符号扩展64位值。如,

$ bash hashcode2.sh hello

hashCode: 0x5e918d2 (99162322 decimal)

$ bash hashcode2.sh password

hashCode: 0x4889ba9b (1216985755 decimal)

请注意,它确实产生了您的预期输出:

$ bash hashcode2.sh "__INDEX_STAGING_DATA__0_1230ee6d-c37a-46cf-821c-55412f543fa6"

hashCode: 0x63779e0d (1668783629 decimal)

请告诉我这是否是您尝试做的更多。

答案 1 :(得分:1)

我得到了一个精益解决方案:

hashCode() {
    o=$1
    h=0
    for j in $(seq 1 ${#o})
    do
        a=$((j-1))
        c=${o:$a:1}
        v=$(echo -n "$c" | od -d)
        i=${v:10:3}
        h=$((31 * $h + $i ))
        # echo -n a $a c $c i $i h $h
        h=$(( (2**31-1) & $h ))
        # echo -e "\t"$h
    done
    echo $h
}

哪个错了。 :)错误是在我聪明的按位 - (2**31-1) ^ $h的ORing中,按位ANDing似乎更明智一点:(2**31-1) & $h

这可能会缩小为:

hashCode() {
    o=$1
    h=0
    for j in $(seq 1 ${#o})
    do
        v=$(echo -n "${$o:$((j-1)):1}" | od -d)
        h=$(( (31 * $h + ${v:10:3}) & (2**31-1) ))
    done
    echo $h
}