37
\$\begingroup\$

Guidelines

Scenario

John has an important number, and he doesn't want others to see it.

He decided to encrypt the number, using the following steps:

His number is always a non-decreasing sequence (ie. "1123")

He converted each digit into English words. (ie. "123" -> "ONETWOTHREE")

And then, rearrange the letters randomly. (ie. "ONETWOTHREE" -> "ENOWTOHEETR")

John felt that his number were safe in doing so. In fact, such encryption can be easily decrypted :(


Task

Given the encrypted string s, your task is to decrypt it and return the original number.


Rules

  • This is code golf, so the shortest answer in bytes wins
  • You can assume that the input string is always valid
  • The input string only contains uppercase letters
  • The original numbers are always arranged in ascending order
  • You may return the number in string or integer format
  • The letters will only be shuffled between one word, not between the whole string.
  • The numbers will only be from 1 to 9 inclusive (ONE to NINE)

Possible Unscrambled String

Here is a list of the strings just after they have been converted to strings from the numbers:

 1 -> ONE 
 2 -> TWO
 3 -> THREE
 4 -> FOUR
 5 -> FIVE
 6 -> SIX
 7 -> SEVEN
 8 -> EIGHT
 9 -> NINE

Examples

"NEO" -> 1

"ENOWOT" -> 12

"EONOTWHTERE" -> 123

"SNVEEGHEITNEIN" -> 789

"ENOOWTEERHTRUOFEVIFXISNEVESTHGIEENIN" -> 123456789

"NOEWOTTOWHEERT" -> 1223

\$\endgroup\$
8
  • \$\begingroup\$ What does "non strict" mean in "his number is always a non strict increasing sequence"? \$\endgroup\$ Jul 11, 2017 at 8:26
  • \$\begingroup\$ Can you add even more complicated test cases? \$\endgroup\$
    – ZaMoC
    Jul 11, 2017 at 8:29
  • 2
    \$\begingroup\$ @ TessellatingHeckler : A non-strictly increasing sequence is when the next number can be the same as the previous ex. 1-1-1-2-2-3 (non-strictly increasing) as opposed to 1-2-3-4-5 (strictly increasing) \$\endgroup\$ Jul 11, 2017 at 8:37
  • 1
    \$\begingroup\$ Technically speaking, this is an encoding, not encryption, since there is no key. \$\endgroup\$ Jul 11, 2017 at 9:10
  • 3
    \$\begingroup\$ It would be interesting to have a version of this challenge where it is allowed to shuffle letters throughout the entire string. \$\endgroup\$ Aug 18, 2017 at 6:20

21 Answers 21

10
\$\begingroup\$

Python 2, 121 117 115 bytes

def g(s,a=0,f=''):
 for c in s:
    a+=34**ord(c)%43;r='P!\x83u\x8eI\x92|Z'.find(chr(a))+1
    if r:f,a=f+`r`,0
 return f

-4 bytes: After all that golfing I forgot to inline a single-use variable. Brain fart.
-2 bytes: Double-spaced indent → single tab indent (thanks to Coty Johnathan Saxman); note that this does not display correctly in the answer.

Ungolfed (compatible with python 3):

nums = [80, 33, 131, 117, 142, 73, 146, 124, 90]

def decode(str):
    acc = 0
    final = ''
    for c in str:
        acc += (34**ord(c))%43
        if acc in nums:
            final += str(1+nums.index(acc))
            acc=0
    return final

Magic number finder:

#!/usr/bin/env python3
from itertools import count, permutations

def cumul(x):
    s = 0
    for v in x:
        s += v
        yield s

all_words = 'ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE'.split()

for modulo in range(1, 1000):
    for power in range(1, 300):
        combinations = []
        for word in all_words:
            my_combination = []
            for perm in permutations(word):
                my_combination += cumul(power**(ord(x)) % modulo for x in perm)
            combinations.append(my_combination)

        past_combinations = set(())
        past_intermediates = set(())
        collision = False
        for combination in combinations:
            final = combination[-1]
            if final in past_intermediates or any(intermediate in past_combinations for intermediate in combination):
                collision = True
                break
            past_combinations.add(final)
            past_intermediates.update(combination)

        if not collision:
            print("Good params:", power, modulo)
            print("Results:", ", ".join(str(x[-1]) for x in combinations))

Explanation:

I had a feeling that I could smash the ASCII bits together and sum them up somehow to determine when I had a full word. Originally I tried messing with 3**ord(letter) and comparing to expected results, but it resulted in some very large numbers. I though it would be appropriate to brute-force some parameters a little, namely modulus (to ensure the numbers are small) and a multiplier to disperse the numbers differently around the range of the modulus.

I ended up changing the multiplier variable into a variable affecting the power itself because (from trial and error) that somehow managed to give me a slightly shorter golfed answer.

And above you see the results of that brute-forcing and a little manual golfing.

The reason for choosing 3**x originally is because I knew you could represent every number there. The most repeated digits any number had is two (thrEE, sEvEn, NiNe, etc), so I decided to think of every input as a base-3 number. That way I could (mentally) represent them as something like 10100000000010020000 (three; a 1 in the t slot, a 1 in the r slot, a 1 in the h slot, and a 2 in the e slot). Each number this way gets a unique representation which can be easily pieced together by iterating the string and summing some numbers, and it ends up independent of the actual order of the letters. Of course, this didn't turn out to be the ideal solution, but the current solution is still written with this idea in mind.

\$\endgroup\$
6
  • \$\begingroup\$ What's Py3K?... \$\endgroup\$ Jul 11, 2017 at 16:18
  • \$\begingroup\$ Apologies, edited (it's the former name of python 3) \$\endgroup\$ Jul 11, 2017 at 16:19
  • 1
    \$\begingroup\$ It's cheap, but you can save 2 bytes (since this is python 2) by subbing your second indentation level (two spaces) for a single tab. [tio.run/##NU7NCoJAGDy7T/… Try it online!] \$\endgroup\$ Jul 12, 2017 at 7:15
  • \$\begingroup\$ Also, you might be able to save 6 bytes using literal \x83, \x8e, and \x92 in the string. \$\endgroup\$ Jul 12, 2017 at 15:36
  • \$\begingroup\$ @CalculatorFeline Unfortunately my interpreter doesn't like that: SyntaxError: Non-ASCII character '\xc2' in file <stdin> on line 3, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details. It works if I put the coding comment up there, but that gains 15 extra bytes. \$\endgroup\$ Jul 12, 2017 at 21:54
7
+200
\$\begingroup\$

Jelly,  38  37 bytes

ḟ“RGS”O“OX‘,“¢©“¢¢¤‘yF×4/%74ị⁽Gל?9¤Ḍ

A monadic link taking a list of characters (the string) and returning an integer.

Try it online!

Uses a very different method to Pietu1998's Jelly answer, yet has the same byte count (I really thought it might it did end up as less)!

Does not rely on the monotonicity of the original number (so an input of HTREEWTONOE would work for example).

How?

First note that the words themselves (and therefore any anagrams thereof) can all be changed to ones of length 4 by removing any Rs, Gs and Ss and replacing any Os with two characters (say "12") and any Xs with three characters (say "345").

letters  -> -RGS  -> O:12, X:345
ONE         ONE      12NE
TWO         TWO      TW12
THREE       THEE     THEE
FOUR        FOU      F12U
FIVE        FIVE     FIVE
SIX         IX       I345
SEVEN       EVEN     EVEN
EIGHT       EIHT     EIHT
NINE        NINE     NINE

We may then map the product of the ordinals of those characters to the numbers 1 to 9 using modulo arithmetic, depending upon our choice (the "12345"), then look these up in a reordered list of the digits. The code actually casts to characters first and then replaces the ordinals, but it is also possible in 37 bytes with characters, e.g. "DIAAE" (try it).

ḟ“RGS”O“OX‘,“¢©“¢¢¤‘yF×4/%74ị⁽Gל?9¤Ḍ - link: list of characters
 “RGS”                                - literal ['R','G','S']
ḟ                                     - filter discard
      O                               - convert to ordinals
       “OX‘                           - code-page indices list = [79,88]
            “¢©“¢¢¤‘                  - code-page indices lists = [[1,6],[1,1,3]]
           ,                          - pair -> [[79,88],[[1,6],[1,1,3]]]
                    y                 - translate (replace 79s (Os) with [1,6]
                                                       and 88s (Xs) with [1,1,3])
                     F                - flatten into a single list
                       4/             - 4-wise reduce by:
                      ×               -   multiplication (product of each window of four)
                         %74          - modulo 74
                                   ¤  - nilad followed by link(s) as a nilad:
                             ⁽G×      -   base 250 literal = 18768
                                œ?9   -   permutation of [1,2,3,4,5,6,7,8,9] at that
                                      -   index in a lexicographically sorted list of
                                      -   all such permutations -> [1,5,8,2,4,9,7,6,3]
                            ị         - index into
                                    Ḍ - convert from decimal digits to an integer

Another 37

42BĖZ“Øȧɲ“Pt’żḥ€⁸*/)⁸xO×⁵/%⁽R1%⁽©v%⁵Ḍ

Try it online!

This one repeats characters such that any* shuffled number name becomes length ten:

characters repeat
G R S T U V 1
H N 2
E F I 3
W 4
O 5
X 6

e.g:

ONE    => OOOOONNEEE
TWO    => TWWWWOOOOO
THREE  => THHREEEEEE
OURF   => OOOOOURFFF
EVIF   => EEEVIIIFFF
IXS    => IIIXXXXXXS
VENES  => VEEENNEEES
GHIET  => GHHIIIEEET
NINE   => NNIIINNEEE
WOTISX => WWWWOOOOOTIIISXXXXXX

Then takes the products of the ordinals of length ten slices and performs a modulo chain to map these to \$[1,9]\$ appropriately.

* The repetition part works for "ZERO" too -> "ZEEEROOOOO" as 'Z' will also repeat once.

\$\endgroup\$
8
  • \$\begingroup\$ Your answer is literally the only answer on this page that returns a correct value for: NINEONENIENOENNNIENOENNEINEONEINEONNENIENOINNEINENINNEINENIENNIENNNNIENNEININENIENNENINEINENINENNIEINNEINNENNIENIN. \$\endgroup\$ Aug 11, 2017 at 14:53
  • \$\begingroup\$ +Infinity points. \$\endgroup\$ Aug 11, 2017 at 14:56
  • \$\begingroup\$ Thanks! (that threw me because there are zero width spaces to the code block in the comment, but (whew) it does work) \$\endgroup\$ Aug 11, 2017 at 15:14
  • \$\begingroup\$ It's not a valid input anyway ;). \$\endgroup\$ Aug 14, 2017 at 1:53
  • \$\begingroup\$ Oh wow, I didn't know a bounty was coming - thanks! Yeah, it was not part of the requested spec, I just made a method that would work with unordered input. \$\endgroup\$ Aug 14, 2017 at 2:11
6
\$\begingroup\$

Python 2,131 127 bytes

s=input()
for y in'WXGURFSOIZ':vars()[y]=s.count(y)
while Z<9:s+=[O-U-W,W,R-U,U,F-U,X,S-X,G,I-X-G-F+U][Z]*str(Z+1);Z+=1
print s

Try it online!

Based on a corrected version of the JavaScript Draco18s solution.

\$\endgroup\$
3
  • \$\begingroup\$ What an interesting use of vars! \$\endgroup\$
    – xnor
    Jul 11, 2017 at 23:24
  • \$\begingroup\$ @xnor it was ovs how learned me that for other golfs :))) \$\endgroup\$
    – mdahmoune
    Jul 12, 2017 at 11:48
  • \$\begingroup\$ Very clever. Have a +1 for adapting my answer (as flawed as it was originally). \$\endgroup\$ Jul 12, 2017 at 15:44
5
\$\begingroup\$

PHP, 164 bytes

for($c=count_chars($argn);$i<9;)echo str_pad("",[$c[79]-$c[87]-$u=$c[85],$c[87],$c[72]-$g=$c[71],$u,$f=$c[70]-$u,$x=$c[88],$c[86]-$f,$g,$c[73]-$x-$f-$g][+$i],++$i);

Try it online!

PHP, 179 bytes

based on the previous approach check first the even numbers and then the odd numbers in increasing order

for($z=[$o=($c=count_chars($argn))[87],$f=$c[85],$x=$c[88],$g=$c[71],$c[79]-$o-$f,$c[72]-$g,$v=$c[70]-$f,$c[86]-$v,$c[73]-$x-$v-$g];$i<9;)echo str_repeat(++$i,$z[_405162738[$i]]);

Try it online!

PHP, 201 bytes

for(;$o=ord(WUXGOHFVN[$i]);$i++)for(;$r[$o]<count_chars($argn)[$o];$t[]=$i>3?2*$i-7:2+2*$i,sort($t))for(++$r[$o],$n=0;$q=ord(([TO,ORF,IS,HEIT,EN,TREE,IVE,SEEN,NIE][+$i])[$n++]);)$r[$q]++;echo join($t);

Try it online!

\$\endgroup\$
9
  • \$\begingroup\$ fails for ENOOWTWTOWOT \$\endgroup\$
    – Titus
    Jul 11, 2017 at 14:58
  • \$\begingroup\$ @Titus is now fixed. I have misunderstand the question \$\endgroup\$ Jul 11, 2017 at 17:05
  • \$\begingroup\$ Yea the examples are somewhat misleading. Wow that did cost! Would you break that down?! \$\endgroup\$
    – Titus
    Jul 11, 2017 at 17:35
  • \$\begingroup\$ @Titus I think I have reach the limit to find another way as your approach \$\endgroup\$ Jul 11, 2017 at 18:18
  • 1
    \$\begingroup\$ $i++<9 and $i instead of $i<10 and ++$i (-1 byte); _405162738[$i] instead of $i%2?$i/2+4:$i/2-1 (-4 bytes) ($i/2+~($i%2*-5) would work too, but that´s one byte longer.) \$\endgroup\$
    – Titus
    Jul 11, 2017 at 19:09
5
\$\begingroup\$

Javascript (ES6), 288 150 144 bytes

q=s=>[u=(l=t=>s.split(t).length-1)`U`,l`O`-l`W`-u,l`W`,l`R`-w,u,f=l`F`-u,x=l`X`,l`S`-x,g=l`G`,l`I`-x-g-f].map((n,i)=>`${i}`.repeat(i&&n)).join``

const testCases = ['NEO', 'ENOWOT', 'EONOTWHTERE', 'SNVEEGHEITNEIN', 'ENOOWTEERHTRUOFEVIFXISNEVESTHGIEENIN']

testCases.forEach(testCase => console.log(testCase, q(testCase)))

Longer than the other two one of the other JS entries, but I thought I'd drop an interesting approach that might work for someone in another language.

Essentially we can determine the following:

W -> 2
X -> 6
G -> 8
U -> 4

Any occurrence of these letters implies that that digit exists in the original number. From here we can deduce the rest of the digits:

R-U -> 3
F-U -> 5
S-X -> 7

Including the two complicated cases:

O-(U+W) -> 1
I-(X+G+(F-U)) -> 9

Both 1 and 9 area Hard comparatively. For ONE, E shows up more than once in some words (SEVEN has two) as does N (NINE), so we're stuck with checking for O which occurs in two other places, fortunately both are simple.

For NINE, nine is hard no matter how you slice it.

Thus we end up with this map:

[u=(l=t=>s.split(t).length-1)`U`,  //unused 0; precompute 'U's
 l`O`-l`W`-u,    //1
 l`W`,           //2
 l`R`-w,         //3
 u,              //4
 f=l`F`-u,       //5
 x=l`X`,         //6
 l`S`-x,         //7
 g=l`G`,         //8
 l`I`-x-g-f]     //9

9 is able to back-reference siX, eiGht, and Five (with 5 back-referencing foUr) with the variable assignments, saving bytes. Thanks to Neil for this, it uses several features of JS I am very unfamiliar with (the back-ticks for stripping (' in half, for instance) and actually comes much closer to the idea I'd doodled out on paper before attempting to code it (I'd left 9 as "what's left over", thinking about it as "if I see an X I can remove it and an S and I from the string, then..." so that after the four simple cases the next 3 would become simple).

The reason this entry is interesting is because it can handle any shuffled string as input. i.e. rather than the individual words being shuffled, we can shuffle the whole string, which is what I thought John was doing originally:

q=s=>[u=(l=t=>s.split(t).length-1)`U`,l`O`-l`W`-u,l`W`,l`R`-w,u,f=l`F`-u,x=l`X`,l`S`-x,g=l`G`,l`I`-x-g-f].map((n,i)=>`${i}`.repeat(i&&n)).join``

const testCases = ['XENSENINEVSI']

testCases.forEach(testCase => console.log(testCase, q(testCase)))

\$\endgroup\$
9
  • 1
    \$\begingroup\$ Great, but there is a problem with counting 9... I think it may be i-x-g-f+u \$\endgroup\$
    – mdahmoune
    Jul 11, 2017 at 22:43
  • \$\begingroup\$ @mdahmoune Shoot, you're right. I messed that one up. :< \$\endgroup\$ Jul 12, 2017 at 1:49
  • \$\begingroup\$ Save 4 bytes by using s.split(t).length-1, 2 bytes using s.repeat(n>0&&n) (why is n less than zero anyway? saves 7 bytes). Save a bunch of bytes by declaring g in the scope of s so that you don't have to keep passing it all the time, and better still you can make it a tagged template, which saves 55 bytes in total (before 9 correction). Save more bytes by saving repeated values in temporaries, and I shaved a few more off using map: s=>[,(l=t=>s.split(t).length-1)`O`-l`W`-l`U`,w=l`W`,l`R`-w,u=l`U`,l`F`-u,x=l`X`,l`S`-x,g=l`G`,l`I`-x-g].map((n,i)=>`${i}`.repeat(n)).join`` . \$\endgroup\$
    – Neil
    Jul 12, 2017 at 12:24
  • \$\begingroup\$ @Neil I am not sure why N ever ended up less than zero, but it did when testing for THREE. I kept getting an error and investigating I found that it was needed, but I'm still not sure. The templated library map you've got there is javascript I don't even know how to read. :D \$\endgroup\$ Jul 12, 2017 at 13:33
  • \$\begingroup\$ @Neil Ah, right, the reason for checking n > 0: If there is a TWO but no THREE. R = 0, W = 1. 0-1 = -1. I was having trouble figuring that out an hour ago, I knew that it was related to the 3-check, but was having a devil of a time working it out (lack of coffee). \$\endgroup\$ Jul 12, 2017 at 14:58
5
\$\begingroup\$

C# (.NET Core), 234 bytes

string q(string s){string[]n="ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE".Split(',');var r="";int x=0,i=0,j;try{for(;;)for (j=x;;)if(n[i].IndexOf(s[j])>=0){if(++j==n[i].Length+x){r+=i+1;x=j;break;}}else{i++;break;}}catch{}return r;}

Try it online!

Expanded version:

static string q(string s)
{
    string[] n = "ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE".Split(',');
    var r = "";
    int x = 0, i = 0, j = 0;
    try
    {
        for(;;)
            for (j = x; ;)
                if (n[i].IndexOf(s[j]) >= 0)
                {
                    if (++j == n[i].Length + x)
                    {
                        r += i + 1; x = j;
                        break;
                    }
                }
                else
                {
                    i++;
                    break;
                }
    }
    catch { }
    return r;
}

Being my first entry, I'm uncertain about the rules... I'm only counting the size of the class used to de-crypt, not the code that tests it, right?

Edit

And for the fun of it - here's what I started doing, not reading the complete rules :S - See it at IdeOne. It de-crypts even when characters from one digit can be scrambled to any place in the string.

Edit 2

Shortened according to tips by TheLethalCoder. Thanks!

Edit 3

And now Titus shaved of a few more bytes. Thanks!

Edit 4 Six years later :D

As Magic Octopus Urn commented, this wasn't working in all cases. Had to add a few bytes to make it work :(

Using try/catch I can ignore boundary checks and save a few bytes :P

\$\endgroup\$
14
  • 2
    \$\begingroup\$ Hello and welcome to PPCG! You only need to include the method, you can remove public static from it to. You can convert to an anonymous method like s=>{<do stuff>return"";}. You can use var a few times, declaring variables together saves bytes i.e. int i=1,j;. Creating an array from a string and splitting on it is usually shorter (though I haven't checked in this case) i.e. "ONE|TWO".Split('|'). You can use <0 instead of ==-1 \$\endgroup\$ Jul 11, 2017 at 11:00
  • \$\begingroup\$ For more tips see Tips for code-golfing in C#. \$\endgroup\$ Jul 11, 2017 at 11:01
  • \$\begingroup\$ @TheLethalCoder Great tips, thanks! \$\endgroup\$
    – SamWhan
    Jul 11, 2017 at 11:03
  • \$\begingroup\$ Not tested at all but I believe the following is the equivalent of your code for 221 bytes: s=>{var n="ONE|TWO|THREE|FOUR|FIVE|SIX|SEVEN|EIGHT|NINE".Split('|');for(int i=0,j;++i<= 9;)for(j=0;n[i-1].IndexOf(s[j])<0;){if(++j==n[i-1].Length){var r=i+"";while(j<s.Length){j+=n[i].Length;r+=++i;}return r;}}return "";} \$\endgroup\$ Jul 11, 2017 at 11:07
  • \$\begingroup\$ On a side note it is usually easier to use TIO for your TIO's! \$\endgroup\$ Jul 11, 2017 at 11:10
4
\$\begingroup\$

Mathematica, 133 bytes

(s={};c=Characters;j=c@#;Table[If[FreeQ[j~Count~#&/@c[#[[i]]]&@ToUpperCase@IntegerName@Range@9,0],s~AppendTo~i],{i,9}];FromDigits@s)&


input

"VENESGTHIEENNI"

output

789

\$\endgroup\$
1
  • \$\begingroup\$ Could you save an extra byte with c@#[[i]] instead of c[#[[i]]]? You might be able to save another byte by using infix syntax ~ on the Table. \$\endgroup\$ Jul 12, 2017 at 6:49
4
\$\begingroup\$

Retina, 88 bytes

[EFIST]

^(ON|NO)*
$#1$*1
O

W
2
HR|RH
3
UR|RU
4
X
6
GH|HG
8
(NN)*$
$#1$*9
r`NV|VN
7
V
5

Try it online!

Explanation

  • First, drop a bunch of unnecessary characters not needed for distinctness
  • Pick the 1s off the front (this lets us drop the rest of the Os immediately after and clears up some Ns before we get to the 5, 7, 9 mess)
  • 2, 3, 4, 6, and 8 are now trivial
  • 9s are a double NN, so grab those off the end before we deal with 5 and 7
  • Replace 7s from the right (so we don't reduce VNV to 75 instead of 57)
  • 5s are the remaining Vs
\$\endgroup\$
2
  • \$\begingroup\$ If you add %(G` to the header, you can use the original code and it will evaluate each line of the input separately: TIO \$\endgroup\$
    – PunPun1000
    Aug 11, 2017 at 19:57
  • \$\begingroup\$ Thanks @PunPun1000. I figured there must be a way to do that but gave up after not finding it quickly. \$\endgroup\$
    – Kytheron
    Aug 18, 2017 at 1:09
3
\$\begingroup\$

JavaScript (ES6), 142 139 Bytes

Saved 3 Bytes thanks to Neil.

Doesn't currently take advantage of numbers are always arranged in ascending order

f=s=>s?'ENO|OTW|EEHRT|FORU|EFIV|ISX|EENSV|EGHIT|EINN'.split`|`.findIndex(w=>[...s.slice(0,y=w.length)].sort().join``==w)+1+f(s.slice(y)):''

f=s=>s?'ENO|OTW|EEHRT|FORU|EFIV|ISX|EENSV|EGHIT|EINN'.split`|`.findIndex(w=>[...s.slice(0,y=w.length)].sort().join``==w)+1+f(s.slice(y)):''

const testCases = ['NEO', 'ENOWOT', 'EONOTWHTERE', 'SNVEEGHEITNEIN', 'ENOOWTEERHTRUOFEVIFXISNEVESTHGIEENIN']

testCases.forEach(testCase => console.log(testCase, f(testCase)))

\$\endgroup\$
5
  • \$\begingroup\$ wait what?? "axbxc".split`x`.join``. How is this called? Can't seem to find anything on google. \$\endgroup\$
    – Qwerty
    Jul 11, 2017 at 10:31
  • \$\begingroup\$ @Qwerty - They are tagged template literals, an ES6 feature which I'm using to save a few bytes by not needing parens in the case of split and join \$\endgroup\$
    – Craig Ayre
    Jul 11, 2017 at 10:33
  • \$\begingroup\$ You answered it. I know tagged template literals, but I haven't realised you can use it on these functions as well. Thank you. \$\endgroup\$
    – Qwerty
    Jul 11, 2017 at 10:35
  • \$\begingroup\$ They're a little different, you have template literals (e.g. x=`foo${5+5}bar`), they're tagged when you call a function using them without parens: foo`foo${5+5}bar` which is the same as foo(['foo','bar'], 10) \$\endgroup\$
    – Craig Ayre
    Jul 11, 2017 at 10:38
  • 1
    \$\begingroup\$ f(s.slice(y)) is always a string so you don't need the ''+ before it. \$\endgroup\$
    – Neil
    Jul 11, 2017 at 11:51
2
\$\begingroup\$

Jelly, 38 bytes

Dị“©ȯ¿w¶&ÇhṆỌƘ#Ȯʋ~¢CNẓ_»Ḳ¤FṢŒu
L3*Ç€iṢ

Try it online!

Explanation

L3*Ç€iṢ    Main link. Argument: s (string)
L            Get length of s.
 3*          Raise 3 to that power. This will always be greater than n.
   ǀ        Get the name of each of the numbers using the helper link.
     iṢ      Find the position of the sorted input.

Dị“©ȯ¿w¶&ÇhṆỌƘ#Ȯʋ~¢CNẓ_»Ḳ¤FṢŒu    Helper link. Argument: n (number)
D                                   Get digits of n.
  “©ȯ¿w¶&ÇhṆỌƘ#Ȯʋ~¢CNẓ_»            The string "one two (...) eight nine AA".
                        Ḳ           Split that string at spaces.
 ị                                  Get name of each digit in the list.
                          F         Flatten to a single string.
                           Ṣ        Sort the characters.
                            Œu      Make uppercase.
\$\endgroup\$
5
2
\$\begingroup\$

Javascript (ES6), 221 bytes

s=>(m=btoa`8Ñ>Mc¾LtDáNQ!Q>HþHA7átþ4Ò`.split`+`.map(s=>RegExp(s.replace(/(.)\1*/g,c=>`(?=(.*${c[0]}){${c.length}})`))),t=0,r=0,[...s].map(c=>(t+=c,d=1,n=0,m.map((r,i)=>t.match(r)&&(d--,n=i)),d||(r=r*10+n+1,t=0))),r)

Example code snippet:

f=

s=>(m=btoa`8Ñ>Mc¾LtDáNQ…!Q>H…þHA7átþ4Ò`.split`+`.map(s=>RegExp(s.replace(/(.)\1*/g,c=>`(?=(.*${c[0]}){${c.length}})`))),t=0,r=0,[...s].map(c=>(t+=c,d=1,n=0,m.map((r,i)=>t.match(r)&&(d--,n=i)),d||(r=r*10+n+1,t=0))),r)

console.log(f("NEO"))
console.log(f("ENOWOT"))
console.log(f("EONOTWHTERE"))
console.log(f("SNVEEGHEITNEIN"))
console.log(f("ENOOWTEERHTRUOFEVIFXISNEVESTHGIEENIN"))

\$\endgroup\$
2
\$\begingroup\$

Retina, 160 bytes

([ONE]{3})*([TWO]{3})*([THRE]{5})*([FOUR]{4})*([FIVE]{4})*([SIX]{3})*([SEVN]{5})*([EIGHT]{5})*([NIE]{4})*
$#1$*1$#2$*2$#3$*3$#4$*4$#5$*5$#6$*6$#7$*7$#8$*8$#9$*9

Try it online! Loosely based on @TessellatingHeckler's PowerShell answer.

\$\endgroup\$
2
\$\begingroup\$

Perl 5, 102 + 1 (-n) = 103 bytes

for$i(map{"[$_]{".length.'}'}ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE){$,++;print$,while(s/^$i//)}

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ Nice! Couple of tricks that help: map{...} can often be replaced with map...,, length and y///c are usually interchangeable too (not always smaller when not working on $_ though!), instead of the while, ++$,x s/^$i// is shorter, and if you change -n to -p you can append to ` $\ ` instead of calling print! Try it online! \$\endgroup\$ Aug 18, 2017 at 12:06
  • \$\begingroup\$ Also, I hope you don't mind me posting any advice, if you'd prefer I'll refrain. :) \$\endgroup\$ Aug 18, 2017 at 12:08
1
\$\begingroup\$

PowerShell, 182 bytes

[regex]::Replace("$args",'(?<1>[ONE]{3z2>[TWO]{3z3>[THRE]{5z4>[FOUR]{4z5>[FIVE]{4z6>[SIX]{3z7>[SVEN]{5z8>[EIGHT]{5z9>[NIE]{4})'.replace('z','})|(?<'),{$args.groups.captures[1].name})

Try it online!

Ungolfed but not working code:

[System.Text.RegularExpressions.Regex]::Replace("$args",

    '(?<1>[ONE]{3})       
    |(?<2>[TWO]{3})
    |(?<3>[THRE]{5})
    |(?<4>[FOUR]{4})
    |(?<5>[FIVE]{4})
    |(?<6>[SIX]{3})
    |(?<7>[SVEN]{5})
    |(?<8>[EIGHT]{5})
    |(?<9>[NIE]{4})'

    ,{$args.groups.captures[1].name}
)

e.g. (?<3>[THRE]{5}) matches the character class THRE, so it can match them out of order, and has to match any of these characters five times next to each other, and the capture group is named '3' to map names with numbers.

Rudimentary compression by swapping the repeating text })|(?< for a z.

\$\endgroup\$
1
\$\begingroup\$

C++, 296, 288 bytes

Short Version:

#define T string
using namespace std;T N[]={"ONE","TWO","THREE","FOUR","FIVE","SIX","SEVEN","EIGHT","NINE"};T Q(T S){T R="";for(int i=0;i<9;i++){do{if(S.find(N[i])!=T::npos){S.erase(S.find(N[i]),N[i].size());R+=to_string(i+1);}}while(next_permutation(N[i].begin(),N[i].end()));}return R;}

Full Version:

#define T string
using namespace std;

T N[]={"ONE","TWO","THREE","FOUR","FIVE","SIX","SEVEN","EIGHT","NINE"};

T Q(T S)
{
    T R="";
    for(int i=0;i<9;i++)                             //for all possible                             
                                                     //codewords (ONE,TWO...NINE)   
    {
        do
        {   
            if(S.find(N[i])!=T::npos)                //if found in encrypted word
            {
                S.erase(S.find(N[i]),N[i].size());  //erase it from the word
                R+=to_string(i+1);                  //save integer to the result string
            }
                                                    //check next permuation of codeword  

        } while(next_permutation(N[i].begin(),N[i].end())); 
    }                                                   

    return R;
}

Try ONLINE!

Edit:
1) 200->296 bytes, for including namespace and definition of N in the count, as suggested by orlp 2) 296->288, for using macro, thanks to Zacharý

\$\endgroup\$
4
  • \$\begingroup\$ You need to include the definition of N and using namespace std; into your byte count. \$\endgroup\$
    – orlp
    Jul 11, 2017 at 9:19
  • \$\begingroup\$ I should be more specific, not just include it in your byte count but also into your answer. Your answer must be able to run just by calling Q right after it without any other additions. \$\endgroup\$
    – orlp
    Jul 11, 2017 at 9:39
  • \$\begingroup\$ I re-edited to include it all. For the definition of N i was not sure myself, but for the namespace, I usually dont include it (treat it as library stuff). Though, in the current code it is crucial for the string to work \$\endgroup\$ Jul 11, 2017 at 10:07
  • 1
    \$\begingroup\$ Can you define a macro to save a few bytes? repl.it/JY7k \$\endgroup\$
    – Adalynn
    Jul 12, 2017 at 13:30
1
\$\begingroup\$

Ruby, 138 114 110 bytes

gsub(/#{"3ONE3TWO5THRE4FOUR4FIVE3SIX5SEVN5EIGHT4NIE".gsub(/(.)(\D+)/,'([\2]{\1})|')}/){(1..9).find{|i|$~[i]}}

Byte count includes 1 byte for the -p option.

What?

This:

/#{"3ONE3TWO5THRE4FOUR4FIVE3SIX5SEVN5EIGHT4NIE".gsub(/(.)(\D+)/,'([\2]{\1})|')}/

is a regex literal which, through string interpolation, evaluates to:

/([ONE]{3})|([TWO]{3})|([THRE]{5})|([FOUR]{4})|([FIVE]{4})|([SIX]{3})|([SEVN]{5})|([EIGHT]{5})|([NIE]{4})|/

If we assign that to regex, the rest of the code is somewhat easy to grasp: Each match in the input is substituted with the number of the capturing group, extracted from the magical variable $~ which contains the current match data:

gsub(regex){(1..9).find{|i|$~[i]}}

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Java 8, 198 256 bytes

s->{String r="",x=r;for(String n:"ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE".split(" ")){for(char c:n.toCharArray())x+="(?=.*"+c+")";x+="["+n+"]{"+n.length()+"}x";}for(int i=0,q;i<9;)for(q=(s+" ").split(x.split("x")[i++]).length-1;q-->0;)r+=i;return r;}

+58 bytes.. due to regex of the previous version not working properly (it was also matching "EEE";"EEN";etc.)

Explanation:

Try it here.

s->{                     // Method with String as parameter and return-type
  String r="",           //  Result-String
         x=r;            //  Regex-String
  for(String n:"ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE".split(" ")){
                         //  Loop (1) from "ONE" through "NINE":
    for(char c:n.toCharArray())
                         //   Inner loop (2) over the characters of this String
      x+="(?=.*"+c+")";  //    Append regex-group `(?=\w*c)` where `c` is the capital character
                         //   End of inner loop (2) (implicit / single-line body)
    x+="["+n+"]{"+n.length()+"}x";
                         //   Append regex part `[s]{n}` where `s` is the String, and `n` is the length
  }                      //  End of loop (1)
  // The regex now looks like this, which we can split on "x":
  // (?=.*O)(?=.*N)(?=.*E)[ONE]{3}x(?=.*T)(?=.*W)(?=.*O)[TWO]{3}x(?=.*T)(?=.*H)(?=.*R)(?=.*E)(?=.*E)[THREE]{5}x(?=.*F)(?=.*O)(?=.*U)(?=.*R)[FOUR]{4}x(?=.*F)(?=.*I)(?=.*V)(?=.*E)[FIVE]{4}x(?=.*S)(?=.*I)(?=.*X)[SIX]{3}x(?=.*S)(?=.*E)(?=.*V)(?=.*E)(?=.*N)[SEVEN]{5}x(?=.*E)(?=.*I)(?=.*G)(?=.*H)(?=.*T)[EIGHT]{5}x(?=.*N)(?=.*I)(?=.*N)(?=.*E)[NINE]{4}x
  for(int i=0,q;i<9;)    //  Loop (3) from 0 through 9 (exclusive)
    for(q=(s+" ").split(x.split("x")[i++]).length-1;
                         //   Split the input on the current regex-part,
                         //   and save the length - 1 in `q`
        q-->0;           //   Inner loop (4) over `q`
      r+=i               //    And append the result-String with the current index (+1)
    );                   //   End of inner loop (4)
                         //  End of loop (3) (implicit / single-line body)
  return r;              //  Return the result-String
}                        // End of method
\$\endgroup\$
6
  • 1
    \$\begingroup\$ Erf... wrong result for "ENOOWTEERHTRUOFEVIFXISNEVESTHGIEENIN" :( \$\endgroup\$ Jul 12, 2017 at 10:28
  • \$\begingroup\$ Yeah, that's the only thing that prevented me to +1 this! My solution was 240 bytes... before you beat me to it. \$\endgroup\$ Jul 12, 2017 at 13:22
  • \$\begingroup\$ @OlivierGrégoire Feel free to post your 240 byte solution, because I'm unable to find a solution.. The disadvantage about [ONE]{3} is that it also matches EEN at the end of that test case with parts of EIGHT and NINE.. And I doubt there is a regex to match all these: ENO|EON|NEO|NOE|OEN|ONE without also matching EEE;EEN;EEO;... for all numbers that is shorter than 40 bytes.. Maybe I can do something using substring and reverse checking the numbers, but I don't really have the time to figure it out now.. \$\endgroup\$ Jul 12, 2017 at 14:17
  • \$\begingroup\$ @OlivierGrégoire If you still have your 240 byte answer, feel free to post it. Just came across this challenge again, and fixed my answer by making a new regex for +58 bytes.. \$\endgroup\$ Aug 11, 2017 at 12:27
  • 1
    \$\begingroup\$ Well, looks like I found an even shorter way while redoing this challenge :p \$\endgroup\$ Aug 11, 2017 at 13:30
1
\$\begingroup\$

Java (OpenJDK 8), 181 bytes

s->{String x="",r;for(int i=0,l;i<9;)for(r="ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE".split(",")[i++],l=r.length();s.matches("["+r+"]{"+l+"}.*");s=s.substring(l))x+=i;return x;}

Try it online!

I took the liberty to reuse Kevin Cruyssen's TIO template. Hope you don't mind ;)

\$\endgroup\$
1
  • \$\begingroup\$ Ah, nevermind my previous comment.. You build the regex, instead of loop over the regex. Still, I was close with my first answer if only I had used the s.substring. The worst part is, is that I am using s.substring in my current answer, lol.. Ah well, +1 from me. Glad it's almost weekend.. \$\endgroup\$ Aug 11, 2017 at 13:57
1
\$\begingroup\$

05AB1E, 36 31 bytes

‘€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š‘#vyœN>UvyX:

Try it online!


View it ran with debug: TIO With Debug

‘€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š‘# | Push ['ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE']
vyœ                   | For each list of permutations of that word...
   N>U                | Push index + 1 into register X.          
      vyX:            | Replace each permutation with X.
\$\endgroup\$
1
  • \$\begingroup\$ I was just suggesting you had the green mark rather than me and I noticed a bug: FURONESEV returns FUR1SEV :( \$\endgroup\$ Feb 19, 2018 at 13:51
0
\$\begingroup\$

Python 3, 238 236 bytes

def f(s):
 e=''
 while len(s):
  for i in range(9):
   for r in[''.join(p)for p in permutations('ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE'.split()[i])]: 
    if s[:len(r)]==r:e+=str(i+1);s=s[len(r):]
 return e
from itertools import*

Try it online!


Brute-force solution, doesn't take advantage non-decreasingness of digits.


Thanks to @Mr. Xcoder for saving 2 bytes!

\$\endgroup\$
4
  • \$\begingroup\$ You have to include def f(s): in your byte count, this is not an anonymouos function \$\endgroup\$
    – Mr. Xcoder
    Jul 11, 2017 at 21:40
  • \$\begingroup\$ Also you can replace while len(s)>0 with while len(s) \$\endgroup\$
    – Mr. Xcoder
    Jul 11, 2017 at 21:41
  • \$\begingroup\$ @Mr.Xcoder thanks for that clarification \$\endgroup\$
    – Chase
    Jul 12, 2017 at 3:12
  • \$\begingroup\$ You can move the declaration of e into the function header for -1 byte. Also, exec and list comprehensions might save bytes on indentation. \$\endgroup\$ Jul 13, 2017 at 1:25
0
\$\begingroup\$

PHP, 141 bytes

for($a=count_chars($argn);$c=ord($s[++$p]?:$s=[OWU,W,HG,U,FU,X,SX,G,N17.$p=0][$i-print str_repeat($i++,$x)]);)$x=$a[$i+48]+=($p?-1:1)*$a[$c];

older version, 151 bytes:

for($a=count_chars($argn,1);$s=[OWU,W,HG,U,FU,X,SX,G,N17][+$i++];print str_repeat($i,$a[$i+48]))for($p=0;$c=ord($s[$p]);)$a[$i+48]+=($p++?-1:1)*$a[$c];

loops through the digits from 1 to 9, counting unique characters in the word and subtracting non-unique characters´ counts, printing the digit on the go.
Although it is printing on the go, the digit counts must be stored for the 9 case to work.

Run as pipe with -nR or try it online.

It would save 4 more bytes to store the digit counts in $a[$i] instead of $a[$i+48] and use ASCII 1 and 7 (in quotes) instead of the digit characters themselves.

breakdown

for(
    $a=count_chars($argn,1);                # count character occurences in input
    $s=[OWU,W,HG,U,FU,X,SX,G,N17][+$i++];   # loop through digit names
    print str_repeat($i,$a[$i+48])              # print digit repeatedly
)
    for($p=0;$c=ord($s[$p]);)                   # loop through name
        $a[$i+48]+=                                 # add to digit count
        ($p++?-1:1)*                                # (add first, subtract other)
        $a[$c];                                     # character occurences

ONE is not the only word with an O, so it needs to subtract the counts for W (only appearing in TWO) and U (only appearing in FOUR) and so on.
NINE is special, because there is no way to just subtract if I used the letters (that would require I-X-G-F+U or N-O-S+W+U+X), so I use the digit counts instead.

PHP, 160 bytes

$a=count_chars($argn);foreach([W2O,U4FOR,X6SI,G8I,F5I,O1,R3,S7,I9]as$s)for(${$s[$p=1]}+=$n=$a[ord($s)];$c=ord($s[++$p]);)$a[$c]-=$n;while($$i--?print$i:$i++<9);

assumes all upper case input; characters may be scrambled all over.
Run as pipe with -nR or try it online.

explanation

loops through the digit words, counting their unique characters´ occurences in the input and in the process reducing the count of other characters. "Other characters" could mean all other characters in the word; but only considering those that will be needed later saved 19 bytes.

Transforming the str_repeat loop to a combined loop saved 5 bytes.

And using variable variables for the digit count saved another 8.

breakdown

$a=count_chars($argn);                              # count character occurences in input
foreach([W2O,U4FOR,X6SI,G8I,F5I,O1,R3,S7,I9]as$s)   # loop through digit names
    for(${$s[$p=1]}+=                                   # 2. add to digits count
        $n=$a[ord($s)];                                 # 1. get count of unique character
        $c=ord($s[++$p]);)                              # 3. loop through other characters
        $a[$c]-=$n;                                         # reduce character count
while(
    $$i--?print$i                                       # print digit repeatedly
    :$i++<9);                                       # loop through digits
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.