3
\$\begingroup\$

I have an imperative programming background and I've decided to study functional programming by applying it to problems found on sites such as Project Euler and Rosalind. My language of choice is Scheme (I use CHICKEN). As can be seen, I'm only starting out.

This is about the 3rd problem found on Rosalind, that goes like this:

In DNA strings, symbols 'A' and 'T' are complements of each other, as are 'C' and 'G'.

The reverse complement of a DNA string s is the string sc formed by reversing the symbols of s, then taking the complement of each symbol (e.g., the reverse complement of "GTCA" is "TGAC").

Given: A DNA string s of length at most 1000 bp.

Return: The reverse complement sc of s.

Here's my code, which solves the problem:

(define (complement-of char)
  (cond
    ((eq? char #\A)
     #\T)
    ((eq? char #\T)
     #\A)
    ((eq? char #\C)
     #\G)
    ((eq? char #\G)
     #\C)
    (else
      char)))

(define (apply-complements dna-string)
  (reverse-list->string 
    (map complement-of 
     (string->list dna-string))))

(define (solve-problem infile-name outfile-name)
  (let ((outfile-port (open-output-file outfile-name))
        (infile-port (open-input-file infile-name)))
    (begin
      (write-line 
         (apply-complements (read-line infile-port)) outfile-port)
      (close-output-port outfile-port))))

Is there a nicer way to write complement-of? Perhaps something like a dictionary, or would that be too much work in this particular case?

Is solve-problem there appropriate from a style POV, or is this more appropriate?

(define (solve-problem infile-name outfile-name)
  (let ((outfile-port (open-output-file outfile-name))
        (infile-port (open-input-file infile-name)))
    (begin
      (write-line 
         (apply-complements 
           (read-line infile-port)) 
         outfile-port)
      (close-output-port outfile-port))))

Also, is there a way to avoid using begin and still make things look readable? The only idea that comes to mind is extracting a function and through recursion decide whether or not it writes to the file, or closes it. But that feels hardly intuitive.

Finally, I'd like to hear of any other issues my code has.

\$\endgroup\$
3
+50
\$\begingroup\$

complement-of

The nicer way to write complement-of is with case instead of cond:

(case char
  ((#\A) #\T)
  ((#\T) #\A)
  ((#\C) #\G)
  ((#\G) #\C)
  (else char))

Function names generally shouldn't end in -of. (That's a way to pronounce function call, but it's not part of the name.) complement is enough. (Although some Schemes have a predefined function by that name, and it would be nice to not collide with it.)

Beware of eq? — it's not reliable on characters. (It's the equivalent of Java's == on Character). Use eqv? instead unless you know what you're doing. (case uses eqv? internally.)

apply-complements

You can simplify apply-complements by using one of Chicken's libraries: (use (srfi 13)) to get string-map, which is like map but saves you having to convert to a list. That plus string-reverse makes apply-complements easy.

...but I wouldn't call it apply-complements, because that sounds like a reference to the apply function. It returns the reverse complement of its argument, so that should be its name: reverse-complement.

If you want to make it faster, try the imperative approach: allocate a string with make-string, loop with do and string-set!. (It's useful to know how to write imperative code, even in functional languages.)

solve-problem

solve-problem has a very ordinary bug: you forgot to close the input port. :) Fortunately, there are functions that do this automatically so you don't have to remember: call-with-input-file and call-with-output-file. They're also safer, because they close the ports on nonlocal exits like exceptions.

The body of a let is an implicit begin, so the begin in solve-problem is not needed.

Formatting: when a function call is split across multiple lines, the arguments are usually aligned, like this:

(write-line (apply-complements (read-line infile-port))
            outfile-port)

solve-problem has a vague name. How about a description of what it does, such as reverse-complement-file? (Names are the best comments.)

...however, reverse-complement is only one of many operations you might want to apply to a file this way. I'd probably write it as a general-purpose transform-file that takes the operation as a parameter: (transform-file reverse-complement src dest). Or I'd write it as two separate operations for reading and writing, since they're often useful separately. (I'd also use read-string and write-string instead of read-line and write-line, so they'd work on files of more than one line.)

\$\endgroup\$
  • \$\begingroup\$ Thanks! Looks like a complete rundown to me :) Good point at the "of" bit in the function call. Since I read & process maths in Romanian it never occurred to me before but I'll definitely keep it in mind from now on. \$\endgroup\$ – user29120 May 13 '14 at 13:54
1
\$\begingroup\$

You could use a dictionary/hash but for 4 choices it is overkill.

In my opinion, apply-compliments is just the map over compliment-of. I would call the core function reverse-compliment. As an aside, if I read the problem description correctly you reverse the string and then apply the compliments but you appear to be complimenting and then reversing. Functionally it does not matter of course.

My scheme is a little rusty to be honest, but I think since you are in a let form you do not need the begin.

I would have laid out solve-problem the same way.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy