25
\$\begingroup\$

Input

A string of printable ASCII characters, for example:

This is an example string.

Output

For every consonant (BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz) that is not followed by a vowel (AEIOUaeiou) add the last vowel before it, in lowercase.
Consonants before the first vowel are left as-is:

Thisi isi ana examapale seterinigi.

Test cases

AN EXAMPLE WITH A LOT UPPERCASE (plus some lowercase)
=> ANa EXAMaPaLE WITiHi A LOTo UPuPEReCASE (pelusu some lowerecase)

And here comes a **TEST** case with 10% symbols/numbers(#)!
=> Anada here comese a **TESeTe** case witihi 10% siyimiboloso/numuberese(#)!

This is an example string.
=> Thisi isi ana examapale seterinigi.

abcdefghijklmnopqrstuvwxyz
=> abacadefegehijikiliminopoqorosotuvuwuxuyuzu

A pnm bnn
=> A panama banana

Tell me if you need more test cases!
=> Telele me ifi you neede more tesete casese!

Scoring

As this is , the answer with the lowest byte-count in each language wins (no answer will be accepted).

\$\endgroup\$
12
  • \$\begingroup\$ So, should the inserted vowels be always lowercase, while the text might be both uppercase and lowercase? \$\endgroup\$ Jun 6, 2018 at 17:53
  • \$\begingroup\$ Can the output be in the form of a list/array? \$\endgroup\$ Jun 6, 2018 at 17:54
  • \$\begingroup\$ @EriktheOutgolfer Yes, I did not want uppercase where we need lowercase, but it would have overcomplicated the challenge if one had to check the case of adjacent letters \$\endgroup\$
    – wastl
    Jun 6, 2018 at 17:55
  • 11
    \$\begingroup\$ Eat healthy kids, try A pnm bnn! \$\endgroup\$ Jun 6, 2018 at 20:09
  • 4
    \$\begingroup\$ Does anyone else think "How Italians" needs to go in the title somewhere? \$\endgroup\$
    – Artelius
    Jun 7, 2018 at 1:17

19 Answers 19

14
\$\begingroup\$

Retina, 48 bytes

i`(?<=([aeiou]).*?[^\W\d_aeiou])(?![aeiou])
$l$1

Try it online! Explanation: The lookahead searches for a point not followed by a vowel, while the lookbehind searches for an immediately preceding consonant and a previous vowel, which is then inserted in lower case.

\$\endgroup\$
8
\$\begingroup\$

JavaScript (ES6), 108 105 bytes

(Saved 3 bytes thanks to @Shaggy.)

f=s=>(t=(s+=' ').replace(/[aeiou]|[a-z][^aeiou]/ig,r=>r[1]?r[0]+v.toLowerCase()+r[1]:v=r,v=''))!=s?f(t):s

Searches for vowels or for consonants with no following vowel:

/[aeiou]|[a-z][^aeiou]/ig

(We don't need to search for consonants explicitly, because vowels are excluded based on the /[aeiou]|....)

Vowels are stored in v, and consonants with no following vowel have v inserted:

r[1]?r[0]+v.toLowerCase()+r[1]:v=r

(If r[1] exists, we've matched on a consonant plus non-vowel.)

If nothing has been changed, we return the input. Otherwise, we recurse on the replaced string.

f=s=>(t=(s+=' ').replace(/[aeiou]|[a-z][^aeiou]/ig,r=>r[1]?r[0]+v.toLowerCase()+r[1]:v=r,v=''))!=s?f(t):s

console.log(f('A pnm bnn'));
console.log(f('abc'));
console.log(f('AN EXAMPLE WITH A LOT UPPERCASE (plus some lowercase)')); 
console.log(f('And here comes a **TEST** case with 10% symbols/numbers(#)!'));
console.log(f('This is an example string.'));
console.log(f('abcdefghijklmnopqrstuvwxyz'));
console.log(f('Tell me if you need more test cases!'));

\$\endgroup\$
9
  • 1
    \$\begingroup\$ This is even better. I really need to look at regex \$\endgroup\$ Jun 6, 2018 at 19:45
  • \$\begingroup\$ +1 smart idea to use replace over map + join \$\endgroup\$
    – Downgoat
    Jun 6, 2018 at 19:56
  • \$\begingroup\$ Based of yours an (almost) working version without recursion: s=>s.replace(/[aeiou][^a-z]*([a-z](?![aeiou]))+/gi,s=>s.replace(/(?!^)./g,a=>a+s[0].toLowerCase())) I can't seems to have issues with sequences of non-letters though \$\endgroup\$
    – Downgoat
    Jun 6, 2018 at 20:06
  • \$\begingroup\$ Recursion certainly simplifies things here. \$\endgroup\$ Jun 6, 2018 at 20:14
  • \$\begingroup\$ (s+=' ') should save a few bytes. \$\endgroup\$
    – Shaggy
    Jun 6, 2018 at 22:01
5
\$\begingroup\$

Python 2, 134 119 bytes

def f(s,v=''):u=s[:1];return s and u+v*(u.isalpha()-g(u)-g((s+u)[1]))+f(s[1:],[v,u.lower()][g(u)])
g='aeiouAEIOU'.count

Try it online!

EDIT: 15 bytes thx to Lynn

\$\endgroup\$
2
  • \$\begingroup\$ I fiddled with it a little for 119 bytes. \$\endgroup\$
    – Lynn
    Jun 7, 2018 at 4:59
  • \$\begingroup\$ @Lynn: Love the <vowels>.count. \$\endgroup\$
    – Chas Brown
    Jun 7, 2018 at 5:32
5
\$\begingroup\$

sed 4.2.2, 64 bytes

:
s/(([aeiou])[^a-z]*[b-df-hj-np-tv-z])([^aeiou]|$)/\1\l\2\3/I
t

Try it online!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I'll be honest, my goal here is just to try to beat perl by a couple of bytes. Let's see if it holds :) \$\endgroup\$ Jun 7, 2018 at 8:36
4
\$\begingroup\$

Standard ML, 225 223 bytes

str o Char.toLower;fun?c=String.isSubstring(it c)"aeiou"fun g(x,l)$d=(fn l=>if Char.isAlpha$andalso not(?d)then if? $then(it$,l)else(x,l^x)else(x,l))(l^str$)fun f$(c::d::r)=f(g$c d)(d::r)|f$[c]= #2(g$c c);f("","")o explode;

Try it online!

Less golfed:

val lower = str o Char.toLower

fun isVowel c = String.isSubstring (lower c) "aeiou"

(* c is the current char, d is the next char, x is the last vowel and l the accumulator 
   for the resulting string *)
fun g (x,l) c d = 
    if Char.isAlpha c andalso not (isVowel d)
    then if isVowel c 
         then (lower c, l^str c)
         else (x, l^str c^x)
    else (x, l^str c)

fun f t (c::d::r) = f (g t c d) (d::r)
  | f t [c] = #2(g t c #"d")

val h = f ("","") o explode;

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ wow, ML golf looks really interesting! I love it and the use of the $ variable name. \$\endgroup\$
    – Lynn
    Jun 7, 2018 at 5:50
  • \$\begingroup\$ @Lynn I wrote a tip about identifier renaming some time ago and planned to write one about it as well, but haven't gotten around doing so yet. \$\endgroup\$
    – Laikoni
    Jun 7, 2018 at 13:50
4
\$\begingroup\$

Perl 5, 68 67 59 bytes

perl -pe '$v="[aeiou])";1while s/($v[^a-z]*[b-z]\K(?<!$v(?!$v/\L$1/i'

Here's a great example of the usefulness of \K, and I can't believe I didn't know about this feature before Dom Hastings pointed it out.

I haven't been able to get the right behavior with just using s///g, so an actual loop seems necessary. (It's possible that the right use of a look-behind assertion could work without an explicit while -- but I haven't found it.)

\$\endgroup\$
2
  • \$\begingroup\$ Nice approach! Wasn't able to come up with anything better, but managed to get 6 bytes off: Try it online! \$\endgroup\$ Jun 7, 2018 at 10:08
  • 1
    \$\begingroup\$ @DomHastings: Even shorter (down to 58 bytes) by factoring out [aeiou]) to variable: Try it online! \$\endgroup\$ Jun 7, 2018 at 19:26
3
\$\begingroup\$

JavaScript ES6, 115 bytes

Saves 8 bytes thanks to @ETHProductions

s=>[x="",...s].map((i,j)=>(r=/[aeiou]/i).test(i)?x=i:/[a-z]/i.test(i)&&!r.test(s[j]||1)?i+x.toLowerCase():i).join``

I have managed to inflate this more in the process of golfing it O_o but it also fixes a bug

s=>[x="",...s].map(             // Create a new array with x storing last vowel
                                // This also offsets indexes by one so rel to original str refers to next char
   (i,j)=>                      // Going through each char...
      (r=/[aeiou]/i).test(i)?   // If it's vowel, store it in x
          x=i:
      /[a-z]/i.test(i)          // If a letter (thats not a vowel excluded by above)
         &&!r.test(s[j]||1)?    // Test if next char is *not* vowel
         i+x.toLowerCase():i    // If it isn't, then add the most recent vowel after
    ).join``                    // Combine back to string
\$\endgroup\$
3
  • \$\begingroup\$ @RickHitchcock oh shoot totally forgot about ending char, fixing asap \$\endgroup\$
    – Downgoat
    Jun 6, 2018 at 19:42
  • 1
    \$\begingroup\$ @RickHitchcock ok fixed \$\endgroup\$
    – Downgoat
    Jun 6, 2018 at 19:55
  • \$\begingroup\$ Ah, yes thanks for the golf @ETHproductions \$\endgroup\$
    – Downgoat
    Jun 6, 2018 at 20:12
3
\$\begingroup\$

JavaScript, 88 82 Bytes

Done with a single regular expression:

Original Version (88 Bytes):

s=>s.replace(/(?<=([aeiou]).*?(?![aeiou])[a-z])(?=[^aeiou]|$)/gi,(_,c)=>c.toLowerCase())

Updated Version (82 Bytes) after looking at Neil's regular expression:

s=>s.replace(/(?<=([aeiou]).*?[^\W\d_aeiou])(?![aeiou])/gi,(_,c)=>c.toLowerCase())

var tests = {
  "AN EXAMPLE WITH A LOT UPPERCASE (plus some lowercase)":
    "ANa EXAMaPaLE WITiHi A LOTo UPuPEReCASE (pelusu some lowerecase)",
  "And here comes a **TEST** case with 10% symbols/numbers(#)!":
    "Anada here comese a **TESeTe** case witihi 10% siyimiboloso/numuberese(#)!",
  "This is an example string.":
     "Thisi isi ana examapale seterinigi.",
  "abcdefghijklmnopqrstuvwxyz":
    "abacadefegehijikiliminopoqorosotuvuwuxuyuzu",
  "A pnm bnn":
     "A panama banana",
  "Tell me if you need more test cases!":
     "Telele me ifi you neede more tesete casese!"
};

for ( test in tests )
{
  var result = (s=>s.replace(/(?<=([aeiou]).*?[^\W\d_aeiou])(?![aeiou])/gi,(_,c)=>c.toLowerCase()))(test);
  console.log( result === tests[test], result );
}

\$\endgroup\$
3
\$\begingroup\$

Japt -P, 28 bytes

ó@\ctX ©\VtYÃËè\v ?P=D:D¬qPv

Try it online!

Unpacked & How it works

UóXY{\ctX &&\VtY} mD{Dè\v ?P=D:Dq qPv

UóXY{           }  Split the string between any two chars that don't satisfy...
     \ctX &&\VtY     The first char is a consonant and the second is a non-vowel
mD{                And map...
   Dè\v              If this item is a vowel...
       ?P=D            Assign it to P and return as-is
           :Dq qPv     Otherwise, split the item into chars and join with P lowercased
                       (P starts with "", so beginning consonants are not affected)

-P                 Join with ""

The ó function wins over any kind of regexes.

\$\endgroup\$
2
  • \$\begingroup\$ Nice one, ya beat me :D. \$\endgroup\$ Jun 12, 2018 at 18:00
  • \$\begingroup\$ Nicely done - I gave myself severe brainache with this one! \$\endgroup\$
    – Shaggy
    Jun 13, 2018 at 15:05
2
\$\begingroup\$

JavaScript (Node.js), 146 143 132 127 125 bytes

(a,v="")=>[...a].map((b,i)=>(w=/[aeiou]/i).test(b)&&(v=b)?b:w.test(a[i+1]||b)||!b.match(/[a-z]/i)?b:b+v.toLowerCase()).join``

Try it online!

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

Perl 6,  75 73 71  69 bytes

{({S:i/.*(<[aeiou]>).*<-[\W\d_aeiou]><()><![aeiou]>/$0.lc()/}...*eq*).tail}

Try it

{({S:i{.*(<[aeiou]>).*<-[\W\d_aeiou]><()><![aeiou]>}=$0.lc}...*eq*).tail}

Try it

{({S:i{.*(<[aeiou]>).*<:L-[_aeiou]><()><![aeiou]>}=$0.lc}...*eq*).tail}

Try it

{({S:i{.*(<[aeiou]>).*<:L-[_aeiou]><(<![aeiou]>}=$0.lc}...*eq*).tail}

Try it

Expanded:

{  # bare block lambda with implicit parameter $_

  (
    # generate a sequence

    {  # code block used to generate the values

      S               # substitute (not in-place)
      :i              # :ignorecase
      {

          .*              # start at end of string

          ( <[aeiou]> )   # store the previous vowel in $0

          .*

          <:L - [_aeiou]> # letter other than a vowel

          <(              # ignore everything before this

                          # this is where `$0.lc` gets inserted

          # )>            # ignore everything after this

          <![aeiou]>      # not a vowel (zero width lookahead)

      } = $0.lc       # replace with lowercase of the earlier vowel
    }

    ...    # keep generating until:

    * eq * # there are two equal strings (no changes)

  ).tail   # get the last value
}
\$\endgroup\$
2
\$\begingroup\$

Python 3, 125 bytes

lambda s,v='[^aeiouAEIOU':sub(f'(?<={v}\W\d])(?={v}]|$)',lambda m:sub(f'{v}]','',s[:m.end()])[-1:].lower(),s)
from re import*

Try it online!

Python 3.6 allows us to (ab)use f-strings to reuse our set of vowels (and for four more characters saved, the beginning of an inverted regex character class) cheaply (a f prefix on each string, then {v} as needed, instead of the '+v+' you'd need with concatenation, or the [^aeiouAEIOU you'd insert literally.

The regex that matches no characters, just a position, avoids issues with the non-overlapping matches normal regexes require, and removes the need to backreference any part of the match; all we use the match object for is to get the slice index we use to find the prior vowel.

Partially de-golfed, it would be something like:

import re

def get_last_vowel(string):
    '''
    Returns the lowercase version of the last vowel in a string if
    the string contains any vowels, otherwise, return the empty string
    '''
    try:
        *restvowels, lastvowel = re.sub(r'[^aeiouAEIOU]', '', string)
    except ValueError:
        lastvowel = ''  # No vowels in string
    return lastvowel.lower()

def rememebere_tehe_vowelese(string):
    '''Inserts the lowercased last vowel seen after any consonant not followed by a vowel'''
    return re.sub(r'(?<=[^aeiouAEIOU\W\d])(?=[^aeiouAEIOU]|$)',
                  lambda match: get_last_vowel(string[:match.end()]),
                  string)
\$\endgroup\$
2
\$\begingroup\$

TSQL, 500 bytes

 CREATE TABLE i (i CHAR(999)); INSERT i VALUES ('The rain in Spain stays mainly in the plain')
 DECLARE @w CHAR(999)=(SELECT i FROM i),@r VARCHAR(999)='';WITH d(n,c,i,q)AS(SELECT n,SUBSTRING(@w,n,1),CHARINDEX(SUBSTRING(@w,n,1),'AEIOUaeiou'),CHARINDEX(SUBSTRING(@w,n,1),'BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz')FROM(SELECT DISTINCT number n FROM master..[spt_values]WHERE number BETWEEN 1 AND LEN(@w))D)SELECT @r=@r+f.c+LOWER(COALESCE(CASE WHEN f.q<>0 AND COALESCE(d2.i,0)=0 THEN SUBSTRING(@w,(SELECT MAX(n)FROM d WHERE i<>0 AND n<f.n),1)END,''))FROM d f LEFT JOIN d d2 ON f.n=d2.n-1 SELECT @r

Table i is used for input

\$\endgroup\$
2
  • 2
    \$\begingroup\$ Assuming input to be present in a certain variable is generally not allowed. Can this be adapted to be a function instead? \$\endgroup\$
    – Laikoni
    Jun 7, 2018 at 18:56
  • \$\begingroup\$ @Laikoni solution updated to match given rules \$\endgroup\$
    – Jan Drozen
    Jun 8, 2018 at 7:47
2
\$\begingroup\$

SWI-Prolog, 593 bytes

a(S,D):-atom_chars(S,D).
g(_,[],_,-1).
g(E,[E|_],R,R).
g(E,[_|T],I,R):-N is I+1,g(E,T,N,R).
c(A,E):-g(E,A,0,R),R > -1.
v(X):-a('AEIOUaeiou',X).
c(X):-a('BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz',X).
d([_],_,R,R).
d([H|T],_,I,R):-v(V),c(V,H),!,d(T,H,I,[H|R]).
d([H,N|T],V,I,R):-c(C),c(C,H),v(W),c(W,N),!,d([N|T],V,I,[H|R]).
d([H,N|T],V,I,R):-c(C),c(C,H),v(W),\+c(W,N),string_lower(V,LV),!,d([N|T],V,I,[LV,H|R]).
d([H|T],V,I,R):-!,d(T,V,I,[H|R]).
r([],Z,Z).
r([H|T],Z,A):-r(T,Z,[H|A]).
r(S,D):-r(S,D,[]).
m(X,R):-a(X,O),r(O,P),r([''|P],Q),d(Q,'',I,[]),r(I,J,[]),atomic_list_concat(J,R).

Used only built-in predicates (without regex or list manipulating library).

Usage:

?- m('A pnm bnn').
'A panama banana'
true .
\$\endgroup\$
2
\$\begingroup\$

Haskell, 142 130 bytes

""&
import Data.Char
v=(`elem`"aeiouAEIOU")
s&(x:y:z)|v y=x:s&(y:z)
s&(x:y)|v x=x:[toLower x]&y|isAlpha x=x:s++s&y|1>0=x:s&y
_&x=x

Try it online!

The initial ""& is a partial application of the (&) function defined later on, and is placed so oddly to make TIO count the bytes in ""&, but not count the bytes that, in a full program, would be needed to assign that to any named value.


Less golfed:

import Data.Char (isAlpha, toLower)

vowel :: Char -> Bool
vowel = (`elem`"aeiouAEIOU")

replace :: String -> String
replace = go "" -- start by carrying no extra vowel
  where go _ "" = ""
        -- special case for "anything followed by vowel" so later cases can ignore next character
        go s (x:y:more) | vowel y = x : go s (y:more)
        go s (x:xs) | vowel x = x : go [toLower x] xs -- update the vowel we're carrying
                    | isAlpha x = x : s ++ go s xs -- non-vowel letter not followed by a vowel
                    | otherwise = x : go s xs -- some non-letter junk, just include it and carry on

There really ought to be a way to do this more concisely with a fold instead of recursion, but I couldn't figure it out.

\$\endgroup\$
4
  • \$\begingroup\$ Here is a very hacky way of defining a header such that f does not appear in the body: Try it online! \$\endgroup\$
    – Laikoni
    Jun 8, 2018 at 4:50
  • \$\begingroup\$ There are two unnecessary spaces in v = ( and you can define g as an infix operator. \$\endgroup\$
    – Laikoni
    Jun 8, 2018 at 4:54
  • \$\begingroup\$ Putting the base case g _""="" in last position saves a byte: g _ x=x (two bytes if you switch to infix as Laikoni suggests). \$\endgroup\$
    – nimi
    Jun 8, 2018 at 20:01
  • \$\begingroup\$ Per our conventions you'd need to add parenthesis around ""& to make it an function. \$\endgroup\$
    – Laikoni
    Jun 9, 2018 at 20:24
1
\$\begingroup\$

05AB1E, 34 bytes

vyžMylåil©1V}žPylåžM¹N>èå_Y&&i®«}J

Try it online!


I take that back I can only shave 3 bytes off this monstrosity... I think I could shave the boolean down, but there MUST be 3 cases. 1 for vowels. 1 for consonants. 1 for the case that a digit/symbol exists.


v                                 # For each...
 y                                # Push current element.
  žM                              # Push lower-case vowels (aeiou).
    ylå                           # Lower-case current element is vowel?
       i©1V}                      # If so, put it in register, set Y to 1.
            žP                    # Push lower-case consonants (b...z)
              ylå                 # Is current char a consonant?
                 žM¹N>èå_         # Push vowels again, is input[N+1] NOT a vowel? 
                         Y        # Did we ever set Y as 1?
                          &&      # All 3 previous conditions true?
                            i®«}  # Concat the current vowel to the current char.
                                J # Join the whole stack.
                                  # '}' isn't needed here, b/c it's implied.
                                  # Implicit return.
\$\endgroup\$
0
0
\$\begingroup\$

Powershell, 104 bytes

based on Neil's regular expression.

[regex]::Replace($args,'(?i)(?<=([aeiou]).*?[^\W\d_aeiou])(?![aeiou])',{"$($args.Groups[1])".ToLower()})

save it as get-rememebere.ps1. Script for testing:

$test = @"
AN EXAMPLE WITH A LOT UPPERCASE (plus some lowercase)
And here comes a **TEST** case with 10% symbols/numbers(#)!
This is an example string.
abcdefghijklmnopqrstuvwxyz
A pnm bnn
Tell me if you need more test cases!
"@

$expected = @"
ANa EXAMaPaLE WITiHi A LOTo UPuPEReCASE (pelusu some lowerecase)
Anada here comese a **TESeTe** case witihi 10% siyimiboloso/numuberese(#)!
Thisi isi ana examapale seterinigi.
abacadefegehijikiliminopoqorosotuvuwuxuyuzu
A panama banana
Telele me ifi you neede more tesete casese!
"@

$result = .\get-rememebere.ps1 $test
$result -eq $expected
$result
\$\endgroup\$
1
0
\$\begingroup\$

Red, 276 bytes

func[s][v: charset t:"AEIOUaeiou"c: charset 
u:"BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz"b:
parse s[collect[any keep[thru c opt v]keep thru end]]p:""foreach
c b[either find t e: last c: to-string c[p: e][parse c[any[copy p v
| skip]]if find u e[append c lowercase p]]prin c]]

Try it online!

Readable:

f: func [ s ] [
   v: charset t: "AEIOUaeiou"
   c: charset u: "BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz"
   b: parse s [
       collect [ any keep [ thru c opt v ]
       keep thru end ]
   ]
   p: "" 
   foreach c b [
       e: last c: to-string c
       either find t e [ p: e ][
           parse c [ any [ copy p v | skip ] ]
           if find u e [ append c lowercase p ]
       ]
       prin c
   ]
]
\$\endgroup\$
0
\$\begingroup\$

Yabasic, 180 bytes

A full program which takes input from STDIN and outputs to STDOUT

Line Input""s$
x$="AEIOUaeiou"
For i=1To Len(s$)
c$=Mid$(s$,i,1)
?c$;
If InStr(x$,c$)Then
v$=c$
Else
a=Asc(Upper$(c$))
If a>64And a<91And!InStr(x$,Mid$(s$,i+1,1))Then?v$;Fi
Fi
Next

Try it online!

\$\endgroup\$

Your Answer

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

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