Thursday, 6 October 2011

Exercise 149: Design a BSL program that simulates the Unix command wc

Exercise 149: Design a BSL program that simulates the Unix command wc.

The purpose of the command is to count the number of characters (1Strings),
words, and lines in a given file. That is, the command consumes the name of
a file and produces a value that consists of three numbers.


(require racket/base)
(require 2htdp/batch-io)


First, create a function to count the chars 


; calculates the number of chars in a string
; Spaces count as a character
; String->Number
(define (count-chars string)
  (count-chars-helper (explode string) 0))


; List-of-Characters->Number
(define (count-chars-helper loc count)
  (cond ((empty? loc) count)
        ((cons? loc) (count-chars-helper 
                      (rest loc) 
                      (add1 count)))))


(check-expect (count-chars "") 0)
(check-expect (count-chars "This is a string") 16)


Second stage - create a function to count the words

This part is mildy tricky - there are three cases we need to deal with:
  1.  if llos is currently a string, its just a   string and we can recursively iterate on it til the end simply
  2. it's an empty list - ie, a blank line. We need to just skip this  item and move on. If we try and count it, we'll get #void and it will make add1 very unhappy
  3.  it's a populated list - ie, one or more populated lines.  In this case we need to call count-words-helper on it   twice - once to get the count for the first line, and again to get the count for all the remaining lines. The result of the  first count feeds into the second call as we don't know how to    maintain variable state yet... I found this part very difficult    to grok



; calculates the number of words in a list of lists
; of strings
; List-Of-Lists-Of-String->Number
(define (count-words llos)
  (count-words-helper llos 0))


; Does the actual work of getting the count
; - llos -> empty - returns the count
; - (rest llos) is a string - count it, and recurse
; - (rest llos) is a cons, we're on a new line, so just recurse
; List-Of-Lists-Of-String->Number
(define (count-words-helper llos count)


  (cond ((empty? llos) count)
        ((cons? llos) 
  
       (cond ((string? (first llos)) 
              (count-words-helper (rest llos) (add1 count)))
             ((empty? (first llos)) 
              (count-words-helper (rest llos)  count))
             ((cons? (first llos))
              (count-words-helper 
               (first llos) 
               (count-words-helper (rest llos) count)))))))
     


Some tests to make sure we didn't mess that up!


; basic case - an empty list
(check-expect (count-words empty) 0)
; a list of strings
(check-expect (count-words 
               (list "This" "is" "a" "string")) 
              4)
; a list of lists of strings
(check-expect (count-words (list 
                            (list "line" "one") 
                            (list "line" "two")))
              4)
; make sure we handle new lines (empty lists) okay
(check-expect (count-words (list (list "line" "one") 
                                 empty (list "line" "two"))) 
              4)




Finally - last method - count the lines.


; calculates the number of lines in list of lists of strings
; this method is very straight forward - just counting the number of 
; items present in our list
; basically just counting the lists
; List-of-Strings->Number
(define (count-lines los)
  (count-lines-helper los 0))


(define (count-lines-helper los count)  
  (cond ((empty? los) count)
        ((cons? los) (count-lines-helper (rest los) 
                                         (add1 count)))))


  
(check-expect (count-lines empty) 0)
(check-expect (count-lines (list "This is a string")) 
              1)
(check-expect (count-lines (list (list "line one") 
                                 (list "line two"))) 
              2)
(check-expect (count-lines (list (list "line one")
                                 empty 
                                 (list "line two")))
              3)


; finally, wrap it all up with a function that
; calls all 3 of our new methods and produces a list
; character, word and line counts
; String->List-of-Numbers
(define (wc filename)
  (list
   (count-chars (read-file filename))
   (count-words (read-words/line filename))
   (count-lines (read-lines filename))
  ))

No comments:

Post a Comment