Input

A non-empty encoded string consisting of printable ASCII characters (in the range 32-126), where some missing letters have been replaced with _.

Output

A decoded string of the same length with all letters in lowercase, including the missing ones.

How?

Edit: As mentioned by @Deusovi in the comments, this is a variant of Bacon's cipher.

  • Gather all letters in the original string and group them by 5. Additional letters that do not fit in a full group of 5 are ignored.
  • Convert each group into binary: lowercase = 0, uppercase = 1. This leads to a list of integers.
  • Use each value N in this list to replace each _ in the original string with the N-th letter of the alphabet (0-indexed), in order of appearance.

Example: prOGraMMIng PuZZleS & cOde ____

prOGr --> 00110 -->  6 -->  7th letter = 'g'
aMMIn --> 01110 --> 14 --> 15th letter = 'o'
gPuZZ --> 01011 --> 11 --> 12th letter = 'l'
leScO --> 00101 -->  5 -->  6th letter = 'f'

By replacing the missing letters and converting everything back to lowercase, the original string is unveiled:

programming puzzles & code golf

This is the expected output.

Clarifications and rules

  • The missing letters are guaranteed to appear at the end of the string. More formally: there will never be any letter after the first _ in the input string. However, there may be other printable ASCII characters such as spaces and punctuation marks.
  • The input is guaranteed not to contain any useless capital letter: all capital letters are bits set to 1 which are required to decode the missing letters. Everything else is in lowercase.
  • The input string is guaranteed to be valid. Especially:
    • It will always contain enough full groups of 5 letters to decode the underscores.
    • The binary-encoded integers are guaranteed to be in the range [0-25].
  • There may be no _ at all in the input string, in which case you just have to return the input.
  • This is , so the shortest answer in bytes wins!

Test cases

Input : hello!
Output: hello!

Input : helLO, worl_!
Output: hello, world!

Input : i aM yoUr faTh__.
Output: i am your father.

Input : prOGraMMIng PuZZleS & cOde ____
Output: programming puzzles & code golf

Input : Can YOu gUesS tHE ENd oF This ____?
Output: can you guess the end of this text?

Input : THe qUICk brown FOx JUMps oVEr the la__ ___.
Output: the quick brown fox jumps over the lazy dog.

Input : RoadS? wHERe we're goinG WE doN't need _____.
Output: roads? where we're going we don't need roads.

Input : thE greatESt Trick thE DeVIl EVer PUllEd wAs CONvInciNg tHe WorLD h_ ____'_ _____.
Output: the greatest trick the devil ever pulled was convincing the world he didn't exist.

Some extra test-cases:

Input : BInar_
Output: binary

Input : hyPerbolIZ__
Output: hyperbolized
  • Should we only consider as many groups of 5 as there are underscores in the input? – Esolanging Fruit 18 hours ago
  • In that case, the rule when there's no _ in the input string is a bit of a special case. – Esolanging Fruit 18 hours ago
  • Sorry, misunderstood your comment (and I think you misunderstood mine). My question is whether we should ignore extra groups of five if there are too few underscores, which seems apparent from the test cases. – Esolanging Fruit 18 hours ago
  • 1
    Ooh, Bacon cipher! – Deusovi 17 hours ago
  • @Deusovi Ah, I was pretty sure such a scheme would exist. Thanks for mentioning the right name. Related. – Arnauld 16 hours ago

15 Answers 15

05AB1E, 18 bytes

Code:

áS.u5ôJC>.bv'_y.;l

Uses the 05AB1E encoding. Try it online!

Explanation:

á                       # Remove non-letters from the input string.
 S                      # Split the result into individual characters.
  .u                    # Check if is uppercase for each character.
    5ôJ                 # Split into binary numbers of length 5.
       C                # Convert from binary to decimal.
        >               # Add one.
         .b             # Map 1 → A, 2 → B, 3 → C, ..., 25 → Y, 26 → Z.
           v            # For each letter:
            '_y.;       #   Replace the first occurrence of '_' with the current letter.
                 l      #   Convert the string to lowercase.

Jelly,  28 27  26 bytes

-1 thanks to Erik the Outgolfer & dylnan

Not a very Jelly-friendly challenge!

ḟŒs$Ƈ<”[s5Ḅ+97Ọż@ṣ”_$FṁLŒl

A monadic link accepting and returning lists of characters.

Try it online!

How?

ḟŒs$Ƈ<”[s5Ḅ+97Ọż@ṣ”_$FṁLŒl - Link: list of characters    e.g. "MfUNE_?"  (shorthand for ['M','f','U','N','E','_','?'])
    Ƈ                      - filter keep each if:
   $                       -   last two links as a monad:
 Œs                        -     swap-case
ḟ                          -     filter discard
                           - ...i.e. keep A-Z,a-z since they change when the case is swapped
                           -                                  "MfUNE"
      ”[                   - literal character                '['
     <                     - less than? (i.e. is upper-case?) [1,0,1,1,1]
        s5                 - split into fives                 [[1,0,1,1,1]]
          Ḅ                - from base two (vectorises)       [[23]]
           +97             - add (vectorises) ninety-seven    [[120]]
              Ọ            - from ordinals (vectorises)       [['x']]
                    $      - last two links as a monad:
                  ”_       -   literal character              '_'
                 ṣ         -   split at                       [['M','f','U','N','E'],['?']]
               ż@          - swapped @rgument zip             [[['M','f','U','N','E'],'x'],['?']]
                     F     - flatten                          "MfUNEx?"
                       L   - length (of input)                7
                      ṁ    - mould like                       "MfUNEx?"
                           - ...removes any excess characters
                        Œl - lower-case                       "mfunex?"

Python 2, 113 bytes

s=input()
i=k=0
for c in s:
 if c.isalpha():k+=k+(c<'a');i+=1;s=s.replace('_',chr(k%32+97),i%5<1)
print s.lower()

Try it online!

Scala, 184 bytes

def f(s:Array[Char])={var j=0;s.zipWithIndex.collect{case(95,i)=>s(i)=(Integer.parseInt(s.filter(_!=32)slice(j,j+5)map(k=>if(k<91)1 else 0)mkString,2)+97)toChar;j+=5};s.map(_.toLower)}

Try it online!

Explanation:

def f(s: Array[Char]) = {                // takes a String in input
  var j = 0                              // j stores at which block of 5 letters we're currently at
  s.zipWithIndex.collect {               // Array('h', 'e', ...) => Array(('h', 0) ('e', 1), ...) and we apply a collect transformation (filter/map)
    case (95, i) =>                      // we only handle cases where the char is '_' (95)
      s(i) = (                           // we modify the char at index i with the following
        Integer.parseInt(                // Integer.parseInt("00110", 2) = 6
          s                              //
            .filter(_! = 32)             // filter out spaces from the input string
            .slice(j, j+5)               // "substring" the array to the block of 5 letters in question
            .map(                        // map on the current block of 5 letters
              k =>                       // the index of the next char in the block f 5 (e.g. 13)
                if (k < 91) 1 else 0     // if the current char is upper case (<91) then we replace it by a bit true, otherwise by a bit false
            )mkString,                   // Array(0, 1, 1, ...) => "011..."
          2                              // cast string to binary
        )                                //
        + 97                             // +97 to create a lower case char
      )toChar                           // cast from int to char
      j += 5                             // update the starting index of the next block of 5 letters
    }                                    //
  s.map(_.toLower)                       // return the updated seq of chars all in lower case
}                                        //
  • 1
    Oups missed it. Let's add another 15 bytes! – Xavier Guihot 16 hours ago

J, 62 61 bytes

tolower u:@]`(I.@t=.'_'=[)`[}+/@t$97+_5#.\3|0 2 4-.~'@Z`z'&I.

Try it online!

Perl 5 -pF'' -MList::Util=sum, 75 bytes

@a=grep!/\W|\d/,@F;s!_!(a..z)[sum map{a gt shift@a&&16/2**$_}0..4]!eg;$_=lc

Try it online!

Explanation:

  • -pF'' reads a line of input into the variable $_ and, split into characters, into the array @F.
  • @a=grep!/\W|\d/,@F sets the array @a equal to those members of @F that don't satisfy the regex \W|\d. \W is anything but letters, numbers, and _; \d is numbers. So \W|\d is anything but letters and _, and @a has all the letters and _ characters. We will wind up never examining the _ characters in @a. (Note that this only works because the input is guaranteed ASCII.)
  • map{a gt shift@a&&16/2**$_}0..4 does the following for 0 through 4: It shifts the next element off of @a, shortening it, and evaluates whether a is asciibetically greater than that element (i.e. whether that element is uppercase). If so, && isn't short-circuited, so we get 16 divided by 2 to rhe power of the input value (0 through 4). Otherwise && is short-circuited and we get 0. map returns the list of five numbers to sum, which adds them.
  • That's the element we want from the list a..z, and that's what we get from (a..z)[…].
  • s!_!…!eg converts each _ in $_, in turn, to the appropriate letter.
  • $_=lc converts $_ to the lowercase version of itself, and -p prints it.

CJam, 43 bytes

q'_/_0={el_eu=!},_eu.=5/2fb65f+:c1$,(<.+:el

Try it online!

Retina, 91 90 bytes

T`l`a
T`L`A
[^Aa]

L`.{5}
A
aA
+`Aa
aAA
+T`_lA`l_`[^A]A
^
$+¶
+`_(.*)¶a+(.)
$2$1
0G`
T`L`l

Try it online! Explanation:

T`l`a
T`L`A
[^Aa]

Translate lowercase letters to a and uppercase letters to A, deleting everything else.

L`.{5}

Split the Aas into groups of 5.

A
aA
+`Aa
aAA

Convert from binary into unary, treating A as 1 and a as 0. Since there were 5 Aas originally, there are 5 as left, plus a number of As depending on the desired position in the alphabet.

+T`_lA`l_`[^A]A

Increment the last a according to the number of following As.

^
$+¶

Prepend the original input.

+`_(.*)¶a+(.)
$2$1

Replace any _s with the next decoded letter.

0G`

Remove any spare decoded letters.

T`L`l

Lowercase everything.

Retina 0.8.2, 117 bytes

.+
$&¶$&
T`L`l`^.*
T`l`a`.*$
T`L`A
T`aAp`aA_`.*$
(.*¶)?.{5}
$&;
A
aA
+`Aa
aAA
+T`_lA`l_`[^A]A
+`_(.*¶)a+(.);
$2$1
1G`

Try it online! Explanation:

.+
$&¶$&

Duplicate the input.

T`L`l`^.*

Lowercase the first copy.

T`l`a`.*$

Translate lowercase letters to a in the second copy.

T`L`A

Translate uppercase letters to A. These must be in the second copy because the first copy was already lowercased.

T`aAp`aA_`.*$

Delete everything else in the second copy.

(.*¶)?.{5}
$&;

Split the second copy (now just Aas) into groups of 5.

A
aA
+`Aa
aAA
+T`_lA`l_`[^A]A
+`_(.*¶)a+(.);
$2$1
1G`

Decode the letters and insert them as before.

JavaScript (Node.js), 125 124 bytes

x=>x.replace(s=/./g,c=>parseInt(c,36)>9?c.toLowerCase(s+=+(c<{})):c=='_'?('0b'+s.slice(4,9)-~9).toString(36,s=s.slice(5)):c)

Try it online!

Clean, 180 ... 150 bytes

import StdEnv
?s=['a'+sum[i\\i<-:""&c<-s|c<'a']: ?(drop 5s)]
@['_':b][x:y]=[x: @b y]
@[a:b]z=[toLower a: @b z]
@e _=e
$s= @s(?(filter isAlpha s))

Try it online!

Defines the function $ :: [Char] -> [Char] with @ :: [Char] [Char] -> [Char] as a helper to replace underscores, and ? :: [Char] -> [Char] as a helper to generate the replacement characters.

C (gcc), 111 bytes

Edit: Added lowercasing per @FrownyFrog's comment.

f(s,t,i,j)char*s,*t;{for(t=s;t=strchr(t,95);*t=97+i)for(i=j=0;j<5;s++)isalpha(*s)?i=i*2+!(*s&32),*s|=32,j++:0;}

Try it online!

  • You can save 2 bytes with i+=i+(*s<97). – Lynn 2 hours ago

APL (Dyalog Unicode), 46 bytesSBCS

Anonymous lambda, Assumes ⎕IO (Index Origin) to be 0.

{_←'_'=⊢⋄819⌶A[2⊥⍉(+/_⍵)5⍴A∊⍨⍵∩A,819⌶A←⎕A]@_⍵}

Try it online!

{} two-statement function; is the argument, separates statements

 argument (no-op function)
'_'= where equal to an underscore (i.e. a Boolean mapping function)
_← assign that function to _

A[]@_⍵ put the following characters of A at positions of underscores in the argument
  ⎕A the uppercase Alphabet
  A← assign that to A
  819⌶ lowercase it (819 ≈ BIg, with no left argument means not big, i.e. lowercase)
  A, prepend the uppercase alphabet; this gives us all letters
  ⍵∩ intersection of the argument and that; just the letters of the argument
  A∊⍨ which of those are members of the uppercase alphabet; uppercase bits
  ()5⍴reshape that to the following number of rows, and five columns:
   _⍵ the mask of underscores in the argument
   +/ sum that; number of underscores
   transpose (to treat each row as a number rather than as a bit position)
  2⊥ evaluate as base-2
819⌶ lowercase everything

Go, 219 217 192 210 209 156 bytes

Saved 25 bytes thanks to @Lynn! Saved 53 bytes thanks to @ovs!

Had to lose 18 bytes because of a bug with strings with no underscores :(

func p(s string){b:=0;n:=0;for _,c:=range s{if IsLetter(c){b+=b;if IsUpper(c){b+=1};n++;s=g.Replace(s,"_",string('a'+b%32),(5-n%5)/5)}};Print(g.ToLower(s))}

Try it online!

  • 156 bytes – ovs 5 hours ago
  • Looks like I was overthinking the problem just a tad. Thanks! – ollien 2 hours ago

Jelly, 26 bytes

xn¥Œs<”[s5Ḅ‘ịØaṛi”_ḟ0Ɗ¦ƒŒl

Try it online!

Different approach from Jonathan Allan's. EDIT: So, I, uh, apparently thought of the same byte reduction as Jonathan Allan, so it doesn't hurt to mention his name again.

Red, 247 bytes

func[s][a: charset[#"a"-#"z"#"A"-#"Z"]v: copy""parse s[any[copy c a(append v to-string c)|
skip]]k: 0 t: copy""repeat n(length? v)/ 5[c: 0 p: 16
loop 5[if v/(k: k + 1) <#"a"[c: c + p]p: p / 2]append t#"a"+ c]foreach c
t[replace s"_"c]lowercase s]

Try it online!

More readable:

f: func[s][
    a: charset[#"a"-#"z"#"A"-#"Z"]
    v: copy ""
    parse s[any[copy c a(append v to-string c)| skip]]
    k: 0
    t: copy ""
    repeat n (length? v) / 5[
        c: 0
        p: 16
        loop 5[
            if v/(k: k + 1) < #"a" [c: c + p]
            p: p / 2
        ]
        append t #"a" + c
    ]
    foreach c t[replace s "_" c]
    lowercase s
]

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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