Slicing arrays

Arjen Markus (14 april 2004) Here is another experiment with sets of numerical data - in C and Fortran and many other programming languages commonly called multidimensional arrays. The operation implemented below allows you to select so-called slices from these blocks of data. This being done in Tcl, it makes little difference if the data are indeed numbers or something else.

It is an operation that is used quite frequently to copy/select data for some computation.

 # slicearray.tcl --
 #    Procedure to implement slicing operations on lists and
 #    lists of lists:
 #    slice { { 1  2  3  4  5}
 #            { 6  7  8  9 10}
 #            {11 12 13 14 15} } {0 1} ==> { { 1 2 3 4  5}
 #                                           { 6 7 8 9 10} }
 #
 #    slice { { 1  2  3  4  5}
 #            { 6  7  8  9 10}
 #            {11 12 13 14 15} } {} {0 1} ==> { { 1  2}
 #                                              { 6  7}
 #                                              {11 12} }
 #
 #    Note:
 #    The procedure works on any level of nesting, but it
 #    does assume that the indices are within range - there
 #    is no check whether that is indeed the case.

 # arrays --
 #    An appropriate namespace
 #
 namespace eval ::arrays {
    namespace export slice
 }

 # slice --
 #    Return a subset of the data using a slicing operation
 # Arguments:
 #    data       The data to be sliced (a list, list of lists, list of
 #               lists of lists, ...)
 #    indices    A list of zero to three values indicating which
 #               elements should be retained
 #    args       Slicing arguments for the next (deeper) level
 # Result:
 #    New multidimensional array with a reduced number of data
 #
 # Note:
 #    The indices may be:
 #    - An empty list, then the whole dimension is retained
 #    - A single element, which means only one index is retained
 #    - Two elements, specifying the start and stop of the indices
 #      (implicit stepsize: 1)
 #    - Three elements: stasrt, stop and stepsize
 #
 proc ::arrays::slice {data indices args} {
    set recursive 1
    if { [llength $args] == 0 } {
       set recursive 0
    }
    if { [llength $indices] == 0 } {
       set start 0
       set stop  [expr {[llength $data]-1}]
       set step  1
    }
    if { [llength $indices] == 1 } {
       set start [lindex $indices 0]
       set stop  $start
       set step  1
    }
    if { [llength $indices] == 2 } {
       set start [lindex $indices 0]
       set stop  [lindex $indices 1]
       set step  1
    }
    if { [llength $indices] == 3 } {
       set start [lindex $indices 0]
       set stop  [lindex $indices 1]
       set step  [lindex $indices 2]
    }
    set noiter [expr {int(($stop-$start+$step)/$step)}]

    set iter   0
    set idx    $start
    set result {}
    if { $recursive } {
       while { $iter < $noiter } {
          lappend result [eval slice [list [lindex $data $idx]] $args]
          incr idx  $step
          incr iter
       }
    } else {
       while { $iter < $noiter } {
          lappend result [lindex $data $idx]
          incr idx  $step
          incr iter
       }
    }
    return $result
 }

 # testing the implementation
 #
 set data { { 1  2  3  4  5}
            { 6  7  8  9 10}
            {11 12 13 14 15} }

 puts [::arrays::slice $data {0 1}]
 puts [::arrays::slice $data {} {0 1}]
 puts [::arrays::slice $data {} {0 4 2}]
 puts [::arrays::slice $data {2 1 -1} {0 4 2}]