Determine the “Luck” of a string

Given a string, return that string's "luck".

A string's luck, as I completely just made up for the purpose of this challenge, is an integer, determined as so:

• The base luck for a string is 1.
• For each consecutive letter it shares with the word "lucky" (case insensitive), multiply the luck by 2. For instance, if your string was "lumberjack" or "smack" you'd multiply by 4. (More specifically, 2^number of consecutive characters shared.)
• The shared letters have to be in the same consecutive order it appears in "lucky" but can start anywhere in the word for the same value ("luc" has the same 8* multiplier as "cky").
• If the word has multiple occurrences where it shares consecutive characters with lucky, use the longest consecutive string of the characters.
• For ANY letter it shares with the word "omen" subtract 2 from the luck.
• It can match a character any amount of times, in any order. For instance the string "nnnnnomemenn" loses 24 luck (12 matching letters)

Example:

luck("lucky")
>>32

2^5 (5 consecutive letters) = 32

luck("firetruck")
>>6

2^3 - 2 (3 consecutive letters from uck, e shared with omen)

luck("memes")
>>-7

1 - 8 (base amount, 4 shared with "omen")

This is code golf, so the answer with the fewest bytes wins.

You can input and output any way you'd like - write a function, use standard input, etc.

For functions, assume whatever data type would make sense for that language. (For example, in JavaScript, you'd be passed a String and return a Number)

Edit: You can assume any input is lowercase.

• Nice first challenge! – Alex A. Jul 8 '16 at 2:44
• Should the program accept uppercase input? – busukxuan Jul 8 '16 at 2:51
• @busukxuan Good question - no, it doesn't need to accept uppercase input. – charredgrass Jul 8 '16 at 3:00
• @cat Not sure I quite understand what you're asking. But you can just assume all input will be lowercase and you don't need to catch any uppercase input. – charredgrass Jul 8 '16 at 4:15
• Can we assume an upper or lower bound on the luck of a given input? i.e. what's the smallest number of bits / data type I can get away with, or is it as big as my language can handle? that is, should it be int8_t str_luck(const char* str); or should it be uint64_t str_luck(const char* str);? – cat Jul 8 '16 at 4:18

05AB1E, 363228 26 bytes

Œv'¸éyåiyˆ}}¯é¤go¹'ƒÖ¦Ãg·-

Explanation

Œv         }                  # for each substring of input
'¸éyåi  }                   # if substring is part of "lucky"
yˆ                    # add it to global array
¯é¤               # get the longest such substring
go             # raise 2 to its length
¹'ƒÖ¦Ã       # remove all chars from input that isn't in "omen"
g·     # get length and multiply by 2
-    # subtract
# implicitly display

Try it online

Saved 2 bytes thanks to Adnan

• Compression for 1 word can also be done with ', so for 26: Œv'¸éyåiyˆ}}¯é¤go¹'ƒÖ¦Ãg·- :). – Adnan Jul 8 '16 at 14:03
• @Adnan: Weird. I was certain I had tried that. Apparently not. Thanks! – Emigna Jul 8 '16 at 14:14
• why isn't this the top answer? – noɥʇʎԀʎzɐɹƆ Jul 9 '16 at 13:35

JavaScript (ES7), 123112 107 bytes

s=>2**[5,4,3,2,1,0].find((i,_,a)=>a.some(j=>s.includes("luckyL".substr(j,i))))-2*~-s.split(/[omen]/).length

Edit: Saved 11 bytes thanks to @Titus by assuming that the letter L does not appear in the input. Saved 5 bytes thanks to @Oriol. ES6 version for 125 114 109 bytes:

f=
s=>(1<<[5,4,3,2,1,0].find((i,_,a)=>a.some(j=>s.includes("luckyL".substr(j,i)))))-2*~-s.split(/[omen]/).length
;
<input oninput=o.textContent=f(this.value)><pre id=o></pre>

• Why do you use replace([^]) instead of match([])? Do you waste 3 bytes or is there a reason? – Titus Jul 8 '16 at 13:02
• @Titus How many bytes does it cost to deal with a null match result? – Neil Jul 8 '16 at 14:24
• Four for a string, and a pair of () in this case; eating up all six that you would save with match(/[omen]/). Pity. – Titus Jul 8 '16 at 15:14
• @Titus Not sure if that's what you meant but by adding an L to the end of the substr (which will never appear in the original string) I don't have to worry about extraneous matches and I can actually use the same array [5,4,3,2,1,0] both times, saving a whopping 13 bytes! – Neil Jul 8 '16 at 18:33
• -2*s.split(/[omen]/).length+2 is shorter. – Oriol Jul 9 '16 at 2:44

Pyth, 2726 28 bytes

-^2le+k}#"lucky".:Q)yl@"omen

1 byte saved thanks to OP :-)

Explanation:

Implicit Q as input
.:Q              Find all substrings of input
+k}#"lucky"                 Filter for substring of "lucky", prepend "" in case of []
e                            Take last element, which is longest
l                             Get its length
^2                              Raise two to that
@"omen"Q   Filter Q for characters in "omen"
l           Get length; counts how many characters in "omen" there are
y            Double that
-                                Find the difference
• I'm not an expert in Pyth but I believe you can change "omen" to just "omen and Pyth will understand – charredgrass Jul 8 '16 at 3:52
• @charredgrass Oops, my mistake :-) – busukxuan Jul 8 '16 at 4:34
• Doesn't seem to work for string without chars form "lucky" in it. "memes" for example. – Emigna Jul 8 '16 at 12:30
• @Emigna Ah. the zero case again.... Thanks, fixed it! – busukxuan Jul 8 '16 at 12:56

Ruby, 91 87 bytes

String#count's finnicky usage strikes again! (When passed a String, it counts all occurrences of each letter in the function argument instead of all occurrences of the entire string.)

Try it online

->s{2**(z=0..5).max_by{|j|z.map{|i|s[b="lucky"[i,j]]?b.size: 0}.max}-2*s.count("omen")}

A version that takes in lines from STDIN and prints them: 89 bytes (86 +3 from the -n flag)

p 2**(z=0..5).max_by{|j|z.map{|i|$_[b="lucky"[i,j]]?b.size: 0}.max}-2*$_.count("omen")
• ._. that String#count is weird. +1 for (ab)using it. Also is it shorter to use gets rather than a function? – Downgoat Jul 8 '16 at 4:40
• @Downgoat if I gets I also has to puts for output, so not in this case. – Value Ink Jul 8 '16 at 6:21

Ruby: 100 bytes

->s{2**(m=0;4.times{|j|1.upto(5){|i|m=[i,m].max if s.match"lucky"[j,i]}};m)-s.scan(/[omen]/).size*2}
• Try /[omen]/ as the regex to golf it down - it will match any character and is better in practical use than chaining |s for single characters. – charredgrass Jul 8 '16 at 3:21

Javascript - 206 Bytes

r=>{var a="lucky";r:for(var e=5;e>0;e--)for(var n=0;6>n+e;n++){var o=a.substring(n,e+n);if(r.includes(o))break r}for(var t=0,e=0;e<r.length;e++)('omen'.indexOf(r[e])+1)&&t++;return Math.pow(2,o.length)-2*t}
• You could change this condition: s[k]=='o'||s[k]=='m'||s[k]=='e'||s[k]=='n' to look like this: "oman".split("").includes(s[k]) – addison Jul 8 '16 at 3:38
• Welcome to PPCG! You can golf this down by removing whitespace to conserve bytes. Also, instead of (s[k]=='o'||s[k]=='m'||s[k]=='e'||s[k]=='n') you could use ('omen'.indexOf(s[k])+1) (assuming this is JavaScript) – charredgrass Jul 8 '16 at 3:38
• Thanks for the tips! Got it down to 237, although it looks like the Ruby crowd's got me beat. – Christopher Burgdorff Jul 8 '16 at 3:45
• Another small thing: you can shorthand function luck(r) down to r=> to just make it an anonymous function, that's all that is necessary for this challenge. Also I made an edit to the challenge so you don't have to worry about case so you can remove the r=r.toLowerCase(); – charredgrass Jul 8 '16 at 3:49
• Instead of substring you can use slice i believe (test this though, as I'm not sure) – Downgoat Jul 8 '16 at 4:39

Ruby, 57 bytes

b=gets.count'omen'
$.+=1while/[lucky]{#$.}/
p 2**$./2-2*b gets sets$. to 1 as a side effect, then we increment it until the regular expression matching $. consecutive lucky characters no longer matches. Haskell, 99 Another approach... I just learned about function aliasing import Data.List s=subsequences i=intersect l=length f n=2^(l$last$i(s"lucky")$s n)-2*l(i n$"omen") Usage f"lucky" 32 f"firetruck" 6 f"memes" -7 Mathematica, 86 bytes Code: 2^StringLength@LongestCommonSubsequence[#,"lucky"]-2StringCount[#,{"o","m","e","n"}]& Explanation: LongestCommonSubsequence returns the longest contiguous substring common to the input and "lucky". StringLength gives its length. StringCount counts the number of occurrences of the characters of "omen" in the input. Python (139 Bytes) import itertools as t s=input() print 2**max([j-i for i,j in t.combinations(range(6),2)if'lucky'[i:j]in s]+)-2*sum(_ in'omen'for _ in s) • You can save one byte by using from intertools import* – wnnmaw Jul 8 '16 at 19:44 TSQL, 233 bytes Golfed: DECLARE @t varchar(99)='oluck' ,@z INT=0,@a INT=0,@ INT=1,@c INT=0WHILE @a<LEN(@t)SELECT @a+=IIF(@=1,1,0),@z=IIF('LUCKY'LIKE'%'+x+'%'and @>@z,@,@z),@c+=IIF(x IN('O','M','E','N'),2,0),@=IIF(@+@a-1=LEN(@t),1,@+1)FROM(SELECT SUBSTRING(@t,@a,@)x)x PRINT POWER(2,@z)-@c Ungolfed: DECLARE @t varchar(99)='oluck' ,@z INT=0 ,@a INT=0 ,@ INT=1 ,@c INT=0 WHILE @a<LEN(@t) SELECT @a+=IIF(@=1,1,0), @z=IIF('LUCKY'LIKE'%'+x+'%'and @>@z,@,@z), @c+=IIF(x IN('O','M','E','N'),2,0), @=IIF(@+@a-1=LEN(@t),1,@+1) FROM(SELECT SUBSTRING(@t,@a,@)x)x PRINT POWER(2,@z)-@c Try it online Haskell (134 132 Bytes) import Data.List c[]=[] c s@(_:x)=inits s++c x l=length g q=2^(maximum$map(\z->l q-l(q\\z))$c"lucky")-2*(l$intersect q"omen")

Not a code golfer nor a Haskell programmer, so would love some tips on this one.

(Example: g "firetruck")

• I am no Haskell Expert either, but managed to carve off a few byes by changing the algorithm slightly and using function aliases on reused functions. – Zylviij Jul 9 '16 at 1:23

Python 3, 168157152139144 136 bytes

EDIT: Really obvious things I should have seen easier have been changed, and some slightly less obvious.

Edit 2: stoopid (˚n˚). The program threw errors. I fixed it up. not actually 153 :(

Thanks to Leaky Nun for saving 5 bytes, and to jmilloy for saving 13 8 bytes.

s=input()
p=q=k=len(s)
m=0
while-~p:
while-~q:m=(m,q-p)[(s[p:q]in"lucky")*q-p>m];q-=1
p-=1;q=k
print(2**m-2*sum(i in"omen"for i in s))

The program runs through all possibly possible substrings in input (possibly possible, because it computes impossible substrings as well, 8 to 7, for example), checks if the substring is in "lucky", then sets the exponent of 2 to the length of the substring should it be greater than the current value. Possibly could be improved by using only one while loop. Could possibly use some improvement; I'm still getting the hang of this.

• while p+1 becomes while-~p – Leaky Nun Jul 8 '16 at 11:06
• since b=s[p:q], len(b) must be q-p right? – Leaky Nun Jul 8 '16 at 11:11
• I stole you input and print method but did the rest very different, thanks! I think if you just do print(2**m-2*sum(i in"omen" for i in s)) for your last three lines you'll do better, like 148? – jmilloy Jul 8 '16 at 16:45
• Oh, and you can just move s[p:q] into the if clause while-~q:n=q-p;m=n if(s[p:q]in"lucky")*n>m else m;q-=1 for 143? – jmilloy Jul 8 '16 at 16:51
• sum(map(s.count,"omen")) saves one byte, making it 135 – Black Owl Kai Feb 2 at 14:50

PHP program, 139 135 108 bytes

quantum leap fails for multiple substrings where the first occurence is shorter. :(

actually I could save another 7 bytes in PHP<5.4 with register_globals on

<?for($s=$argv;$i<5;$i++)for($j=6;--$j;)$r=max($r,strstr($s,substr('lucky*',$i,$j))?2**$j:1);echo$r-2*preg_match_all('/[omen]/',$s);

usage: php -d error_reporting=0 <filename> <string>

+5 for a function:

function f($s){for(;$i<5;$i++)for($j=6;--$j;)$r=max($r,strstr($s,substr('lucky*',$i,$j))?2**$j:1);return$r-2*preg_match_all('/[omen]/',$s);} tests (on the function) echo '<table border=1><tr><th>input</th><th>output</th><th>expected</th><th>ok?</th></tr>'; foreach([ 'lumberjack'=>0, 'smack'=>2, 'nnnnnomemenn'=>-23, 'lucky'=>32, 'omen'=>-7, 'firetruck'=>6, 'memes'=>-7, 'determine the “luck” of a string'=>0, 'amazing'=>-3, 'wicked'=>2, 'chucky'=>16, 'uckyuke'=>14, 'ugly'=>2, 'lucy'=>8, 'lukelucky'=>30 ] as$x=>$e){$y=f($x); echo"$h<tr><td>",$x,'</td><td>',$y,'</td><td>',$e,'</td><td>',$e==\$y?'Y':'N',"</td></tr>";
}echo '</table>';

C# (Visual C# Interactive Compiler) with flag /u:System.Text.RegularExpressions.Regex, 99 bytes

n=>(1<<Matches(n,"l?u?c?k?y?").OrderBy(x=>x.Value).Last().Length)-n.Count(x=>"omen".Any(c=>c==x))*2

Try it online!

Scala, 155 bytes

def f(w:String)=(1::List.fill((for(a<-1 to 5;s<-"lucky".sliding(a))yield if(w.contains(s)) a else 0).max){2}).product-2*w.filter("omen".contains(_)).length