slice with negative "int-length"

Started by lyl, April 23, 2019, 01:46:05 AM

Previous topic - Next topic

lyl

I don't quite understand how the function slice works with negative "int-length".

The following example comes from the manual:
(slice '(a b c d e f) 2 -2)  → (c d)
As explained in manual:
QuoteIf int-length is negative, slice will take the parameter as offset counting from the end and copy up to that offset

As my view, -2 seems to point to "e"(-1 points to "f"), so why is not the result  (c d e)?



And how to design a function like this:
(myslice '(0 1 2 3 4 5) 2 -2) ->(1 2)
(myslice '(0 1 2 3 4 5) 4 -3) ->(2 3 4)
(myslice '(0 1 2 3 4 5) 4 -1) ->(4)
(myslice "newLISP" 4 -2) ->"LI"

That means the negative symbol will cause myslice select elements in opposite direction.

rickyboy

#1
Maybe it would have been better if the manual had said instead: "If int-length is negative, slice will take the parameter as offset counting from the end and copy up to but not including that offset."



As far as the answer to your second question, I believe that all you need to do is transform the index and length arguments, when the length argument is negative.


(define (myslice xs idx len)
  (if (< len 0)
      (slice xs (+ 1 idx len) (abs len))
      (slice xs idx len)))

Here's another version with implicit slicing.  It's supposed to be faster, but arguably less readable.


(define (myslice xs idx len)
  (if (< len 0)
      ((+ 1 idx len) (abs len) xs)
      (idx len xs)))

Beware, though, when your calling code exceeds the ends of the string or list argument -- sometimes you get errors, other times it wraps.


> (myslice "newLISP" 4 -6)
"P"
> (myslice "newLISP" 4 -7)
"SP"
> (myslice "newLISP" 4 -75)

ERR: invalid string index
called from user function myslice

Of course, you can add checks for these conditions to myslice, if you want.
(λx. x x) (λx. x x)

rickyboy

#2
We might want to add one more thing.  Notice that we can omit the length argument in slice.


> (slice '(0 1 2 3 4 5) 3)
(3 4 5)
> (slice '(0 1 2 3 4 5) -3)
(3 4 5)

But not so with our version of myslice (from the previous post).


> (myslice '(0 1 2 3 4 5) 3)

ERR: value expected in function + : len
called from user function myslice

But that's just a small fix away.


(define (myslice xs idx (len (if (< idx 0)
                                 (- idx)
                                 (- (length xs) idx))))
  (if (< len 0)
      (slice xs (+ 1 idx len) (abs len))
      (slice xs idx len)))

Check:


> (myslice '(0 1 2 3 4 5) 3)
(3 4 5)
> (myslice '(0 1 2 3 4 5) -3)
(3 4 5)

Now, let's regression test the negative length code (which is what you were really after) to convince ourselves that this fix didn't break that part of the code.


> (myslice '(0 1 2 3 4 5) 2 -2)
(1 2)
> (myslice '(0 1 2 3 4 5) 4 -3)
(2 3 4)
> (myslice '(0 1 2 3 4 5) 4 -1)
(4)
> (myslice "newLISP" 4 -2)
"LI"

Seems to be OK.
(λx. x x) (λx. x x)

lyl

#3
Thank you so much for your help @rickboy!! I learned a lot from you.

Based on your idea, I construct "myslice" as below:


(define (myslice xs
        idx
        (len (if (< idx 0)
   (- idx)
   (- (length xs) idx))))
 
  (if (or (>= idx (length xs)) (< idx (- (length xs))))
      (if (list? xs) '()
 (string? xs) "")

      (begin
(setq idx (if (>= idx 0) idx (+ idx (length xs))))
(if (< len 0)
   (slice xs
    (let (new-idx (+ 1 idx len)) (if (>= new-idx 0) new-idx 0))
    (let (abs-len (abs len)) (if (<= abs-len (+ 1 idx)) abs-len (+ 1 idx)))
  )
   (slice xs idx len)
   )
)
      )
  )


;; test
(setq a '(0 1 2 3 4 5))
(setq b "Hello World")
(myslice a 2) ;;->(2 3 4 5)
(myslice a 3 2) ;;->(3 4)
(myslice a 3 4) ;;->(3 4 5)
(myslice a 3 -4) ;;->(0 1 2 3)
(myslice a 3 -5) ;;->(0 1 2 3)
(myslice a 6 -2) ;;->()
(myslice a -7 2) ;;->()
(myslice a 8 -2) ;;->()
(myslice a -3 2) ;;(3 4)
(myslice a -3 -3) ;;(1 2 3)
(myslice a -3 -8) ;;(0 1 2 3)
(myslice b 4 3) ;;->"o W"
(myslice b 4 12) ;;->"o World"
(myslice b -4 -6) ;;-> "llo Wo"
(myslice b -4 -18) ;;-> "Hello Wo"
(myslice b 20 6) ;;-> ""

rickyboy

#4
Ah, I see what you did -- you put in the runtime checks on the bounds of the argument values.  Very nice!



I'd suggest commenting it a bit.  I do this in my own code when I believe there is a chance that it will take me more than 5 seconds to re-parse and understand it when I return to read it in the future -- and I have noticed that my future self always thanks my past self for doing it! :D


(define (myslice xs idx (len (if (< idx 0)
                                 (- idx)
                                 (- (length xs) idx))))
  (if ;; 1. For degenerate `idx` cases, return "empty", rather than
      ;; signalling an exception.
      (or (>= idx (length xs))
          (< idx (- (length xs))))
      (if (list? xs)   '()
          (string? xs) "")
      ;; 2. Normal `idx` cases
      (let (;; ensure `idx` is in positive form, before we proceed
            ;; further.
            idx (if (>= idx 0)
                    idx
                    (+ idx (length xs))))
        (if (< len 0)
            (slice xs
                   ;; ensure that the index argument is not out of
                   ;; bounds.
                   (let (new-idx (+ 1 idx len))
                     (if (>= new-idx 0) new-idx 0))
                   ;; ensure that the length argument is not out of
                   ;; bounds.
                   (let (abs-len (abs len))
                     (if (<= abs-len (+ 1 idx)) abs-len (+ 1 idx))))
            (slice xs idx len)))))
(λx. x x) (λx. x x)

lyl

#5
@rickyboy Really really appreciate your works on my post and your valueable suggestions!!