Implement a Variable Length Hash

10

0

My friend and I have this game that we play with words. It is a fun pastime and it involves "canceling out" letters in a word until there is nothing left. I am really tired of him being so much faster than me at it, so it is your job to implement it and let me finally beat him. Obviously, since I have to make the program as easy to hide as possible, it has to be small as possible.

How does this game work?

The game is a pretty simple algorithm. It reduces a alphabetical string until it can't be reduced any further, thus making it a sort of hash. The actual game that we humans do is very hard to implement, but it can be simplified into the following algorithm:

You start by folding the alphabet in half and lining up the two pieces like such:

a b c d e f g h i j k l m
z y x w v u t s r p q o n

Then, starting from the middle, you assign the positive integers to the top half and the negative to the bottom:

a  b  c  d  e f g h i j k l m
13 12 11 10 9 8 7 6 5 4 3 2 1

z   y   x   w   v  u  t  s  r  p  q  o  n
-13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1

Then you take your string (we will be using hello world) and ignoring any non-alphabetical characters, translate it:

h e l l  o w   o  r  l d
6 9 2 2 -2 -10 -2 -5 2 10

Then you sum the letter values. The ones that lined up in the earlier diagram (e.g. d and w, l and o) will cancel out, while the others will add up.

sum(6 9 2 2 -2 -10 -2 -5 2 10 )=12

12 is the number for b, so the hash of hello world is b

For a word that completely cancels out (e.g. love), you output the "0 character": -. Note that in the input, - will still be disregarded. It only matters in the output.

If the number's magnitude is larger than 13, then you start doubling up on the a's and the z's You basically take as many a's or z's fit into the number and take whatever is left into the last letter like so:

code golf: 43.

Fits 3 a's and has 4 left over:

aaa 4: j
result: aaaj

Hint: This part is basically divmod except that it rounds toward zero, not -infinity (e.g. -43 would become 3 z's and and a -4 which is p so zzzp).

Note: the dash does not come if the a's or z's fit in perfectly, only if it is exactly 0.

Clarifications:

  • The hash is case insensitive
  • Standard Loopholes are not allowed
  • I/O can be in any format that isn't too outlandish, stdin, stdout, command-line arg, function, etc.
  • This is so shortest size in bytes wins.

Examples:

hello world  -->  b

love  -->  -

this is an example -->  aak

hello *&*(&(*&%& world  -->  b

good bye --> ae

root users --> zzs

Maltysen

Posted 2015-03-24T22:47:16.623

Reputation: 25 023

3love is empty... – Justin – 2015-03-25T04:45:58.280

Answers

4

Pyth, 79 78 77 65 61 58

J+\-GK+0fTr13_14=ZsX@JzJKMe,pk*G/H13%H13@JxK?g\aZ>Z0_g\z_Z

orlp

Posted 2015-03-24T22:47:16.623

Reputation: 37 067

You can use @Jz instead of f}YJz There's probably more, but I must sleep now. Good luck ;) – FryAmTheEggman – 2015-03-25T01:56:14.223

@FryAmTheEggman Cool, I wasn't aware of the intersection of @! – orlp – 2015-03-25T01:59:55.043

4

CJam, 46 bytes

Try it online, or try the test suite online.

lel{'n-_W>+_zE<*}%:+{_De<C~e>___0>-'n+'-?o-}h;

Explanation

The algorithm works how you might expect: read the input, convert to lowercase, map each character to a value, sum the values, and print characters and adjust the sum accordingly until the sum is zero. Probably the most interesting optimization (although it only saves two bytes) is that negated character mappings are used instead, as this avoids swapping subtraction arguments to correct the sign when computing the mapped value and avoids swapping again when mapping back to a letter due to the subtraction of a negated value being replaceable by addition.

lel             "Read a line of input and convert all letters to lowercase.";
{               "Map each character:";
  'n-_W>+         "Map each character to its negated value by subtracting 'n'
                   and incrementing if the result is nonnegative.";
  _zE<*           "If the value is out of the letter range, use 0 instead.";
}%
:+              "Compute the sum of the mapped character values.";
{               "Do...";
  _De<C~e>        "Compute the sum clamped to the letter range.";
  __              "If the clamped sum is nonzero, ...";
  _0>-'n+         "... then produce the clamped sum mapped back to a letter by
                     decrementing if it is positive and adding 'n', ...";
  '-              "... else produce '-'.";
  ?
  o               "Output the character produced above.";
  -               "Subtract the clamped sum out of the sum.";
}h              "... while the sum is nonzero.";
;               "Clean up.";

Runer112

Posted 2015-03-24T22:47:16.623

Reputation: 3 636

2

Clip 10, 87

Fr+`m[y?cAyg#Ay-v,-RRZ]0]}m.U`[Fx?x?<x0,:-/xR'z*m'm%xR!!%xR],:/xR'a*m'n%xR!!%xR]]'-[R13

Ypnypn

Posted 2015-03-24T22:47:16.623

Reputation: 10 485

1

R, 258 bytes

function(s){a=13;r=data.frame;z=strsplit(gsub("[^a-z]","",tolower(s)),"")[[1]];d=r(l=rev(letters),h=c(-1*a:1,1:a));m=merge(r(l=z),d);n=sum(m$h);q=abs(n);v=rep(ifelse(n>0,"a","z"),q%/%a);paste0(paste(v,collapse=""),d$l[d$h==sign(n)*q%%a],ifelse(n==0,"-",""))}

This has to be the grossest R code ever. I figured R might be a decent choice since it has a vector of all letters "a" through "z" as a built-in global variable. But it turns out the rest is a mess.

Ungolfed + explanation:

function(s) {
    a <- 13              # Store the value associated with a
    r <- data.frame      # Store the `data.frame` function

    # Split the input into a vector, ignoring case and non-letters
    z <- strsplit(gsub("[^a-z]", "", tolower(s)), "")[[1]]

    # Create a data frame where the first column is the letters
    # z through a and the second is the associated scores
    d <- data.frame(l=reverse(letters), h=c(-1*a:1, 1:a))

    # Merge the split vector with the data frame of scores
    m <- merge(data.frame(l=z), d)

    # Get the total score for the input
    n <- sum(m$h)
    q <- abs(n)

    # Pad the output with a or z as necessary
    v <- rep(ifelse(n > 0, "a", "z"), q %/% a)

    # Collapse the vector of a's or z's into a string
    out1 <- paste(v, collapse="")

    # Look up the letter associated with remainder
    out2 <- d$l[d$h == sign(n)*q%%a]

    # If n = 0, we still want a dash
    out3 <- ifelse(n == 0, "-", "")

    # Return the concatenation of all pieces of the output
    paste0(out1, out2, out3)
}

This creates an unnamed function object that accepts a string as input and returns the associated hash value. To call it, give it a name, e.g. f=function(s){...}.

Examples:

> f("this is an example")
[1] "aak"

> f("root users")
[1] "zzs"

> f("love")
[1] "-"

> f("People like grapes.")
[1] "aaag"

Try it online!

Questions? I'll happily provide any further explanation. Suggestions? Suggestions are more than welcome!

Alex A.

Posted 2015-03-24T22:47:16.623

Reputation: 23 761

1

Haskell, 171 bytes

import Data.Char
m=13
l=zip['a'..'z'][-i|i<-[-m..m],i/=0]
p 0="-"
p n|n>m='a':p(n-m)|n<(-m)='z':p(n+m)|1<2=[c|(c,i)<-l,i==n]
f n=p$sum[y|Just y<-[lookup(toLower x)l|x<-n]]

Test run:

> map f ["hello world", "love", "this is an example", "hello *&*(&(*&%& world", "good bye", "root users"]
["b","-","aak","b","ae","zzs"]

How it works: l is a lookup table from letters to the corresponding value. Lookup all characters from the input string and discard those not found. Sum the resulting list. Depending on the sum p prints - or maybe first some as or zs and finally (reverse-)lookups the letter from l.

nimi

Posted 2015-03-24T22:47:16.623

Reputation: 34 639

1

R - 200

function(s){l=letters
N=setNames
x=strsplit(tolower(s),'')[[1]]
n=(13:-13)[-14]
v=sum(N(n,l)[x[x%in%l]])
o=''
while(v){d=min(max(v,-13),13)
o=paste0(o,N(l,n)[as.character(d)])
v=v-d}
if(o=='')o='-'
o}

flodel

Posted 2015-03-24T22:47:16.623

Reputation: 2 345

+1, definitely better than my R answer. Nice work! – Alex A. – 2015-03-26T14:22:12.560