Hi there, I want to use hash tables in newLisp,
Take for example this code (Python challenge ;)
;* http://www.pythonchallenge.com/pc/def/map.html
(setq input
(string
"g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp.n"
"bmgle gr gl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle.n"
"sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."))
; Create table for decryption
(for (k 0 25)
(setq key (char (+ k (char "a"))))
(setq value (char (+ (% (+ k 2) 26) (char "a"))))
(context 'dic key value)
;;(println key " - " value)
)
(define (decrypt input table)
(setq len (length input))
(setq output "")
(for (k 0 (- len 1))
(setq key (char (char input k)))
(if (context? table key)
(setq output (string output (context table key)))
(setq output (string output key))
)
)
(setq return output)
)
(println (decrypt input dic))
(println (decrypt "map" dic))
This way I decoded the string, using a hash table for lookup, we have "context?" for checking existence, that's nice, but what about length or foreach operations?, I guess I'm missing something... :(
I have read the docs about the "context" thing, by the way.
Is not this a *weird* way to add hashes to the language? I read some posts than says earlier implementations of newLisp didn't have context before.
But I want to believe that I'm still trying to program "the perl, ruby, python way". Please tell me how is the newLip way to do this kind of simple things. Please.
(ex)
Hi _ex_. I don't know what "foreach keys %hash..." is, so I can't help you with contexts and hashes much. There are probably others who know Python here.
But you asked how do others do tasks like this, and this would be my approach:
(map (fn (c)
(if (and (LT c (char "y"))
(>= c (char "a")))
(print (char (+ 2 c)))
(>= c (char "y"))
(print (char (- c 24)))
(LT c (char "a"))
(print (char c))))
(map char (explode input)))
(for some reason I couldn't get the angle brackets to work - replace LT with an opening one)
Actually at first i just did a map to see what was what:
(map (fn (f) (print (char (+ 2 (char f))))) (explode input))
but that revealed that the puntuation hadn't been encrypted, so more logic was needed.
If you want more cryptic puzzles to solve, there's still the Da Vinci Code newLISP puzzle //http://newlisper.blogspot.com ;-(((((
There are two ways to iterate through a hash, which are better called dictionaries or namespaces in newLISP. Even scripting languages using hashes tend to talk about them as dictionaries lately ;)
; make a dictionary
(context 'foo "sdfg" 111)
(context 'foo "yete" 222)
(context 'foo "asdf" 333)
; display all key value pairs in alphabetical order
(dotree (s foo)
(println s "->" (eval s)))
; or an alternative method
(dolist (s (symbols foo))
(println s "->" (eval s)))
; generates the output
foo:asdf->333
foo:sdfg->111
foo:yete->222
Instead of 'context' you also can use 'sym' for creating/testing/modifying symbols. Sometimes its more convenient, it depends on the situation.
Lutz
ps: the docoument http://newlisp.org/DesignPatterns.html has been updated with the previous explanations in chapter 11
Thank you guys.
I'm learning new things with newLisp, what is good, and I'm havng a nice time in the way :). map seems an interesting function and so dotree and dolist.
cormullion:
the idea was to use a dictionary for this problem, but your function helpme to undestand the *map* concept, I refined a little using regex, I loved perl just by its regex support...
(define (decrypt input , output)
(setq output "")
(map
(fn (c)
(if (regex "[a-z]+" (char c))
(print (char (+ (% (+ (- c (char "a")) 2) 26) (char "a"))))
(print (char c))
)
)
(map char (explode input))
)
(setq return output)
)
Lutz: nice to read that docu :)
How about sorting a dictionary? I can't find an example on how to use sort to sort contexts, something like this in perl:
foreach $value (sort {$coins{$a} cmp $coins{$b} } keys %coins)
{
print "$value $coins{$value}<br>";
}
[/code]
Quote from: "_ex_"
How about sorting a dictionary? I can't find an example on how to use sort to sort contexts
:-) I remember asking that question too (//http://www.alh.net/newlisp/phpbb/viewtopic.php?t=892&highlight=sort+hash and I don't think I've got the hang of this area of newLISP yet enough. Somehow I feel I should be able to reverse-sort a dictionary, but I've managed to cope without one so far. But...
As an example, is there a better way to do this:
(context 'english-german "please" "bitte")
(context 'english-german "thanks" "danke schone")
(context 'english-german "excuse me" "entschuldigen sie")
(context 'english-german "beer" "bier")
(context 'english-german "coffee" "kaffee")
(println "English-German dictionary")
(dotree (s english-german)
(println " " s "->" (context english-german (name s))))
; reverse 'sort' to create German-English dictionary...
(dotree (s english-german)
(context 'german-english (context english-german (name s)) s))
(println "German-English dictionary")
(dotree (s german-english)
(println " " s "->" (eval s)))
Which creates a new context with the entries reversed:
English-German dictionary
english-german:beer->bier
english-german:coffee->kaffee
english-german:excuse me->entschuldigen sie
english-german:please->bitte
english-german:thanks->danke schone
German-English dictionary
german-english:bier->english-german:beer
german-english:bitte->english-german:please
german-english:danke schone->english-german:thanks
german-english:entschuldigen sie->english-german:excuse me
german-english:kaffee->english-german:coffee
- as you see, it went a bit wrong here.
I've noticed that sometimes newLISP requires us to 'unlearn' things as well as 'learn' things...
your code for inverting the dictionary is fine, just use 'name' once more when assigning the value:
(dotree (s english-german)
(context 'german-english (context english-german (name s)) (name s)))
(dotree (s german-english)
(println " " s "->" (eval s)))
=>
german-english:bier->beer
german-english:bitte->please
german-english:danke schone->thanks
german-english:entschuldigen sie->excuse me
german-english:kaffee->coffee
Lutz
Ps: 'name' supresses the context part of a symbol when converting to a string.
ouch! this hurts...
All that I was trying to do was sort my map in order to decrypt a message, this map has the form:
map frequency { "char" => freq }
where freq is the number of repetitions of the character "char" in one string:
But the dotree function sorts them (seems) as they were strings.. :(
Consider the next problem: you got an *encrytpted* message and the frequency info of characters in that language in one string from more frequent to less frequent. *decrypt* that message...
This was very easy to do in ruby (and I'm learning it at the side of newLisp):
# Decrypt
text = "Uid nx, aex jcdjipx iu wzux zp, ta wxtpa jtdaws, ai etkx vis."
freqLang = "TEOIARNSHLMYUCWDGPFBVKJ"
len = text.length
frequency = Hash.new
for k in 0..( len - 1 )
c = text[ k, 1 ].upcase
if c =~ /[A-Z]/
if frequency.has_key?( c )
frequency[ c ] = frequency[ c ] + 1;
else
frequency[ c ] = 1;
end
end
end
dic = Hash.new
freqText = ""
index = 0
frequency.sort{ |a,b| b[1]<=>a[1] }.collect{ |a|
#puts( a[0] + " - " + a[1].to_s )
freqText += a[0]
dic[ a[0].upcase ] = freqLang[ index, 1 ]
index += 1
}
puts( freqLang )
puts( freqText )
decrypted = "";
len = text.length;
for k in 0..( len - 1 )
uper = false
c = text[ k, 1 ]
if c =~ /[A-Z]/ then uper = true
else c = c.upcase end
if dic.has_key?( c )
if uper then decrypted += dic[ c ]
else decrypted += dic[ c ].downcase end
else decrypted += c end
end
puts( decrypted )
I don't want to past the perl code here but is equally suscint.
Now when I tryed the newLips version I got stuck...
(setq text
(string "Uid nx, aex jcdjipx iu wzux zp, ta wxtpa jtdaws, ai etkx vis."))
(setq freqLang "TEOIARNSHLMYUCWDGPFBVKJ")
(context 'frequency) ;; Here we are ~creating~ a hash...
(context 'MAIN) ;; THIS IS UGLY!!! but sadly necessary.. guess why?
(context 'inverse)
(context 'MAIN)
(for (k 0 (- (length text) 1))
(setq c (upper-case (char (char text k))))
(if (regex "[A-Z]" c)
(if (context? frequency c)
(context 'frequency c (+ 1 (context 'frequency c)))
(context 'frequency c 1)
)
)
)
(dotree (s 'frequency)
(println (name s) " - " (eval s))
(context 'inverse (string (eval s)) (name s))
)
(dotree (s 'inverse)
(println s)
)
Lutz you are a kind person, and newLips is REALLY cool but...
newLisp is using context for two totally different kind of things
- context as a perl package
- context as a perl hash
I think seriously in the second form only as a hack (and not of the nice ones)
My friends are going to laugh if I tell them this...
We live in the *regex* world and here perl is KING, but so much Paul Graham is affecting my brain, some neurons on my head are spiking "hey maybe it's true" but when I see CLisp I don't know where to start, Where are my regex?, Where are my arrays? Where are my hashes? How can I open/write files? That all that I can think, excuse my rudeness. Sure thing they must be there or maybe a superior version of these, but I don't have the time (years I suspect) to master it.
Now newLisp is cool because it can be run from the console, has regex, has arrays, open/write files, has hashes, errr... no it doesn't have hashes.
And you know dictionaries are hell useful.
Any ~modern~ language has them, the faty snake python has them, the prima-donna ruby has them, even the minuscule lua has them...
So I hope newlisp.
(ex)
A namespace/context for implementing dictionaries is a very efficient way to implement a dictionary. newLISP namespaces/contexts are implemented using red-black binary trees, a very speedy and memory efficient method to implement dictionaries and more scalable than implementations using hashing techniques, which need allocate overflow spaces and take care of ties produced by the hashing algorithms.
Here is a short little module to implement dictionaries which feel more like what you are used in Ruby or Python:
;; module hash.lsp
(context 'HASH)
(define (HASH:HASH this)
(new 'MAIN:HASH this))
(define (put key value)
(context this key value))
(define (get key)
(context this key))
(define (keys)
(map name (difference (symbols this)
(append '(this put get key keys value)
(list (sym (name this) this))))))
(context MAIN)
and its called HASH to make everybody feel more familiar ;). This is how to use it:
(load "hash.lsp")
; create a new HASH
(HASH 'myhash)
; put some values
(myhash:put "abc" 123)
(myhash:put "xyz" 456)
; retrieve values
(myhash:get "abc") => 123
(myhash:get "xyz") => 456
; retrieve all keys
(myhash:keys) => ("abc" "xyz")
Myself I prefer to use the form (context ctx key value) directly because it is directer and faster. But this HASH module gives you a very readable syntax familiar in other scripting languages.
Lutz
Lutz my friend, seems I still have lots to walk in the newLisp way :)
In your example:
; create a new HASH
(HASH 'myhash)
; put some values
(myhash:put "a" 123)
(myhash:put "x" 456)
(myhash:put "d" 5)
How do you sort the dictionary by value?, I mean I wan't to get
(println myhash:sort-by-value-descend)
=> 456 "x"
=> 123 "a"
=> 5 "d"
This is the hard part for me.
Regards.
(ex)
(sort (map (lambda (x) (list (myhash:get x) x)) (myhash:keys)) (lambda (x y) (< y x)))
Thank you very much frontera000!
(println (sort (map (lambda (x) (list (myhash:get x) x)) (myhash:keys)) (lambda (x y) (< y x))))
(println (sort (map (lambda (x) (list (myhash:get x) x)) (myhash:keys)) (lambda (x y) (> y x))))
Finally... but you must to agree:
It takes more code (and more neurons) :)
(context 'DIC)
(define (DIC:DIC this)
(new 'MAIN:DIC this))
(define (put key value)
(context this key value))
(define (get key)
(context this key))
(define (has_key key)
(context? this key))
(define (echo)
(map (fn (key) (println key " - " (DIC:get key))) (DIC:keys)))
(define (vsort func)
(sort (map (fn (key) (list (DIC:get key) key)) (DIC:keys)) func))
(define (keys)
(map name (difference (symbols this)
(append '(this put get key keys value has_key echo vsort func)
(list (sym (name this) this))))))
(context MAIN)
; Decrypt
(setq text (string
"Uid nx, aex jcdjipx iu wzux zp, ta wxtpa jtdaws, ai etkx vis.n"
"Scfzezdi Ntapcniai. (hhh.tdaznt.qin/zyak/dcos)"))
(setq freqLang "TEOIARNSHLMYUCWDGPFBVKJ")
(DIC 'frequency)
(for (k 0 (- (length text) 1))
(setq c (upper-case (char (char text k))))
(if (regex "[A-Z]" c)
(if (frequency:has_key c)
(frequency:put c (+ 1 (frequency:get c)))
(frequency:put c 1))))
(setq freqText "")
(DIC 'dic)
(setq ind 0)
(map
(fn (x)
;;(println (x 1) " - " (x 0))
(setq freqText (string freqText (x 1)))
(dic:put (upper-case (string (x 1))) (char (char freqLang ind)))
(inc 'ind))
(frequency:vsort (fn (x y) (< y x))))
(println freqLang)
(println freqText)
(setq decrypted "")
(for (k 0 (- (length text) 1))
(setq uper 0)
(setq c (char (char text k)))
(if (regex "[A-Z]" c)
(setq uper 1)
(setq c (upper-case c)))
(if (dic:has_key c)
(if (= uper 1)
(setq decrypted (string decrypted (dic:get c)))
(setq decrypted (string decrypted (lower-case (dic:get c)))))
(setq decrypted (string decrypted c))))
(println decrypted)
If you feel something is wrong or can be improved, please bring it on! (pedantic-mode-encorauged)
A little minor thing from pedantic-mode. ;-)
Since (setq ..) can take multiple arguments you can make one of them here.
In big loops it could be a bit faster.
(for (k 0 (- (length text) 1))
(setq uper 0
c (char (char text k)))
(if (regex "[A-Z]" c)
(setq uper 1)
(setq c (upper-case c)))
(if (dic:has_key c)
(if (= uper 1)
(setq decrypted (string decrypted (dic:get c)))
(setq decrypted (string decrypted (lower-case (dic:get c)))))
(setq decrypted (string decrypted c))))
nice to know :)
Hi _ex_! Good code! I hope you continue with newLISP, despite your contextual reservations!
I was interested in your first Ruby script for decryption, and tried to translate it to a newLISP version, sticking to my own programming style :-)
(set
'text "Uid nx, aex jcdjipx iu wzux zp, ta wxtpa jtdaws, ai etkx vis. Scfzezdi Ntapcniai. (hhh.tdaznt.qin/zyak/dcos"
'freq-lang "TEOIARNSHLMYUCWDGPFBVKJ"
'freq-table '())
(map (fn (ch)
(if (regex "[A-Z]" ch)
(begin
(set 'found (lookup ch freq-table))
(if found
(replace-assoc ch freq-table (list ch (+ 1 found)))
(push (list ch 1) freq-table)))))
(map upper-case (explode text)))
; sort
(sort freq-table (fn (x y) (> (last x) (last y))))
; build dictionary
(map (fn (d e)
(push (list (first d) (last d) e) dict))
freq-table (explode freq-lang))
; decode text
(map (fn (c)
(set 'a (lookup (upper-case c) dict))
(set 'upper-case? (= c (upper-case c)))
(if a
(if upper-case? (print a) (print (lower-case a)))
(if upper-case? (print c) (print (lower-case c)))))
(explode text))
As you see, I tend to not use hash/dictionary/contexts automatically, but stick with my favourite list-based structures. To be honest I'm still not confident in my use of contexts other than for building modules. And with only the alphabet to store...
I was puzzled for a while as to why none of these scripts actually generates the right answer...;-) But it must be because there aren't enough letters in the text for the statistics to work. Also, the answers of my script and yours differ: again, I think this is because the letters with similar frequencies are sorted differently, and assigned differently as a result - not enough coded text to get some differences.
Or I have I misread your code?
Anyway, good stuff.
Hi cormullion :-)
Quote from: "cormullion"
If you want more cryptic puzzles to solve, there's
still the Da Vinci Code newLISP puzzle http://newlisper.blogspot.com ;-(((((
I have honestly tried and tried and tried to figure this out, and frankly, it's beginning to make me feel stupid and resentful about the lack of response to your hard work. Why hasn't anyone answered this, yet!?
m i c h a e l
P.S. I even wrote an email to Lutz asking him to help me figure this out, but I didn't end up sending it as I didn't want to bother him (not to mention humiliate myself ;-)
:-) If only the decrypted solution was interesting enough when you finally manage to decode it... Perhaps Dan Brown has the same problem...
This isn't something like, "Be sure to drink your Ovaltine!"[1] or anything, is it?
m i c h a e l
[1] Christmas Story
Her is a way to generate the freq-table with much less code:
(set 'text "Uid nx, aex jcdjipx iu wzux zp, ta wxtpa jtdaws, ai etkx vis. Scfzezdi Ntapcniai. (hhh.tdaznt.qin/zyak/dcos")
(set 'letters (unique (sort (explode text))))
=>
(" " "(" "," "." "/" "N" "S" "U" "a" "c" "d" "e" "f" "h" "i" "j"
"k" "n" "o" "p" "q" "s" "t" "u" "v" "w" "x" "y" "z")
(map list letters (count letters (explode text)))
=> (
(" " 15)
("(" 1)
("," 3)
("." 4)
("/" 2)
("N" 1)
("S" 1)
("U" 1)
("a" 9)
("c" 4)
("d" 6)
("e" 3)
("f" 1)
("h" 3)
("i" 9)
("j" 3)
("k" 2)
("n" 4)
("o" 1)
("p" 4)
("q" 1)
("s" 3)
("t" 7)
("u" 2)
("v" 1)
("w" 3)
("x" 6)
("y" 1)
("z" 6))
Lutz
Very nice! Good to see a craftsman at work!
... continuing from previous post
and here is an even faster and shorter method using 'bayes-train' to automatically count into a context:
(set 'text "Uid nx, aex jcdjipx iu wzux zp, ta wxtpa jtdaws, ai etkx vis. Scfzezdi Ntapcniai. (hhh.tdaznt.qin/zyak/dcos")
(bayes-train (explode text) '() 'freq-tab)
(dotree (c freq-tab)
(println (last (name c)) "=>" (first (eval c))))
=>15
(=>1
,=>3
.=>4
/=>2
N=>1
S=>1
U=>1
a=>9
c=>4
d=>6
e=>3
f=>1
... etc
Each letter symbol contains a list, i.e:
freq-tab:_a => (9 0)
the empty list in:
(bayes-train (explode text) '() 'freq-tab)
provided a place for the 0 position, which you could use to store another letter instead.
Lutz
cormullion, I think I'll follow with my newLisp endeavours :) because is the *only* lisp I can afford now (and that with a lot of struggling by my part)
About the message I didn't want to post the text complete
Uid nx, aex jcdjipx iu wzux zp, ta wxtpa jtdaws, ai etkx vis.
Dcos zyexdzaxr aex Jxdw jezwipijes iu etkzyg nidx aety iyx hts
ai ri aex ptnx aezyg. Z zyexdzaxr aeta jezwipijes udin Wtdds Htww,
hei zp ns exdi tqactwws. Z htya ai ntfx Dcos cpxdp udxx. Z htya ai
gzkx aexn aex udxxrin ai qeiipx. Jxijwx tdx rzuuxdxya. Jxijwx qeiipx
rzuuxdxya qdzaxdzt. Oca zu aexdx zp t oxaaxd hts tniyg ntys
twaxdytazkxp, Z htya ai xyqicdtgx aeta hts os ntfzyg za qinuidatowx.
Pi aeta'p heta Z'kx adzxr ai ri.
Z htya ai piwkx jdiowxnp Z nxxa zy aex rtzws wzux os cpzyg qinjcaxdp,
pi Z yxxr ai hdzax jdigdtnp. Os cpzyg Dcos, Z htya ai qiyqxyadtax aex
aezygp Z ri, yia aex ntgzqtw dcwxp iu aex wtygctgx, wzfx patdazyg hzae
jcowzq kizr pinxaezyg pinxaezyg pinxaezyg ai pts, "jdzya exwwi hidwr."
Z vcpa htya ai pts, "jdzya aezp!" Z riy'a htya tww aex pcddicyrzyg
ntgzq fxshidrp. Z vcpa htya ai qiyqxyadtax iy aex atpf. Aeta'p aex otpzq
zrxt. Pi Z etkx adzxr ai ntfx Dcos qirx qiyqzpx tyr pcqqzyqa.
Scfzezdi Ntapcniai. (hhh.tdaznt.qin/zyak/dcos)
I hope nobody gets offended if I post words of another master I admire here.
About: Da Vinci Code newLISP puzzle http://newlisper.blogspot.com
What it is? I went before (nice blog by the way) but searching with firefox I can't find anything about da vinci code, I'm missing something desho?
EDIT: I've searched wikipedia and found that the da vinci code could mean that you've hidden text in your blog as a secret message... LOL sorry I never read that book. And yes I like puzzles ;)
Hi (ex) :-)
Here is the url for cormullion's Da Vinci Code (//http) post.
m i c h a e l
Another attempt to produce a succint yet readable solution...
(for some reason I found myself writing as if it was Forth... ;-))
(set
'atext [text]os zyexdzaxr aex ... as previous post ...
apcniai. (hhh.tdaznt.qin/zyak/dcos) [/text]
'freq-lang "TEOAINRHSLMCYUDWGQFBPVKZXJ")
(define (in-range? x low high) (and (>= x low) (<= x high)))
(define (letter? c) (or
(in-range? (char c) (char "a") (char "z"))
(in-range? (char c) (char "A") (char "Z"))))
(set 'letters-used (unique (sort (filter letter? (explode atext)))))
(set 'letter-freq-table (map list letters-used (count letters-used (explode atext))))
(sort letter-freq-table (fn (x y) (> (last x) (last y))))
(set 'dict (map (fn (e f) (list (first e) (last e) f)) letter-freq-table (explode freq-lang)))
(map (fn (chr)
(set 'letter (lookup (lower-case chr) dict))
(set 'upper-case? (in-range? (char chr) (char "A") (char "Z")))
(if letter
(if upper-case? (print letter) (print (lower-case letter)))
(if upper-case? (print chr) (print (lower-case chr)))
))
(explode atext))
(exit)
;->
"by inherited the Perl philosophy of having more than one way
to do the same thing. I inherited that philosophy from Larry Wall,
who is my hero actually. I want to make Ruby users free. I want to
give them the freedom to choose. People are different. People choose
different criteria. But if there is a better way among many
alternatives, I want to encourage that way by making it comfortable.
So that's what I've tried to do.
I want to solve problems I meet in the daily life by using computers,
so I need to write programs. By using Ruby, I want to concentrate the
things I do, not the magical rules of the language, like starting with
public void something something something to say, "print hello world."
I just want to say, "print this!" I don't want all the surrounding
magic keywords. I just want to concentrate on the task. That's the basic
idea. So I have tried to make Ruby code concise and succinct."
Yukihiro Matsumoto. (www.artima.com/intv/ruby)
We like Ruby too (though not quite as much as newLISP :-))
Here's a quote from a language whose designer's
name also ends with *tz (Lutz not Matz):
"newLISP relies on a minimum of theoretical concepts. It
does not implement many paradigms or principles
discovered or invented in Computers Science and used in
other computer languages. The LISP list is the basic
paradigm for data and program structure and is enough to
capture, represent and process the highest degree of
complexity.
newLISP is very small and has built-in most of the
technologies needed in today's networked and
distributed applications. Tomorrow's software systems
are not constituted by big monolithic systems but small
agile agents running and cooperating on distributed
architectures. newLISP is small enough to run on
embedded systems or distributed server farms built from
thousands of small simple computer nodes.
newLISP recognizes the changing nature of progressing
technology and constantly tries to adapt to it. Over
the years the functionality found in newLISP has
undergone many changes adapting to what is necessary at
the time.
The newLISP Philosophy can be expressed with the few
words simple, small, agile, cooperative, adapting."
hmm.. nice to see cormullion... ;)
BTW your code doesn't work for me... I've done the changes:
(setq freqLang "TEOIARNSHLMYUCWDGPFBVKJ")
(define (in-range? x low high) (and (>= x low) (<= x high)))
(define (letter? c) (or
(in-range? (char c) (char "a") (char "z"))
(in-range? (char c) (char "A") (char "Z"))))
(set 'letters-used
(unique (sort (map upper-case (filter letter? (explode text))))))
(set 'letter-freq-table
(map list letters-used (count letters-used (map upper-case (explode text)))))
(sort letter-freq-table
(fn (x y) (> (last x) (last y))))
(set 'dict
(map (fn (e f) (list (first e) (last e) f)) letter-freq-table (explode freqLang)))
(map (fn (chr)
(set 'letter (lookup (upper-case chr) dict))
(set 'upper-case? (in-range? (char chr) (char "A") (char "Z")))
(if letter
(if upper-case? (print letter) (print (lower-case letter)))
(print chr)))
(explode text))
Yeah Lu(tz) ... Ma(tz) nice catch :)