don't worry, it's probably fine

Solving Weekly Challenge 254 with Uiua

31 Jan 2024

uiua tdd

I’ve really been enjoying uiua as my first foray into array and stack languages. I’ll be tackling the Perl Weekly Challenges using uiua to strengthen my own understanding but also to be a resource to people new to the language like myself.

I’ll be cracking on with week 254 which has two puzzles: Three Power and Reverse Vowels.

Three Power

This problem exercises your knowledge of maths as well as coding.

You are given a positive integer, $n. Write a script to return true if the given integer is a power of three otherwise return false.

Let’s start by making some assumptions for our solution. The examples given are 27 (which is 3^3), 0 (which is 0^3), and 6 (which is not a power of 3).

It’s unclear from the examples whether it would count 64 as a power of three; yes, it is 4^3 but no, it is also 2^6. We can start by assuming the question means “a prime number, one, or zero, to the power of 3” and iterate from there.

Uiua has recently added an inverse to reduce-multiply () using un (°). Where reduce-multiply would take an array of numbers and return their product, the inverse of this operation takes an integer and returns an array of its prime factors.

Under our assumption above, this problem reduces to checking whether the array of prime factors (a) has length 3 and (b) has all equal entries. This can be done easily using classify () which assigns a classified index to each entry in an array. The returned value for an array of identical values would be an array of the same length but filled with 0s.

There are two edge cases to tackle. Neither 0 nor 1 have prime factorisations so we can test if the input is either of those values using member ().

Finally, we have our solution given our assumption, and the corresponding tests. (uiua pad link)

ThreePower ← (≍⊚3⊛°/×|1)∊:0_1.

---
⍤"0 is a power of 3" ThreePower 0
⍤"1 is a power of 3" ThreePower 1
⍤"8 is a power of 3" ThreePower 8
⍤"27 is a power of 3" ThreePower 27

⍤"9 is not a power of 3" ¬ ThreePower 9
⍤"54 is not a power of 3" ¬ ThreePower 54
⍤"1000 is not a power of 3" ¬ ThreePower 1000
---

What if our assumption was incorrect, and 64 is a power of 3 under our logic? Let’s refine the solution above. We’re now looking for an array of prime factors whose unique entries are only in groups of size divisible by 3.

We can do this using a combination of classify and group () to “count” how many of each prime factor there are. Once we know the counts of each prime factor, we can test to see how many of them are divisible by 3.

The way we test for an array being all 0s or empty is to reduce-sum and test if it’s equal to 0. This lets us refactor out the check for whether the input is 1 because while °/× 0 is an error, °/× 1 is an empty list.

Our second attempt looks like this. (uiua pad link)

ThreePower ← (=0/+◿3⊕⧻.⊛°/×|1)=0.

---
⍤"0 is a power of 3" ThreePower 0
⍤"1 is a power of 3" ThreePower 1
⍤"8 is a power of 3" ThreePower 8
⍤"27 is a power of 3" ThreePower 27
⍤"64 is a power of 3" ThreePower 64
⍤"1000 is a power of 3" ThreePower 64

⍤"9 is not a power of 3" ¬ ThreePower 9
⍤"54 is not a power of 3" ¬ ThreePower 54
---

Nice! Let’s move onto the second challenge.

Reverse Vowels

We’re doing some string manipulation in this challenge.

You are given a string, $s. Write a script to reverse all the vowels (a, e, i, o, u) in the given string.

I’m a big advocate of test-driven development (TDD) and part of that approach is incrementally doing the simplest thing that will solve the problem first, then refactoring and improving.

What’s the easiest solution for this? If we have an array of a-zA-Z and an array of a-zA-Z but with the vowels moved then the translation becomes finding the index of the letter in the original array () and picking the corresponding entry from the modified array ().

Let’s see how our first attempt looks (uiua pad link)

ReverseVowels ← (
  ⊙(
    "ubcdofghijklmnepqrstavwxyzUBCDOFGHIJKLMNEPQRSTAVWXYZ"
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  )
  ⊏⊗
)

---
⍤.≍ "Aiau" ReverseVowels "Uiua"
⍤.≍ "Ruka" ReverseVowels "Raku"
⍤.≍ "Porl" ReverseVowels "Perl"
⍤.≍ "Jaliu" ReverseVowels "Julia"
---

It works but it’s super boring. Let’s have a bit more fun. Can we avoid hard-coding the long strings? Yes!

We can construct the lookup arrays programmatically with the following process:

  1. Create a range of A-Z using range ()
  2. Duplicate it, and apply the vowel transforms using a fold() over the original array and transformations.
  3. Duplicate, lowercase, and join the original and transformed arrays using both () and join ()

Let’s see how it looks (uiua pad link)

ReverseVowels ← (
  ⊙(
    .+@\0+65⇡26
    ∧(⍜⊡+⊙:°⊟)[0_20 4_10 14_¯10 20_¯20]
    ∩(⊂+32.)
  )
  ⊏⊗
)

---
⍤.≍ "Aiau" ReverseVowels "Uiua"
⍤.≍ "Ruka" ReverseVowels "Raku"
⍤.≍ "Porl" ReverseVowels "Perl"
⍤.≍ "Jaliu" ReverseVowels "Julia"
---

Isn’t that easier to understand? Not really. The original implementation was simpler even if it was less “clever”. We are balancing complexity and readability all the time, depending on our audience.

All in all, these were a fun couple of puzzles! Uiua has some built in capabilities that make it an absolute beast for solving problems in a concise way.