;; CS101 Lecture Notes, Spring 2013 ;; Lecture 20 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; BASICS OF TEXT PROCESSING - CHARACTERS AND STRINGS ;; ;; Every string is actually an ordered sequence of characters ;; ;; Each character literal is preceded by #\, so #\b is b, #\A is A, etc. ;; ;; Primitive functions that consume characters: ;; (char=? char char) -> boolean; checks if 2 characters are the same ;; (char-ci=? char char) -> boolean; case-insensitive equality check ;; (char-alphabetic? char) -> boolean ;; (char-downcase char) -> char; converts char to lower-case ;; (char-upcase char) -> char; converts char to upper-case ;; (char-lower-case? char) -> boolean ;; (char-upper-case? char) -> boolean ;;Use the char-ci=? function to write a vowel-tester: ;; Contract: (vowel? char) -> boolean ;; Header: (define vowel? (lambda (ch) ... )) ;; Purpose: Is the given char a vowel? ;;; Pre-function tests: (FILL IN THE RETURN VALUE) ;(check-expect (vowel? #\J) #f) ;(check-expect (vowel? #\i) #t) ;(check-expect (vowel? #\A) #t) ;(check-expect (vowel? #\1) #f) ; ;;: Function definition: ;(define vowel? ; (lambda (ch) ; (cond ; ; char-ci=? is case-insensitive check for equality of chars ; [(char-ci=? ch #\A) #t] ; [(char-ci=? ch #\e) #t] ; [(char-ci=? ch #\i) #t] ; [(char-ci=? ch #\o) #t] ; [(char-ci=? ch #\U) #t] ; [else #f]))) ;; STOP! DON'T LOOK AHEAD UNTIL AFTER YOU WRITE THE FUNCTION ;; ABOVE. ; ; Another way to write a vowel checker, using built-in functions ; explode, string, and member? ; ; (explode string) -> list of 1 character strings ; (string char char ...) -> string ; (member? X (listof X)) -> boolean ; ; Often, it is easier to convert a string into a list before ; processing it. To convert a list of 1 character strings back ; into a string, use ; ; (implode list-of-1strings) -> string ; ; ;; Contract: (vowel? char) -> boolean ;; Header: (define vowel? (lambda (ch) ... )) ;; Purpose: Is the given char a vowel? ;; Pre-function tests: (FILL IN THE RETURN VALUE) (check-expect (vowel? #\J) #f) (check-expect (vowel? #\i) #t) (check-expect (vowel? #\A) #t) (check-expect (vowel? #\1) #f) ;;; Function: (define vowel? (lambda (ch) (local ; create a list of 1-letter strings out of a string of all vowels [(define VOWELS (explode "aeiouAEIOU"))] ; check if ch, converted to a string, is a member of VOWELS (member? (string ch) VOWELS)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PALINDROME CHECKER: EXAMPLE OF STRING PROCESSING ;; ;; The functions used in this section are: ;; ;; * Getting one character out of a string: ;; string-ref: string number -> char (at position indexed by number) ;; * Converting one or more characters to strings and string-appending them: ;; string: char ... -> string ;; * Returning part of a string: ;; substring: string num1 num2 -> returns the string between indices num1 ;; and (num2 - 1) in string ;; string-append: string string ... -> string ;; string-length: string -> number ;; ;; Strings are really "0-based" indexed collections of characters. Each ;; character in the string has an index number and the index numbers of ;; a particular string have indices ranging from 0 to (sub1 (string-length str)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Given a string, determine whether it reads the same forward and ;; backwards (i.e., whether it is a palindrome.) Use explicit ;; recursion in this version and then write a version using list ;; functions. ;; Contract: (palindrome? string) -> boolean ;; Header: (define palindrome? (lambda (str) ;; Purpose: Is the given lowercase alpha string a palindrome? ;; Pre-function tests: (FILL IN THE RETURN VALUE) (check-expect (palindrome? "liver") #f) (check-expect (palindrome? "rotator") #t) (check-expect (palindrome? "maddam") #t) ;; Function definition (define palindrome? (lambda (str) ; turn str into list of 1-char strings (explode), reverse that list ; (reverse), implode the reversed list and compare it to str. If ; they are the same, the string is a palindrome. (string=? str (implode (reverse (explode str)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Write a version of a palindrome checker that can consume entire phrases ;; and that determines whether the letters in the phrase form a palindrome. ;; Contract: (pal? string) -> boolean ;; Header: (define pal? (lambda (phrase) ... )) ;; Purpose: Is the given string a palindrome? ;; Pre-function tests: (FILL IN THE RETURN VALUE) (check-expect (pal? "A Toyota! Race fast, safe car! A Toyota!") #t) (check-expect (pal? "Golf? No sir, prefer prison-flog.") #t) (check-expect (pal? "Lonely Tylenol.") #t) (check-expect (pal? "Stack cats") #t) (check-expect (pal? "Dennis, Nell, Edna, Leon, Nedra, Anita, Rolf, Nora, Alice, Carol, Leo, Jane, Reed, Dena, Dale, Basil, Rae, Penny, Lana, Dave, Denny, Lena, Ida, Bernadette, Ben, Ray, Lila, Nina, Jo, Ira, Mara, Sara, Mario, Jan, Ina, Lily, Arne, Bette, Dan, Reba, Diane, Lynn, Ed, Eva, Dana, Lynne, Pearl, Isabel, Ada, Ned, Dee, Rena, Joel, Lora, Cecil, Aaron, Flora, Tina, Arden, Noel, and Ellen sinned.") #t) (check-expect (pal? "This is a palindrome?") #f) ;; Function definition: (define pal? (lambda (phrase) (palindrome? (to-lowercase (remove-nonletters phrase))))) ;; What helper functions might we need to make this function work? ; Contract: (to-lowercase string) -> string ; Header : (define to-lowercase (lambda (str) ... )) ; Purpose: Convert str to all lowercase (check-expect (to-lowercase "ABCDEF") "abcdef") (check-expect (to-lowercase "abcdef") "abcdef") (check-expect (to-lowercase "") "") (define to-lowercase (lambda (str) ; Turn str into list of 1-char strings (explode), map an unnamed lambda ; that returns the 1-char string in its lowercase form, and implode list ; back into a string (implode (map (lambda (st) (string (char-downcase (string-ref st 0)))) (explode str))))) ;; Contract: (remove-nonletters string) -> string ;; Header: (define remove-nonletters (lambda (str) .. )) ;; Purpose: return string consisting of only the letters in str ;; Pre-function tests: (check-expect (remove-nonletters "How many?") "Howmany") (check-expect (remove-nonletters "Stack cats.") "Stackcats") (check-expect (remove-nonletters "") "") ;; Function definition: (define remove-nonletters (lambda (str) (local ;; define variable to hold list of 1-char strings [(define LETRS (explode str))] ;; filter out all the non-alpha characters and implode them ;; to form a lowercase string of letters. (implode (filter (lambda (ltr) (char-alphabetic? (string-ref ltr 0))) LETRS))))) ; ; Another way to write a palindrome checker is to "step over", or ignore, ; all non-alphabetic characters by making extra recursive calls. ; ;; Contract: (phrase-pal? string) -> boolean ;; Header: (define phrase-pal? (lambda (phrase) ... )) ;; Purpose: Are the letters in the given phrase a palindrome? ;; Pre-function tests: (check-expect (phrase-pal? "Able was I ere I saw Elba.") #t) (check-expect (phrase-pal? "Hello there, how are you?") #f) (check-expect (phrase-pal? "A Santa dog lived as a devil God at NASA.") #t) (check-expect (phrase-pal? "A man, a plan, a canal -- Panama!!") #t) ;; Function definition: (define phrase-pal? (lambda (phrase) (local [(define helper (lambda (pos1 pos2) (cond ;; When position pointers are equal (in odd-length palindrome ;; case) or when pos1 > pos2 (in even-length palindrome case) ;; end the recursion [(<= pos2 pos1) #t] ;; If char at pos1 is not a letter, bypass it [(not (char-alphabetic? (string-ref phrase pos1))) (helper (add1 pos1) pos2)] ;; If char at pos2 is not a letter, bypass it [(not (char-alphabetic? (string-ref phrase pos2))) (helper pos1 (sub1 pos2))] ;; If chars at pos1 and pos2 are letters, compare them for ;; equality. [(char-ci=? (string-ref phrase pos1) (string-ref phrase pos2)) (helper (add1 pos1) (sub1 pos2))] ;; If execution gets to this clause, chars at pos1 and pos2 are ;; not equal, return false. [else #f])))] ;; start pos1 at 0 (beginning of string) and pos2 at length of phrase ;; minus 1 (the last letter in phrase (helper 0 (sub1 (string-length phrase)))))) ; ; A case study in string processing and top-down programming: ; ; Anyone ever heard of Pig Latin? Pig Latin is an invented language formed by ; transforming each English word according to the following rules: ; ; 1. If the word begins with a consonant, you form its Pig Latin equivalent ; by moving the initial consonant string (all letters up to the first ; vowel) from the beginning of the word to the end and then adding the ; suffix "ay". ; ; 2. If the word begins with a vowel or has no vowels, you simply add ; the suffix "way". ; ; ; Pig-Latin algorithm for single words (with no whitespace): ; ; 1. Prompt for and read string (str) from user. ; ; 2. If first character is a vowel, or if there are no vowels, return the ; string ending with suffix "way". ; ; 3. If the first character is a consonant, ; ; a) Find the position of the first vowel. ; ; b) Keep the substrings up to the first vowel and after the first vowel ; in local variables. ; ; c) Return the string, starting at the first vowel position, followed ; by the consonant substring, followed by "ay". ; ;; Program to convert single word into its pig-latin equivalent. ;; We can make use of one of the vowel? programs we wrote above: ;(define VOWELS (explode "AEIOUaeiou")) ;; WHAT OTHER HELPER FUNCTIONS WILL WE NEED? ;; Contract: (first-vowel string) -> number ;; Header: (define first-vowel (lambda (str) ... )) ;; Purpose: return position of first vowel in str or -1 if there ;; is no vowel ;; Pre-function tests: (check-expect (first-vowel "scare") 2) (check-expect (first-vowel "rhythm") -1) ;; could be 2 if y is vowel (check-expect (first-vowel "mango") 1) (check-expect (first-vowel "appetizer") 0) ;; Function definition: (define first-vowel (lambda (str) (local ;; Define a local helper function to do recursion based on ;; the current position in str [(define fvhelp (lambda (pos) (cond ;; Base case 1: if pos is at the last position i str, no vowel found, ;; so return -1 [(= pos (string-length str)) -1] ;; Base case 2: pos is at the first vowel in str, return pos [(vowel? (string-ref str pos)) pos] ;; Recursive case: keep looking through str [else (fvhelp (add1 pos))])))] ;; start helper function at position 0 (fvhelp 0)))) ;; Contract: (vowel-to-end string number) -> string ;; Header: (define vowel-to-end (lambda (str num) ... )) ;; Purpose: return substring from position num to the end of ;; str ;; ;; Pre-function tests: (check-expect (vowel-to-end "self" 1) "elf") (check-expect (vowel-to-end "scrabble" 3) "abble") (check-expect (vowel-to-end "throughput" 3) "oughput") ;; Function definition: (define vowel-to-end (lambda (str num) (substring str num))) ;; Contract: (beg-to-vowel string number) -> string ;; Header: (define beg-to-vowel (lambda (str num) ... )) ;; Purpose: return substring from 0 to first vowel ;; ;; Pre-function tests: (check-expect (beg-to-vowel "towel" 1) "t") (check-expect (beg-to-vowel "fresh" 2) "fr") (check-expect (beg-to-vowel "scram" 3) "scr") ;; Function definition: (define beg-to-vowel (lambda (str num) (substring str 0 num))) ;; Contract: (pig-latin-translator) -> void; side-effect printing ;; Header: (define pig-latin-translator (lambda () ... )) ;; Purpose: Prompt for and read a string and produce the pig-latin ;; version ;; No pre-function tests possible due to side effect printing and ;; reading. (define pig-latin-translator (lambda () (begin ;; prompt user for input (printf "Please enter a word and I'll give you its Pig-Latin equivalent~%") (local ;; read input from user, storing it in word after converting it to a string [(define word (symbol->string (read))) ;; fv will hold the position of the first vowel in word (define fv (first-vowel word))] (cond ;; if the first vowel is at position 0 or if there are no vowels, ;; append "way" [(<= fv 0) (printf "The result is ~a.~%" (string-append word "way"))] ;; otherwise, append the sections of word together and append "ay" [else (printf "The result is ~a.~%" (string-append (vowel-to-end word fv) (beg-to-vowel word fv) "ay"))])))))