Multi dimensional - iterator, Iterator2D, Iterator3D


(Ted van Gaalen) #1

Hi Garth

I’m not sure why you’re trying to avoid sequences - as far as the actual values you are iterating over, your needs seem to be pretty well covered by the existing stride() family.

I am not avoiding per se the use of Strides & Sequence & Co.
It is just because I simply don’t need them in my iterator functions.

Given that you just want to flatten the call sites, perhaps something like this would suit your needs:

let xrange = stride(from: 0.0, to: 30.0, by: 10.0)
let yrange = stride(from: 0.0, to: 20.0, by: 5.0)
let zrange = stride(from: 10.0, to:-10.0, by:-5.0)

for (x, y, z) in cartesianProduct(xrange, yrange, zrange) {
   print("x = \(x) y = \(y) z = \(z)")
   if z < 0.0 {
       print ( "** z value \(z) is below zero! **" )
       break
   }
}
Using strides removes the need for any of the NumericType labels, and cartesianProduct() would be usable for any (reiterable) sequences, not just your designated types.

True. but what I have now works. all I need was new iterators for numeric type as soon as possible
because the for;; will be no longer available in 3.0.
As a sort of spin-off I also made it for 2 and 3 dimensions.
For my apps this is sufficient and even better to use than the nested for;;for;;

I am currently trying to write an iterator that should work
with a variable depth of levels. A recursive approach
Currently it is under construction not yet working as expected.
I cannot wrap my brain around the recursive part. (yet)
will drop it for a day or two. :o)
You can try / improve it in your xcode playground if you like. is 2.3 but should also work in 3.0.

struct IteratorParameters<T:NumericType>
{
    let start: T
    let step: T
    let test: (T) -> Bool
}

func iterateR<T: NumericType>( parameters: [IteratorParameters<T>], block: (Int,[T]) -> Bool)
{
    var currents = [T]()
    for parm in parameters
    {
        currents.append(parm.start) // initialize current values with start values.
    }
    
    let levels = parameters.count - 1
    var level = 0

    //——under construction————obviously still not ok here---------------------------
    func iterateLevel() // recursive nested function.
    {
        
        while parameters[level].test(currents[level]) && block(level,currents)
        {
            currents[level] = currents[level] + parameters[level].step
            iterateLevel() // recursive
        }
        level = level + 1
        print("\n")
    }
    //-------------------------------------------------------------------------------
    iterateLevel()
}

func testIteratorR()
{
    
    var itparms = [IteratorParameters<Int>]()
    
    itparms.append( IteratorParameters( start: 0, step : 1, test: {$0 < 3} ) )
    itparms.append( IteratorParameters( start: 0, step : 1, test: {$0 < 3} ) )
    itparms.append( IteratorParameters( start: 0, step : 1, test: {$0 < 3} ) )
    itparms.append( IteratorParameters( start: 0, step : 1, test: {$0 < 3} ) )
    
    iterateR(itparms,
             block: {lvl, values in
                         print("level=\(lvl) values = \(values)")
                       return true } )
}

If you want a language issue to obsess over, I suggest the inflexibility of tuples, which force you to have a separate wrapper for each number of dimensions. :slight_smile:

:o) I find very little use for tuples, except perhaps when tuples are used to pass byte arrays to lower-level functions, and even then.
I am more and more less “obsessed” with changing and disappearing language features, because
in many cases, in Swift increasingly you can roll your own to replace them or even make something better, if really needed.
like in Smalltalk as well.

I would have thought there’d be an off-the-shelf cartesian product somewhere that you could use, but it doesn’t seem to come up in the Google. It’d look something like this. (This is causing a compiler crash in Xcode 8b3 and so is not completely vetted, but it’s probably close…)

Will try your solution (looks clever) although I don’t need the collection “by-product” Perhaps more suited for
in progs that have a more functional programming inclination?

func cartesianProduct<U, V where U: Sequence, V == U.Iterator.Element>(_ args: U...) ->
   AnyIterator<[V]>
{
   var iterators = args.map { $0.makeIterator() }
   var values = [V?]()
   for i in 0 ... iterators.endIndex {
       values.append(iterators[i].next())
   }
   var done = values.contains { $0 == nil }

   return AnyIterator() {
       if done {
           return nil
       }
       let thisValue = values.map { $0! }
       var i = args.endIndex
       repeat {
           values[i] = iterators[i].next()
           if values[i] != nil {
               return thisValue
           } else if i == 0 {
               done = true
               return thisValue
           } else {
               iterators[i] = args[i].makeIterator()
               values[i] = iterators[i].next()
               i -= 1
           }
       } while true
   }
}

func cartesianProduct<U, V where U: Sequence, V == U.Iterator.Element>(_ a: U, _ b: U, _ c: U) ->
   AnyIterator<(V, V, V)>
{
   var subIterator: AnyIterator<[V]> = cartesianProduct(a, b, c)
   return AnyIterator() {
       if let value = subIterator.next() {
           return (value[0], value[1], value[2])
       }
       return nil
   }
}

Kind Regards
Ted

···

Garth

On Jul 30, 2016, at 1:48 PM, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> wrote:

Hi Chris,

thanks for the tip about Hirundo app!

A positive side-effect of removing the classical for;; loop
(yes, it’s me saying this :o) is that it forces me to find
a good and generic equivalent for it,
making the conversion of my for;;s to 3.0 easier.
which is *not* based on collections or sequences and
does not rely on deeper calls to Sequence etc.

so, I’ve made the functions [iterator, iterator2D, iterator3D] (hereunder)
wich btw clearly demonstrate the power and flexibility of Swift.

Very straightforward, and efficient (i assume) just like the classical for;; loop.
It works quite well in my sources.

As a spin-off, I’ve extended these to iterators for matrices 2D and cubes? 3D...

Question:
Perhaps implementing “multi dimensional iterator functions
in Swift might be a good idea. so that is no longer necessary to nest/nest/nest iterators.

Met vriendelijke groeten, sorry for my “intensity” in discussing the classical for;;
I'll have to rethink this for;; again..
Thanks, Ted.

Any remarks ( all ), suggestions about the code hereunder: ?

protocol NumericType
{
   func +(lhs: Self, rhs: Self) -> Self
   func -(lhs: Self, rhs: Self) -> Self
   func *(lhs: Self, rhs: Self) -> Self
   func /(lhs: Self, rhs: Self) -> Self
   func %(lhs: Self, rhs: Self) -> Self
}

extension Double : NumericType { }
extension Float : NumericType { }
extension CGFloat: NumericType { }
extension Int : NumericType { }
extension Int8 : NumericType { }
extension Int16 : NumericType { }
extension Int32 : NumericType { }
extension Int64 : NumericType { }
extension UInt : NumericType { }
extension UInt8 : NumericType { }
extension UInt16 : NumericType { }
extension UInt32 : NumericType { }
extension UInt64 : NumericType { }

/// Simple iterator with generic parameters, with just a few lines of code.
/// for most numeric types (see above)
/// Usage Example:
///
/// iterate(xmax, { $0 > xmin}, -xstep,
/// {x in
/// print("x = \(x)")
/// return true // returning false acts like a break
/// } )
///
/// -Parameter vstart: Initial value
/// -Parameter step: The iteration stepping value.
/// -Parameter test: A block with iteration test. e.g. {$0 > 10}
///
/// -Parameter block: A block to be executed with each step.
/// The block must include a return true (acts like "continue")
/// or false (acts like "break")
/// -Please Note:
/// There is minor precision loss ca: 1/1000 ... 1/500
/// when iterating with floating point numbers.
/// However, in most cases this can be safely ignored.
/// made by ted van gaalen.

func iterate<T:NumericType> (
                   vstart: T,
                  _ vstep: T,
                  _ test: (T) -> Bool,
                  _ block: (T) -> Bool )
{
   var current = vstart

   while test(current) && block(current)
   {
       current = current + vstep
   }
}

/// X,Y 2D matrix (table) iterator with generic parameters
func iterate2D<T:NumericType> (
    xstart: T, _ xstep: T, _ xtest: (T) -> Bool,
  _ ystart: T, _ ystep: T, _ ytest: (T) -> Bool,
  _ block: (T,T) -> Bool )
{
   var xcurrent = xstart
   var ycurrent = ystart

   var dontStop = true

   while xtest(xcurrent) && dontStop
   {
       ycurrent = ystart
       while ytest(ycurrent) && dontStop
       {
           dontStop = block(xcurrent, ycurrent)
           ycurrent = ycurrent + ystep
       }
       xcurrent = xcurrent + xstep
   }
}

/// X,Y,Z 3D (cubic) iterator with generic parameters:

func iterate3D<T:NumericType> (
   xstart: T, _ xstep: T, _ xtest: (T) -> Bool,
_ ystart: T, _ ystep: T, _ ytest: (T) -> Bool,
_ zstart: T, _ zstep: T, _ ztest: (T) -> Bool,
     _ block: (T,T,T) -> Bool )
{
   var xcurrent = xstart
   var ycurrent = ystart
   var zcurrent = zstart

   var dontStop = true

   while xtest(xcurrent) && dontStop
   {
       ycurrent = ystart
       while ytest(ycurrent) && dontStop
       {
           zcurrent = zstart
           while ztest(zcurrent) && dontStop
           {
               dontStop = block(xcurrent, ycurrent, zcurrent)
               zcurrent = zcurrent + zstep
           }
           ycurrent = ycurrent + ystep
       }
       xcurrent = xcurrent + xstep
   }
}

func testIterator()
{
   iterate(0.0, 0.5, {$0 < 1000.00000} ,
           { value in
               print("Value = \(value) ")
               return true
   } )

   let startv: CGFloat = -20.0
   let stepv: CGFloat = 0.5

   iterate(startv, stepv, {$0 < 1000.00000} ,
           { val in
               print("R = \(val)")
               return true
   } )

   let tolerance = 0.01 // boundary tolerance for floating point type

   iterate2D( 0.0, 10.0, { $0 < 100.0 + tolerance } ,
              0.0, 5.0, { $0 < 50.0 + tolerance } ,
              {x,y in
               print("x = \(x) y = \(y)")
               return true // false from block stops iterating ( like break)
               } )

   iterate3D( 0.0, 10.0, { $0 < 30.0 } , // x
              0.0, 5.0, { $0 < 20.0 } , // y
              10.0, -5.0, { $0 > -10.0 } , // z
              {x,y,z in
                   print("x = \(x) y = \(y) z = \(z)")
                   if z < 0.0
                   {
                       print ( "** z value \(z) is below zero! **" )

                       return false // (acts as break in for;:wink:
                   }
                   return true // return stmt is obligatory (continue)
              } )
}