Feature proposal: Range operator with step


(Ted van Gaalen) #1

Hi Erica, Dave

Based on what I’ve (not all) read under this topic:

I’d suggest a more complete approach:

Ranges should support: (as yet not implemented)

- All numerical data types (Double, Float, Int, Money***, Decimal*** )
- An arbitrary increment or decrement value.
- Working in the complete - + numerical range
- Allow traversing in both - + directions.

The basics:

    (v1…v2).by(v3) // this still is readable. and can be optimized by the compiler (predictable sequence)

Rules:

    v1, v2, v3 are any numerical type scalar type
    v1, v2, v3 must have the same numerical type.
    v1 > v2: is allowed and correctly evaluated. e.g. (8.0…-3.14159).by(-0.0001)

    The ..< half open operator is no longer allowed. write e.g. 0...ar.count - 1

    "by(…)” is obligatory with floating point range.

           the default “by(…)” value of 1 makes sense only with integer ranges.

valid examples:
    (5…9) // 5 6 7 8 9 Integer range without “by” defaults to 1 as increment value.
    (1...10).by(2) // 1 3 5 7 9.
    (2...10).by(2) // 2 4 6 8 10.
    (4…-4).by(2) // 4 2 0 -2 -4 . // runs backwards
    (30..-10).by(-2) // 30 28 26 24 22 ….. -10.
    (10...0).by(-3) // 10 7 4 1.

    (12…-10) // is valid, but returns empty because default increment value = 1

    (12.0…-12.0).by(-1.5) // 12.0 10.5 9.0…. // of course with float imprecision
                                                                       
   invalid examples:

   (23.0..<60.5).by(0.5) // half open ranges are no longer allowed **
  (23.0…60.5) // “ by" is obligatory with floats.
  (14...8).by(0.5) // v1 v2 and v3 don’t have the same numerical type

Half open ranges make no real sense (are not really useful) with floating point values.
and no sense at all with e.g (12..<-1) afaics

At least for float iterations the increment value should not each time be accumulated like
v1 += v3; v1 += v3; v1 += v3; …. // causes float number drift.
but be freshly multiplied with each iteration, e.g. by using an internal iteration counter
like so:

v1 = v3 * i++; v1 = v3 * i++; v1 = v3 * i++;….

for reasons of precision.

If one has worked with floating point data more often
awareness of its precision limitations become a second nature conscience.
E.g. it is perfectly acceptable and known (also in classical for-loops) that
(0.0…1.0).by(0.1) is not guaranteed to reach the humanly expected value of 1.0.
This is normal. Due to the nature of what mostly is done in the
floating point numbers domain, this does not often cause a problem
(e.g like working with a slide ruler)
if it is important to reach the “expected end” then one could
-add an epsilon value like so (v1…v2 + 0.1)
-generate the desired float sequence within an integer loop.

The above “range” (imho) improvement makes the
stride.. function/keyword completely unnecessary.

Due to its ability to process reversed ranges as is,
the .reverse() is optional (no longer necessary in most cases,
allowing the compiler to optimize without having to process
it like a collection.

Now that we have a fully functional range, which can do all things desired, one can
then of course, pass this range without any change to a collection based for … e.g.

for v in (v1…v2).by(v3) // optionally add other collection operators/filters/sorts here

(or in any other construct you might see fit)
                                       
This seems to be a reasonable alternative for

- the classical for ;; loop
-the collection-free for-loop
     for v from v1 to v2 by v3

As you know, the latter is what I have been suggesting,
but seemingly does not find much support,
(because I received very little reactions)
making it doubtful if I will ever make a proposal for this for loop.
Anyone out there should I stil do that?

When one does not further extend the range
with filters like sort etc. the compiler can still optimize
a collection-in-between out of the way.
(Thank you Taras, für das Mitdenken. :o)

** note that a half open range would in most cases be unnecessary
      if collection indexes started with 1 instead of 0, e.g. someArray[1…10]
     (but it’s too late to change that.)
     “the color of the first apple?” vs
     “the color of the zeroth (0) ??? apple?” ? Silly isn’t?

*** possible future numerical “native” types

It could be that (at least) partly something similar has already been suggested.
but so much is here of this topic that i can’t see the wood for the trees, so to speak.

Your (all) opinions are appreciated.

kind regards, mit freundlichen Grüssen, Met vriendelijke groeten,
Sigh, why do we Dutch always have to speak the languages of the bigger countries :o)
TedvG

···

on Wed Apr 06 2016, Erica Sadun <erica-AT-ericasadun.com <http://erica-at-ericasadun.com/>> wrote:

   On Apr 6, 2016, at 12:16 PM, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
   (0..<199).striding(by: -2)

   are even or odd.

(0..<199).striding(by: -2): 0..<199 == 0...198 Even
(1..<199).striding(by: -2): 1..<199 == 1...198 Even

I understand the logic that got you there, but I find it incredibly
counter-intuitive that striding by 2s over a range with odd endpoints
should produce even numbers... I can't imagine any way I'd be convinced
that was a good idea.

(0..<198).striding(by: -2): 1..<198 == 0...197 Odd
(1..<198).striding(by: -2): 1..<198 == 1...197 Odd

-- E

--

Dave


(Milos Rankovic) #2

If the range does not assume `start >= end`, is it still necessary to also indicate the traversal direction with the sign of the step (`-0.0001`)?

milos

···

On 6 Apr 2016, at 21:08, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

v1 > v2: is allowed and correctly evaluated. e.g. (8.0…-3.14159).by(-0.0001)


(Milos Rankovic) #3

Apologies, I inverted the operator there! This is, of course what I meant:

···

On 6 Apr 2016, at 21:08, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org> wrote:

v1 > v2: is allowed and correctly evaluated. e.g. (8.0…-3.14159).by(-0.0001)

If the range does not assume `start <= end`, is it still necessary to also indicate the traversal direction with the sign of the step (`-0.0001`)?

milos


(Ted van Gaalen) #4

Hello Milos,
Good question
was thinking about this too.
that would imply the ‘by”value should/must always be an absolute value?
however (if it is a var) it cannot be guaranteed to be + or -
that’s why I thought to leave it as is.
?
TedvG

···

On 06.04.2016, at 22:18, Milos Rankovic <milos@milos-and-slavica.net> wrote:

On 6 Apr 2016, at 21:08, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

v1 > v2: is allowed and correctly evaluated. e.g. (8.0…-3.14159).by(-0.0001)

If the range does not assume `start >= end`, is it still necessary to also indicate the traversal direction with the sign of the step (`-0.0001`)?

milos


(Ted van Gaalen) #5

Grrrr Typos… Sorry, here it is again: vital corrections look at<<<<<< <<<<<<<<<

Hi Erica, Dave

Based on what I’ve (not all) read under this topic:

I’d suggest a more complete approach:

Ranges should support: (as yet not implemented)

- All numerical data types (Double, Float, Int, Money***, Decimal*** )
- An arbitrary increment or decrement value.
- Working in the complete - + numerical range
- Allow traversing in both - + directions.

The basics:

    (v1…v2).by(v3) // this still is readable. and can be optimized by the compiler (predictable sequence)

Rules:

    v1, v2, v3 are any numerical type scalar type
    v1, v2, v3 must have the same numerical type.
    v1 > v2: is allowed and correctly evaluated. e.g. (8.0…-3.14159).by(-0.0001)

    The ..< half open operator is no longer allowed. write e.g. 0...ar.count - 1

    "by(…)” is obligatory with floating point range.

           the default “by(…)” value of 1 makes sense only with integer ranges.

valid examples:
    (5…9) // 5 6 7 8 9 Integer range without “by” defaults to 1 as increment value.
    (1...10).by(2) // 1 3 5 7 9.
    (2...10).by(2) // 2 4 6 8 10.
    (4…-4).by(-2) // 4 2 0 -2 -4 . // runs backwards <<<<<<<<<<<<<<<<<<<
    (30..-10).by(-2) // 30 28 26 24 22 ….. -10.
    (10...0).by(-3) // 10 7 4 1.

    (12…-10) // is valid, but returns empty because default increment value = 1

    (12.0…-12.0).by(-1.5) // 12.0 10.5 9.0…. // of course with float imprecision
                                                                       
   invalid examples:

   (23.0..<60.5).by(0.5) // half open ranges are no longer allowed **
  (23.0…60.5) // “ by" is obligatory with floats.
  (14...8).by(0.5) // v1 v2 and v3 don’t have the same numerical type

Half open ranges make no real sense (are not really useful) with floating point values.
and no sense at all with e.g (12..<-1) afaics

At least for float iterations the increment value should not each time be accumulated like
v1 += v3; v1 += v3; v1 += v3; …. // causes float number drift.
but be freshly multiplied with each iteration, e.g. by using an internal iteration counter
like so:

v = v1 + v3 * i++; v = v1 + v3 * i++; v = v1 + v3 * i++; v = v1 + v3 * i++; <<<<<<<<<<<<<<<<<<<<<<<

for reasons of precision.

If one has worked with floating point data more often
awareness of its precision limitations become a second nature conscience.
E.g. it is perfectly acceptable and known (also in classical for-loops) that
(0.0…1.0).by(0.1) is not guaranteed to reach the humanly expected value of 1.0.
This is normal. Due to the nature of what mostly is done in the
floating point numbers domain, this does not often cause a problem
(e.g like working with a slide ruler)
if it is important to reach the “expected end” then one could
-add an epsilon value like so (v1…v2 + 0.1)
-generate the desired float sequence within an integer loop.

The above “range” (imho) improvement makes the
stride.. function/keyword completely unnecessary.

Due to its ability to process reversed ranges as is,
the .reverse() is optional (no longer necessary in most cases,
allowing the compiler to optimize without having to process
it like a collection.

Now that we have a fully functional range, which can do all things desired, one can
then of course, pass this range without any change to a collection based for … e.g.

for v in (v1…v2).by(v3) // optionally add other collection operators/filters/sorts here

(or in any other construct you might see fit)
                                       
This seems to be a reasonable alternative for

- the classical for ;; loop
-the collection-free for-loop
     for v from v1 to v2 by v3

As you know, the latter is what I have been suggesting,
but seemingly does not find much support,
(because I received very little reactions)
making it doubtful if I will ever make a proposal for this for loop.
Anyone out there should I stil do that?

When one does not further extend the range
with filters like sort etc. the compiler can still optimize
a collection-in-between out of the way.
(Thank you Taras, für das Mitdenken. :o)

** note that a half open range would in most cases be unnecessary
      if collection indexes started with 1 instead of 0, e.g. someArray[1…10]
     (but it’s too late to change that.)
     “the color of the first apple?” vs
     “the color of the zeroth (0) ??? apple?” ? Silly isn’t?

*** possible future numerical “native” types

It could be that (at least) partly something similar has already been suggested.
but so much is here of this topic that i can’t see the wood for the trees, so to speak.

Your (all) opinions are appreciated.

kind regards, mit freundlichen Grüssen, Met vriendelijke groeten,
Sigh, why do we Dutch always have to speak the languages of the bigger countries :o)
TedvG

···

on Wed Apr 06 2016, Erica Sadun <erica-AT-ericasadun.com <http://erica-at-ericasadun.com/>> wrote:

   On Apr 6, 2016, at 12:16 PM, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
   (0..<199).striding(by: -2)

   are even or odd.

(0..<199).striding(by: -2): 0..<199 == 0...198 Even
(1..<199).striding(by: -2): 1..<199 == 1...198 Even

I understand the logic that got you there, but I find it incredibly
counter-intuitive that striding by 2s over a range with odd endpoints
should produce even numbers... I can't imagine any way I'd be convinced
that was a good idea.

(0..<198).striding(by: -2): 1..<198 == 0...197 Odd
(1..<198).striding(by: -2): 1..<198 == 1...197 Odd

-- E

--

Dave


(Milos Rankovic) #6

Hi Ted,

that would imply the ‘by”value should/must always be an absolute value?

In a way: Instead of `Strideable.Stride` I would suggest `Strideable.Distance`.

At any rate, leaving the sign to be direction indicator makes it forever necessary for everyone to make this counterintuitive metal gymnastics, since most of the time in life we do not walk backwards, even when we are returning back whence we came from!

What do you think?

milos

···

On 6 Apr 2016, at 21:34, Ted F.A. van Gaalen <tedvgiosdev@gmail.com> wrote:

Hello Milos,
Good question
was thinking about this too.
that would imply the ‘by”value should/must always be an absolute value?
however (if it is a var) it cannot be guaranteed to be + or -
that’s why I thought to leave it as is.
?
TedvG

On 06.04.2016, at 22:18, Milos Rankovic <milos@milos-and-slavica.net <mailto:milos@milos-and-slavica.net>> wrote:

On 6 Apr 2016, at 21:08, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

v1 > v2: is allowed and correctly evaluated. e.g. (8.0…-3.14159).by(-0.0001)

If the range does not assume `start >= end`, is it still necessary to also indicate the traversal direction with the sign of the step (`-0.0001`)?

milos


(Ted van Gaalen) #7

Hi Milos

Yes, (v1…v2).by(v3) it can determine the going of either in + or - direction,
but only at run time! because the contents of v1, v2, v3 are of course unknown at compile time.
Ergo: it cannot expected be an absolute value.

however, I suggested (coded) that here on 4.3.2016… Works in Playground Xcode 7.3
look in the struct its “init” where the direction is determined
by wether “from” or “to” is bigger”
implemented like this (and also with a floating point number tolerance)
(The struct should be numerical generic, but I didn’t manage to change it to generic,)
Anyway, should be compilerized/hand coded in asm perhaps (I can’t do that)

TedvG

public struct StriderGenerator : GeneratorType
{
    private let low: Double
    private let high: Double
    private var step : Double
    private var tol : Double

    private var iterator = 0

    private let moveForward: Bool
    
    private var done = false
        
    public init(from: Double, to: Double, by: Double, tolerance: Double)
    {
        step = by
        if from < to
        {
            low = from
            high = to
            moveForward = true
        }
        else
        {
            low = to
            high = from
            moveForward = false
        }
        self.tol = tolerance * 0.5 // center it.
    }
    
    /// return next value or nil, if no next
    /// element exists.
    
    public mutating func next() -> Double?
    {
        let current:Double
        if done
        {
            return nil
        }
        
        if moveForward
        {
            current = low + Double(iterator) * step
        }
        else
        {
            current = high - Double(iterator) * step
        }
        iterator += 1
        
        // done if exceeding low or high limits + tolerance
        
        done = current > high + tol ||
               current < low - tol
        
        if done
        {
            return nil
        }
        else
        {
            return current
        }
    }
}

public struct Strider : SequenceType // Aragorn
{
    private let start: Double
    private let end: Double
    private let step: Double
    private let tol: Double

    init(from: Double, to: Double, by: Double, tolerance : Double)
    {
        _precondition(by > 0.0 ,
            "Init of struct Strider: 'by:...' value must be > 0.0.")
        _precondition(abs(by) > tolerance,
            "Init of struct Strider: 'by:...' value must be > tolerance.")
        _precondition(tolerance >= 0.0,
            "Init of struct Strider: tolerance:... value must be >= 0.0")
        
        start = from
        end = to;
        step = by
        tol = tolerance
    }
    
    /// Return a *generator* over the elements of this *sequence*.
    
    public func generate() -> StriderGenerator
    {
        return StriderGenerator(from: start, to: end, by: step, tolerance: tol)
    }
}

public extension Double
{
    
    public func strider(to to: Double, by: Double, tolerance: Double ) -> Strider
    {
        return Strider( from: self, to: to, by: by, tolerance: tolerance)
    }
}

print("Testing the new .strider extension")

let testvalues =
[
    // fr: to: by: tolerance:
    [ 0.0, 5.0, 1.0, 0.0 ],
    [-3.0, 4.0, 0.12, 0.1 ],
    [ 2.0, -1.0, 0.34, 0.1 ],
    [ 0.001, -0.002, 0.0001, 0.00001 ]
]

for parm in testvalues
{
    
    print("==============Stride from: \(parm[0]) to: \(parm[1]) by: \(parm[2]) tolerance: \(parm[3])\n")
    
    for val in parm[0].strider(to: parm[1], by: parm[2], tolerance: parm[3])
    {
        print("\(val) ", terminator:"")
    }
    print("\n\n")
}

TedvG

···

On 06.04.2016, at 23:15, Milos Rankovic <milos@milos-and-slavica.net> wrote:

Hi Ted,

that would imply the ‘by”value should/must always be an absolute value?

In a way: Instead of `Strideable.Stride` I would suggest `Strideable.Distance`.

At any rate, leaving the sign to be direction indicator makes it forever necessary for everyone to make this counterintuitive metal gymnastics, since most of the time in life we do not walk backwards, even when we are returning back whence we came from!

What do you think?

milos

On 6 Apr 2016, at 21:34, Ted F.A. van Gaalen <tedvgiosdev@gmail.com <mailto:tedvgiosdev@gmail.com>> wrote:

Hello Milos,
Good question
was thinking about this too.
that would imply the ‘by”value should/must always be an absolute value?
however (if it is a var) it cannot be guaranteed to be + or -
that’s why I thought to leave it as is.
?
TedvG

On 06.04.2016, at 22:18, Milos Rankovic <milos@milos-and-slavica.net <mailto:milos@milos-and-slavica.net>> wrote:

On 6 Apr 2016, at 21:08, Ted F.A. van Gaalen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

v1 > v2: is allowed and correctly evaluated. e.g. (8.0…-3.14159).by(-0.0001)

If the range does not assume `start >= end`, is it still necessary to also indicate the traversal direction with the sign of the step (`-0.0001`)?

milos


(Ted van Gaalen) #8

Hi All.

nearly no one has yet reacted on my mail, echoed here again,

which leaves by me the following questions pending:

-does what I define and describe completely cover all the required functionality
for ranges for all numerical types?

-does it eliminate the need for using “Stride(...)” at least for numerical scalars?

-combined with for .. in… , is it an adequate replacement for the classical for loop?

-can we eliminate half open operators? Because imho that would simplify things greatly.
          -to which text of Dijkstra is this related?

Objections?
improvements?
Things that I have missed?
Feasibility?
Implementable?

Thanks
TedvG

···

On 06.04.2016, at 22:43, Ted F.A. van Gaalen <tedvgiosdev@gmail.com> wrote:

Hi Erica, Dave

Based on what I’ve (not all) read under this topic:

I’d suggest a more complete approach:

Ranges should support: (as yet not implemented)

- All numerical data types (Double, Float, Int, Money***, Decimal*** )
- An arbitrary increment or decrement value.
- Working in the complete - + numerical range
- Allow traversing in both - + directions.

The basics:

    (v1…v2).by(v3) // this still is readable. and can be optimized by the compiler (predictable sequence)

Rules:

    v1, v2, v3 are any numerical type scalar type
    v1, v2, v3 must have the same numerical type.
    v1 > v2: is allowed and correctly evaluated. e.g. (8.0…-3.14159).by(-0.0001)

    The ..< half open operator is no longer allowed. write e.g. 0...ar.count - 1

    "by(…)” is obligatory with floating point range.

           the default “by(…)” value of 1 makes sense only with integer ranges.

valid examples:
    (5…9) // 5 6 7 8 9 Integer range without “by” defaults to 1 as increment value.
    (1...10).by(2) // 1 3 5 7 9.
    (2...10).by(2) // 2 4 6 8 10.
    (4…-4).by(-2) // 4 2 0 -2 -4 . // runs backwards <<<<<<<<<<<<<<<<<<<
    (30..-10).by(-2) // 30 28 26 24 22 ….. -10.
    (10...0).by(-3) // 10 7 4 1.

    (12…-10) // is valid, but returns empty because default increment value = 1

    (12.0…-12.0).by(-1.5) // 12.0 10.5 9.0…. // of course with float imprecision
                                                                       
   invalid examples:

   (23.0..<60.5).by(0.5) // half open ranges are no longer allowed **
  (23.0…60.5) // “ by" is obligatory with floats.
  (14...8).by(0.5) // v1 v2 and v3 don’t have the same numerical type

Half open ranges make no real sense (are not really useful) with floating point values.
and no sense at all with e.g (12..<-1) afaics

At least for float iterations the increment value should not each time be accumulated like
v1 += v3; v1 += v3; v1 += v3; …. // causes float number drift.
but be freshly multiplied with each iteration, e.g. by using an internal iteration counter
like so:

v = v1 + v3 * i++; v = v1 + v3 * i++; v = v1 + v3 * i++; v = v1 + v3 * i++; <<<<<<<<<<<<<<<<<<<<<<<

for reasons of precision.

If one has worked with floating point data more often
awareness of its precision limitations become a second nature conscience.
E.g. it is perfectly acceptable and known (also in classical for-loops) that
(0.0…1.0).by(0.1) is not guaranteed to reach the humanly expected value of 1.0.
This is normal. Due to the nature of what mostly is done in the
floating point numbers domain, this does not often cause a problem
(e.g like working with a slide ruler)
if it is important to reach the “expected end” then one could
-add an epsilon value like so (v1…v2 + 0.1)
-generate the desired float sequence within an integer loop.

The above “range” (imho) improvement makes the
stride.. function/keyword completely unnecessary.

Due to its ability to process reversed ranges as is,
the .reverse() is optional (no longer necessary in most cases,
allowing the compiler to optimize without having to process
it like a collection.

Now that we have a fully functional range, which can do all things desired, one can
then of course, pass this range without any change to a collection based for … e.g.

for v in (v1…v2).by(v3) // optionally add other collection operators/filters/sorts here

(or in any other construct you might see fit)
                                       
This seems to be a reasonable alternative for

- the classical for ;; loop
-the collection-free for-loop
     for v from v1 to v2 by v3

As you know, the latter is what I have been suggesting,
but seemingly does not find much support,
(because I received very little reactions)
making it doubtful if I will ever make a proposal for this for loop.
Anyone out there should I stil do that?

When one does not further extend the range
with filters like sort etc. the compiler can still optimize
a collection-in-between out of the way.
(Thank you Taras, für das Mitdenken. :o)

** note that a half open range would in most cases be unnecessary
      if collection indexes started with 1 instead of 0, e.g. someArray[1…10]
     (but it’s too late to change that.)
     “the color of the first apple?” vs
     “the color of the zeroth (0) ??? apple?” ? Silly isn’t?

*** possible future numerical “native” types

It could be that (at least) partly something similar has already been suggested.
but so much is here of this topic that i can’t see the wood for the trees, so to speak.

Your (all) opinions are appreciated.

kind regards, mit freundlichen Grüssen, Met vriendelijke groeten,
Sigh, why do we Dutch always have to speak the languages of the bigger countries :o)
TedvG

on Wed Apr 06 2016, Erica Sadun <erica-AT-ericasadun.com <http://erica-at-ericasadun.com/>> wrote:

   On Apr 6, 2016, at 12:16 PM, Dave Abrahams via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
   (0..<199).striding(by: -2)

   are even or odd.

(0..<199).striding(by: -2): 0..<199 == 0...198 Even
(1..<199).striding(by: -2): 1..<199 == 1...198 Even

I understand the logic that got you there, but I find it incredibly
counter-intuitive that striding by 2s over a range with odd endpoints
should produce even numbers... I can't imagine any way I'd be convinced
that was a good idea.

(0..<198).striding(by: -2): 1..<198 == 0...197 Odd
(1..<198).striding(by: -2): 1..<198 == 1...197 Odd

-- E

--

Dave