Re: Factor

Factor: the language, the theory, and the practice.

Reverse Vowels

Monday, February 12, 2024

Our task today is to “reverse vowels of a string”. This sounds like (and probably is) a coding interview question as well as a LeetCode problem, a Codewars kata, and the second task in the Perl Weekly Challenge #254.

If you don’t want spoilers, maybe stop reading here!


We are going to use Factor to solve this problem as well as a variant that is a bit more challenging.

Let’s Reverse The Vowels

One of the benefits of the monorepo approach that we have taken to building the extensive Factor standard library is developing higher-level words that solve specific kind of tasks.

One of those is arg-where – currently in the miscellaneous sequences.extras vocabulary – which we can use to find all the indices in a string that contain a vowel?:

IN: scratchpad "hello" [ vowel? ] arg-where .
V{ 1 4 }

We’ll want to group the beginning and ending indices, ignoring the middle index if the number of indices is odd since it would not change:

: split-indices ( indices -- head tail )
    dup length 2/ [ head-slice ] [ tail-slice* ] 2bi ;

We can then build a word to reverse specified indices:

: reverse-indices ( str indices -- str )
    split-indices <reversed> [ pick exchange ] 2each ;

And then use it to reverse the vowels:

: reverse-vowels ( str -- str )
    dup >lower [ vowel? ] arg-where reverse-indices ;

And see how it works:

IN: scratchpad "factor" reverse-vowels .
"foctar"

IN: scratchpad "concatenative" reverse-vowels .
"cencitanetavo"

Pretty cool!

Let’s Reverse The Vowels, Maintain The Case

A somewhat more challenging task is to reverse the vowels, and to swap their letter case.

Let’s start by building a word to swap the case of two letters:

: swap-case ( a b -- a' b' )
    2dup [ letter? ] bi@ 2array {
        { { t f } [ [ ch>upper ] [ ch>lower ] bi* ] }
        { { f t } [ [ ch>lower ] [ ch>upper ] bi* ] }
        [ drop ]
    } case ;

And then another word to exchange two indices, but also swap their case:

: exchange-case ( i j seq -- )
    [ '[ _ nth ] bi@ swap-case ]
    [ '[ _ set-nth ] bi@ ] 3bi ; inline

A word to reverse the indices, but also swap their case:

: reverse-indices-case ( str indices -- str )
    split-indices <reversed> [ pick exchange-case ] 2each ;

And, finally, a word to reverse the vowels, but also swap their case:

: reverse-vowels-case ( str -- str )
    dup >lower [ vowel? ] arg-where reverse-indices-case ;

And then see how it works:

IN: scratchpad "FActor" reverse-vowels-case .
"FOctar"

A pretty fun problem!