Aai wrote:
>  e.g. is it possible to avoid 'some' boxing.

Oh!  Sure.

>  HCF=: 1 1,[:(#(2...@#))[:;(,;@(|.@(#;.1)(],~1$~[)&.>
<@:>:;.1)&.>@{:)^:(]`(<@2:))

Let's break this up for easier reading:

           HCF0       =:  1 1,[:(#(2...@#))[:;(,;@(|.@(#;.1)(],~1$~[)&.>
<@:>:;.1)&.>@{:)^:(]`(<@2:))    
                
           HCF1       =:  1 1 , [: expand tail
             expand   =:  # (2...@#)
             tail     =:  [: ; (, copies&.>@{:)^:powers
               copies =:  ;@(|.@(#;.1)(],~1$~[)&.> <@:>:;.1)
               powers =:  ]`(<@2:) 
        
First let's clean up a couple of minor things before we tackle the heart of
the algorithm (copies).  With an eye towards the final, fully fixed form,
let's first move that rightmost raze out of tail and into expand: 

             expand   =:  (# (2...@#))@:;
             tail     =:  (, copies&.>@{:)^:powers

then let's rewrite HCF1 as:

           HCF1       =:  1 1 , expand@:tail

Next, we could take advantage of the general definition of monad I. to write
expand a little more succinctly:

             expand   =:  2 + I. @:;

This formulation would also allow us to take advantage of any special code
in I.'s implementation [1].  

Next, for consistency (of inputs to copies), powers should initialize tail
with a vector, not a scalar:

               powers =:  ]`(<@,@2:) 

Though this change has no impact on the results, in general admitting this
kind of inconsistency can cause trouble and should be avoided [2].

Next, we notice that tail has the form (, f@:{:)^:g, which we can simplify
to f^:(<@:g) (a nice feature of ^:):

               powers =:  <@:>:`(<@,@2:)  NB. Use < to keep intermediate
results
             tail     =:  copies&.>^:powers

Having made these minor changes, we can address the heart of the solution,
copies, and its "boxiness".  First, let`s define a utility conj to do "under
right":

           un         =:  conjunction def 'v^:_1@:(u v)'  

With that, we can now write:

               copies =:  >: #!.1  un|.~ 1 j. #;.1 #^:_1 ::1:~ ]~:{.

Note that this new formulation uses expand (#^:_1) and a fitted complex-copy
(# with j.), which allows it to avoid boxes entirely [3] (to eliminate boxes
entirely from the solution would be to implement a different algorithm,
which isn't the object of this message).  The wart  ::  is required because
0 #^:_1: 1  is an error.

Now let's compare & contrast our new verb with the original.  First, we need
to collect the final definitions:

           un         =:  conjunction def 'v^:_1@:(u v)'     
        
           HCF1       =:  1 1 , exp...@tail
             expand   =:  2+I.@;
             tail     =:  copies&.>^:powers
               copies =:  >: #!.1  un|.~ 1 j. #;.1 #^:_1 ::1:~ ]~:{.
               powers =:  <@>:`(<@,@2:)

Then, to compare it textually/stylistically to the original, we need to
write HCF1 out as a single fully-fixed verb:
           
           NB.  Original
           HCF0       =:  1 1 , [: (# (2...@#)) [: ; (, ;@(|.@ #;.1 (] ,~
1$~[)&.> <@:>:;.1)&.>@{:)^:(]`(<@2:))   

           NB.  HCF1 fully fixed
           HCF1f0     =:  1 1 , 2 + I.@;@((>: #!.1 un|.~ 1 j. #;.1 #^:_1
::1:~ ] ~: {.)&.>^:(<@>:`(<@,@2:)))

           NB.  Or use [:
           HCF1f1     =:  1 1 , 2 + [: I.@; (>: #!.1 un|.~ 1 j. #;.1 #^:_1
::1:~ ] ~: {.)&.>^:(<@>:`(<@,@2:))
   
Having done that, let's compare the lot for performance:
  
           ways       =:  HCF0`HCF1`HCF1f0`HCF1f1
           assert (-: |.)@:(ways`:0) 20             NB.  Identical results
           (>ways) ,"1 ' ' ,.  '5.2d' 8!:2 (%"1 <./) (6!:2, 7!:2@:])&> ways
,L:0 ' 20'
        HCF0    2.28 1.00
        HCF1    1.00 1.17
        HCF1f0  1.03 1.17
        HCF1f1  1.00 1.17
                     
As expected, none of the stylistic changes to HCF1 had an effect.  More
surprising is that HCF1 is more than a factor of two faster than the
original; I'm not sure whether that's because we reduced the boxing, or
simply because we used  f^:(<@:g)  in lieu of  (, f@:{:)^:g .  It would be
easy to test.

-Dan


[1]  Side note: instead of writing  1 1 , (2+I.)@:tail  , we could use
overtake, 
     in the hope that giving the interpreter more information might allow it
to
     improve performance for the prependation, as in:
         
         (({.!.1~ -)&(+&2) #)@:I.  
     
     But my tests didn't show improved performance, so it's not worth the
extra
     code complexity or loss of clarity.  It may not even be possible to
improve 
     the performance of a prependation, anyway.

[2]  Often, you'll discover this when writing the verb that consumes the 
     inconsistent input, as you'll find yourself handling "edge cases" (i.e.

     your own inconsistencies).  But the more insidious cases are where you
     may not discover the error when writing the verb, but calling it (often
     with rare arguments, like 0, which makes for unpleasant surprises).

[3]  Though of course I had a twinge when writing  #;.1 f~ ]~:{.  because
;.1  
     so obviously also does  ]~:{.  that using both seems redundant.  We
could 
     avoid duplicating this work with e.g. (f~ (- |.!:_1)I.@:-.)@:(~:{.) ,
but 
     again this shows no improvement (to the contrary), and yet is longer
and 
     harder to understand. Perhaps if we could avoid the  -.  it might be 
     competitive with the ;.1 style solution.


----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm

Reply via email to