One problem five solutions — DNA to RNA

Kevin Da Silva
4 min readFeb 2, 2023

--

Hi everyone, this is the first episode of the year in this series where we select one problem and see five different solutions for them written in different programming languages.

I was looking for a cool problem to showcase here, then I received an email from exercism featuring the functional February to promote functional programming languages, and I started looking for the problems in the Elixir track, and I found the DNA to RNA problem:

Given a DNA strand, return its RNA complement (per RNA transcription).
Both DNA and RNA strands are a sequence of nucleotides.
The four nucleotides found in DNA are adenine (A), cytosine (C), guanine (G) and thymine (T).
The four nucleotides found in RNA are adenine (A), cytosine (C), guanine (G) and uracil (U).
Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement:

G -> C
C -> G
T -> A
A -> U

For a simple problem that requires a map from one value to another, let’s check the implemented solutions.

Elixir

Due to this problem being found in the Elixir track, I will start with it

defmodule RnaTranscription do
def to_rna(dna) do
to_string(dna)
|> String.split("", trim: true)
|> Enum.map(fn "G" -> "C"
"C" -> "G"
"T" -> "A"
"A" -> "U"
end)
|> Enum.join("")
|> to_charlist()
end
end

I really loved how the problem was expressed into a sequence o pipes, first, the function receives a charlist in dna, then we turn the charlist to a string, and with a string in our hands we split it, value by the value of the sequence, and after turning it into a list we map it with a function that checks:

if the value received is a G return C
if the value received is a C return G
if the value received is a T return A
if the value received is an A return U

After mapping each value in the list we join it to turn the list into a string, after it we just have to turn the string into a charlist again

Haskell

The Haskell solution is by far the most elegant and beautiful solution in this article, it was very simple, concise, and precise in tackling the problem


toRna [] = ""
toRna ('G':xs) = 'C':toRna xs
toRna ('C':xs) = 'G':toRna xs
toRna ('T':xs) = 'A':toRna xs
toRna ('A':xs) = 'U':toRna xs

In Haskell strings are a list of chars so if we receive an empty list we return an empty string, if the list is populated we take the first value and the remaining of the list in the xs variable, then we just have to pattern match the value to check if it is a G, C, T or A, once pattern matched we just have to append the converted value to the head of the list and recursively call toRna with the remaining of the list.

Julia

The first Julia solution for this problem is a solution Im not proud of:

function sequenceencoder(x)
if x == "G"
"C"
elseif x == "C"
"G"
elseif x == "T"
"A"
elseif x == "A"
"U"
end
end

function torna(dna)
splitedSequence = collect(eachsplit(dna, ""))
if first(splitedSequence) == ""
return ""
end
encodedseq = map(sequenceencoder, splitedSequence)
join(encodedseq)
end

I wasn't happy with this solution and was gonna ask for help to improve it, but when I was doing the same solution in Javascript I got really inspired by it and made a similar approach to the solution in Julia, using a Dict:

function torna(dna)
encoder = Dict(
"G"=>"C",
"C"=>"G",
"T"=>"A",
"A"=>"U"
)

splitedSequence = collect(eachsplit(dna, ""))
encodedseq = map(x -> get(encoder, "$x", ""), splitedSequence)
join(encodedseq)
end

Way more simple and easy to read and maintain

Javascript

As I mentioned in the Julia solution, this solution will also have a similar approach, but I have to say that I got pretty happy with the final result in Javascript, and I was able to achieve the solution with just one Object and function chaining.

const toRna = dna => {
const encoder = {
"G": "C",
"C": "G",
"T": "A",
"A": "U"
}
return dna.split("")
.map(x => encoder[x])
.reduce((acc, x) => `${acc}${x}`, "")
}

Also notice that in the Javascript solution, we used a reduce instead of a join function, where we have to concatenate the accumulator(a string) with our current element in the sequence.

Vlang

The last solution for this article is the vlang solution:

fn to_rna(dna string) string {
runes := dna.runes()
return runes.map(fn (x rune) string {
return match x {
`G` { "C" }
`C` { "G" }
`T` { "A" }
`A` { "U" }
else { "" }
}
}).join("")
}

Its a well looking solution, where we turn our dna string into a list of chars(in vlang its called runes), after it we just map the list and validate it with a match similar to the pattern matching in the Elixir and Haskell solutions once we are done we just turn a list of strings into one single string with the join function

Also notice that when we match we are not matching for a string instead we are matching a rune, and when matched we return a string to be able to join it later.

And here we are to the end of one more article for this series, I think its cool to compare multiple approaches to a common problem, gives a sense of comprehension and wisdom, but besides I like to do it in general Its not very easy to find problems to solve in advance, so if you have suggestions of problems you would like to showcase here feel free to comment.

Thank you again for reading and for further detail on the solutions here is the gist I made containing the solutions, test cases and a description of the problem.

--

--

Kevin Da Silva

I'm a back-end developer, functional programming lover, fascinated by computer science and languages. From the south part of Brazil to the world