Bringing back the J to Bifid Cipher
Donnerstag, 22. Oktober 2009
As a programming language enthusiast I often examine new (to me) languages, paradigms and concepts. Recently I played with J - sth. like APL with keyboard friendly syntax. If you are familiar with point-free functional programming, J isn't that complicated. More tedious is the used vocabulary (verbs, nouns, frets, agenda, gerunds...) for trivial things and the fact, that you can't use search engines - neither for "J" (-> "site:jsoftware.com"), nor for a function symbol (combination).
Mind bending are the many one-liners and examples in the documentation. And believe me, there isn't much more satisfaction as in unraveling the first lines of J code. ;)
As practical example on "hello world" level I used the Bifid Cipher:
Keep in mind, this is of course not the shortest or fastest or nicest implementation. It's just my first J program.
A short explanation for the curious
Read from right to left, @ is function composition and & binds a parameter.
pre - prepare our string
1. take a list of characters (the string), convert every character to uppercase (toupper) and look up (&i.) the characters in the ascii table (a.)
2. subtract 65 (ascii value of 'A') from every list item (-&65) (a 0..4 indexed cipher table to save some inc- (>:) and decrements (<:))
3. and decrement every item greater than 9 (]->&9) for the missing 'J'(!)
The last part needs some explanation. (]->&9) is a so called "monadic fork". We have three functions f (] "identity"), h (- "minus") and g (>&9 "greater than 9") and the constellation as monadic fork (f h g) x executes as h(f(x),g(x)). The comparison gives a list of 0s and 1s, "NB." is line comment:
enc - encode
1. Now you are familiar with forks, you can interpret (<.@%&5,5&|) also, didn't you? On the left side we divide every element by 5 (%&5) and round (<. "floor") to the next smallest integer. On the other side we compute the remainder of the division with 5 (| "mod") and append (,) that list to our left result.
2. with ((%&2@#,2:)$[) we reshape ($) our list to nx2, n is the length (#) of the list divided by 2 (%&2), (2:) is the constant function, giving back 2
3. and transpose (|:) it for our final conversion in pst
dec - decode
Decode is similar to encode.
1. div (<.@%&5) and mod (%&|) our list but merge (,@,.) the result
2. and again reshape the result to 2xn with n as half list length
pst - result to string
1. we have to rebuild our string, so we take the first row ([/) and multiply with 5 (5&*) and add (+) the result to our second (]/) row, to which we added 66 (66&+)
2. we have to decrement the values below 75 ('K') (]-<&75)
3. and lookup our values in the ascii table ({&a.)
Now we can compose and use our encode and decode functions:
Another example
Not enough? I see you are as fascinated as I am. The next example ist from j602/system/main/convert.ijs
3 : '... y' defines a verb, imagine it as a function with one argument y. (()`()@.()) is:
Pure beauty! Here (2&=@(3!:0)) acts as a case or if expression. It returns a value which becomes the index of the left "list of functions" (take it with salt). 3!:0 returns the "type" (more salt!) of a parameter. If it is a character it returns 2, the following comparison (2&=) returns 1 and (a.&i) evaluates to the index of this character in the ascii table. Otherwise we assume it is a number and return the corresponding character.
Mind bending are the many one-liners and examples in the documentation. And believe me, there isn't much more satisfaction as in unraveling the first lines of J code. ;)
As practical example on "hello world" level I used the Bifid Cipher:
pre=:(]->&9)@-&65@(a.&i.@toupper)
enc=:|:@((%&2@#,2:)$[)@(<.@%&5,5&|)
dec=:((2:,%&2@#)$[)@(<.@%&5,@,.5&|)
pst=:{&a.@(]-<&75)@(5&*@[/+66&+@]/)
encode=:pst\@enc\@pre
decode=:pst\@dec\@pre
enc=:|:@((%&2@#,2:)$[)@(<.@%&5,5&|)
dec=:((2:,%&2@#)$[)@(<.@%&5,@,.5&|)
pst=:{&a.@(]-<&75)@(5&*@[/+66&+@]/)
encode=:pst\@enc\@pre
decode=:pst\@dec\@pre
Keep in mind, this is of course not the shortest or fastest or nicest implementation. It's just my first J program.
A short explanation for the curious
Read from right to left, @ is function composition and & binds a parameter.
pre - prepare our string
1. take a list of characters (the string), convert every character to uppercase (toupper) and look up (&i.) the characters in the ascii table (a.)
a.&i.@toupper 'hello'
72 69 76 76 79
72 69 76 76 79
2. subtract 65 (ascii value of 'A') from every list item (-&65) (a 0..4 indexed cipher table to save some inc- (>:) and decrements (<:))
-&65@(a.&i.@toupper) 'hello'
7 4 11 11 14
7 4 11 11 14
3. and decrement every item greater than 9 (]->&9) for the missing 'J'(!)
(]->&9)@-&65@(a.&i.@toupper) 'hello'
7 4 10 10 13
7 4 10 10 13
The last part needs some explanation. (]->&9) is a so called "monadic fork". We have three functions f (] "identity"), h (- "minus") and g (>&9 "greater than 9") and the constellation as monadic fork (f h g) x executes as h(f(x),g(x)). The comparison gives a list of 0s and 1s, "NB." is line comment:
NB. the identity (f):
] 7 4 11 11 14
7 4 11 11 14
NB. greater than 9 (g):
(>&9) 7 4 11 11 14
0 0 1 1 1
NB. together with minus h(f(x),g(x)):
(]->&9) 7 4 11 11 14
7 4 10 10 13
] 7 4 11 11 14
7 4 11 11 14
NB. greater than 9 (g):
(>&9) 7 4 11 11 14
0 0 1 1 1
NB. together with minus h(f(x),g(x)):
(]->&9) 7 4 11 11 14
7 4 10 10 13
enc - encode
1. Now you are familiar with forks, you can interpret (<.@%&5,5&|) also, didn't you? On the left side we divide every element by 5 (%&5) and round (<. "floor") to the next smallest integer. On the other side we compute the remainder of the division with 5 (| "mod") and append (,) that list to our left result.
(<.@%&5,5&|) 7 4 10 10 13
1 0 2 2 2 2 4 0 0 3
1 0 2 2 2 2 4 0 0 3
2. with ((%&2@#,2:)$[) we reshape ($) our list to nx2, n is the length (#) of the list divided by 2 (%&2), (2:) is the constant function, giving back 2
((%&2@#,2:)$]) 1 0 2 2 2 2 4 0 0 3
1 0
2 2
2 2
4 0
0 3
1 0
2 2
2 2
4 0
0 3
3. and transpose (|:) it for our final conversion in pst
|:((%&2@#,2:)$]) 1 0 2 2 2 2 4 0 0 3
1 2 2 4 0
0 2 2 0 3
1 2 2 4 0
0 2 2 0 3
dec - decode
Decode is similar to encode.
1. div (<.@%&5) and mod (%&|) our list but merge (,@,.) the result
NB. append from enc
(<.@%&5,5&|) 5 12 12 20 3
1 2 2 4 0 0 2 2 0 3
NB. merge from dec
(<.@%&5,@,.5&|) 5 12 12 20 3
1 0 2 2 2 2 4 0 0 3
(<.@%&5,5&|) 5 12 12 20 3
1 2 2 4 0 0 2 2 0 3
NB. merge from dec
(<.@%&5,@,.5&|) 5 12 12 20 3
1 0 2 2 2 2 4 0 0 3
2. and again reshape the result to 2xn with n as half list length
((2:,%&2@#)$[) 1 0 2 2 2 2 4 0 0 3
1 0 2 2 2
2 4 0 0 3
1 0 2 2 2
2 4 0 0 3
pst - result to string
1. we have to rebuild our string, so we take the first row ([/) and multiply with 5 (5&*) and add (+) the result to our second (]/) row, to which we added 66 (66&+)
NB. our table
t=:((2:,%&2@#)$[)@(<.@%&5,@,.5&|) 5 12 12 20 3
t
1 0 2 2 2
2 4 0 0 3
NB. first row
[/ t
1 0 2 2 2
NB. second row
]/ t
2 4 0 0 3
NB. multiply and add twice
(5&*@[/ + 66&+@]/) t
73 70 76 76 79
t=:((2:,%&2@#)$[)@(<.@%&5,@,.5&|) 5 12 12 20 3
t
1 0 2 2 2
2 4 0 0 3
NB. first row
[/ t
1 0 2 2 2
NB. second row
]/ t
2 4 0 0 3
NB. multiply and add twice
(5&*@[/ + 66&+@]/) t
73 70 76 76 79
2. we have to decrement the values below 75 ('K') (]-<&75)
(]-<&75)(5&*@[/ + 66&+@]/) t
72 69 76 76 79
72 69 76 76 79
3. and lookup our values in the ascii table ({&a.)
NB. decoding result
({&a.) 72 69 76 76 79
HELLO
NB. encoding result
({&a.) 70 78 78 86 68
FNNVD
({&a.) 72 69 76 76 79
HELLO
NB. encoding result
({&a.) 70 78 78 86 68
FNNVD
Now we can compose and use our encode and decode functions:
encode=:pst\@enc\@pre
decode=:pst\@dec\@pre
encode 'PROGRAMMINGPRAXISWITHJ'
OMQNHHQWUIGWRFGSKFOMTP
encode 'PROGRAMMINGPRAXIS'
OMQNHHQWUIGBIMWCS
decode 'OMQNHHQWUIGWRFGSKFOMTP'
PROGRAMMINGPRAXISWITHK
decode 'OMQNHHQWUIGBIMWCS'
PROGRAMMINGPRAXIS
decode=:pst\@dec\@pre
encode 'PROGRAMMINGPRAXISWITHJ'
OMQNHHQWUIGWRFGSKFOMTP
encode 'PROGRAMMINGPRAXIS'
OMQNHHQWUIGBIMWCS
decode 'OMQNHHQWUIGWRFGSKFOMTP'
PROGRAMMINGPRAXISWITHK
decode 'OMQNHHQWUIGBIMWCS'
PROGRAMMINGPRAXIS
Another example
Not enough? I see you are as fascinated as I am. The next example ist from j602/system/main/convert.ijs
av=: 3 : '({&a.)`(a.&i.) @. (2&=@(3!:0)) y'
3 : '... y' defines a verb, imagine it as a function with one argument y. (()`()@.()) is:
m@.n is a verb defined by the gerund m with an agenda specified by n ; that is, the verb represented by the train selected from m by the indices n . If n is boxed, the train is parenthesized accordingly. The case m@.v uses the result of the verb v to perform the selection.
Pure beauty! Here (2&=@(3!:0)) acts as a case or if expression. It returns a value which becomes the index of the left "list of functions" (take it with salt). 3!:0 returns the "type" (more salt!) of a parameter. If it is a character it returns 2, the following comparison (2&=) returns 1 and (a.&i) evaluates to the index of this character in the ascii table. Otherwise we assume it is a number and return the corresponding character.
(2&=@(3!:0)) 'A'
1
(2&=@(3!:0)) 97
0
({&a.) 99
a
(a.&i.) 'a'
97
({&a.)`(a.&i.) @. (2&=@(3!:0)) 'abc'
97 98 99
({&a.)`(a.&i.) @. (2&=@(3!:0)) 97 98 99
abc
1
(2&=@(3!:0)) 97
0
({&a.) 99
a
(a.&i.) 'a'
97
({&a.)`(a.&i.) @. (2&=@(3!:0)) 'abc'
97 98 99
({&a.)`(a.&i.) @. (2&=@(3!:0)) 97 98 99
abc
Trackbacks
Trackback-URL für diesen Eintrag
Keine Trackbacks
Kommentare