0

I'm currently trying to write a function from a list of points that returns the distance from a point p to a point in my point list that is farthest away from p. My list of points are the following:

((2 . 4) (3 . 6) (5 . 12) (-4 . 3) (8.4 . 9) (0 . -1))

I have also made some abstractions to retrieve the general car and cdr (for easier visibility in the code), as well as the car and cdr of the list itself.

(define (get-x p) (car p)
(define (get-y p) (car p)


(define (get-first-point pt-list)
        (get-x pt-list))

(define (get-rest-points pt-list)
        (get-y pt-list))                                                                                         

I have also already written out a generalized distance formula that I can use for any two points that I choose (keep in mind that I abstracted car and cdr to be get-x and get-y respectively).

(define (distance a b)
    (if (or (null? a) (null? b))
        0
        (sqrt (+ (expt (- (get-x a)
                          (get-x b))  2) 
                 (expt (- (get-y a) 
                          (get-y b))  2)))))

Now that I have this, I know that I need to compare the various distances throughout my entire set of points and choose the maximum return value. I have a partial solution, but not one that's correct for every point.

(define (max-distance p pt-list)
    (if (null? pt-list)
        0
        (max (distance p (get-first-point pt-list)) ; 1
             (distance p (get-first-point (get-rest-points pt-list))) ; 2
             (distance p (get-first-point (get-rest-points (get-rest-points pt-list)))) ; 3
             (distance p (get-first-point (get-rest-points (get-rest-points (get-rest-points pt-list))))) ; 4
             (distance p (get-first-point (get-rest-points (get-rest-points (get-rest-points (get-rest-points pt-list)))))) ; 5
             (distance p (get-first-point (get-rest-points (get-rest-points (get-rest-points (get-rest-points (get-rest-points pt-list))))))) ; 6
    )
)

)

You can probably get the gist of what I'm (horribly) attempting at doing, but this is why I need the help.

user2789945
  • 527
  • 2
  • 6
  • 23
  • 1
    In `(define (get-x p) (car p)` and `(define (get-y p) (car p)`, one of those should probably be `cdr`. And even though `get-x` and `get-y` will be the same as `car` and `cdr`, you're not using them as list functions, so `(define (get-first-point pt-list) (get-x pt-list))` really isn't good practice. The idea behind aliasing functions like this is so that you can _change your data structures later_. It's not reasonable to assume that points and lists will always have the same representation, so the accessors for one shouldn't be defined in terms of the accessors for the other. – Joshua Taylor Oct 23 '13 at 15:45

3 Answers3

3

This is a case where you want to fold a function f over your list of points. The function f should take a distance d and point x and return the maximum d and the distance between x and your special designated point p. I've described folds in a bit more detail in some other answers:

The main point is that in a fold, you take a function, an initial value, and a list. You apply the function to the first element of the list and the initial value to produce some new value. Now you recursively fold with the the same function, the new value, and the rest of the list. This is essentially a loop like this:

current value = initial value
while list is not empty
     current value = result of function called with first element of list and current value
     list = rest of list
return current value

In Scheme, where tail call optimization is guaranteed, it is that loop. In your case, you just need to determine what the initial value should be, and what the function should be. Since distances are always non-negative, a reasonable initial value is zero. The function is a bit more complicated. current value will be a distance, but the first element of list will be a point. The result of the function needs to be a new distance, and the new distance should be the maximum of the distance current value and the distance between some the point from list and the special point. In Racket, this kind of fold is called foldl, so we end up with something like this:

(define special-points '((2 . 4) (3 . 6) (5 . 12) (-4 . 3) (8.4 . 9) (0 . -1)))

(define (distance a b)
  (let ((square (lambda (x)
                  (* x x))))
    (sqrt (+ (square (- (car a) (car b)))
             (square (- (cdr a) (cdr b)))))))

(define (max-distance point points)
  (foldl (lambda (current-point current-distance)
           (max current-distance
                (distance point current-point)))
         0
         points))
> (max-distance '(4 . 12) special-points)
13.601470508735444
> (max-distance '(8 . 8) special-points)
13.0
> (max-distance '(2 . 5) special-points)
7.615773105863909

If you're not using Racket, you'll have to write your own foldl, but it's really not very hard. (This actually isn't quite as sophisticated as Racket's foldl, which can take any positive number of lists, but it will work for this case.)

(define (foldl function initial-value list)
  (if (null? list)
      initial-value
      (foldl function
             (function (car list) initial-value)
             (cdr list))))
Community
  • 1
  • 1
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
1

get-yshould use cdr, not car.

get-x and get-y miss a closing parenthesis.

For max-distance, I'd go for

(define (max-distance p pt-list)
  (apply max (map (lambda (x) (distance p x)) pt-list)))

which means that you don't need get-first-point and get-rest-points.

Illustration (using (1 . 1) as p):

> (map (lambda (x) (distance '(1 . 1) x)) '((2 . 4) (3 . 6) (5 . 12) (-4 . 3) (8.4 . 9) (0 . -1)))
'(3.1622776601683795 5.385164807134504 11.704699910719626 5.385164807134504 10.897706180660222 2.23606797749979)

> (apply max (map (lambda (x) (distance '(1 . 1) x)) '((2 . 4) (3 . 6) (5 . 12) (-4 . 3) (8.4 . 9) (0 . -1))))
11.704699910719626

Depending on your definition of "farthest" you may want to include abs in the expression.

uselpa
  • 18,732
  • 2
  • 34
  • 52
  • This is a better use case for a `reduce`/`fold` than a `(apply ... (map ...))`. – Joshua Taylor Oct 23 '13 at 16:52
  • (Good point; I should has mentioned it in my original comment.) The version with `apply` and `map` needlessly creates an intermediate list the same length as the list of points, which can use up lots of extra space if there are lots of points (and it's unnecessary space, no matter how much), and using `apply` with very large argument lists could also be a problem. Using `foldl` avoids the intermediate storage space and the potential problems with long argument lists and `apply`. – Joshua Taylor Oct 23 '13 at 18:32
  • In Common Lisp (which is not Scheme, so it's not directly applicable) there's a constant [`call-arguments-limit`](http://www.lispworks.com/documentation/HyperSpec/Body/v_call_a.htm) that defines the limit of arguments that a function can be called with. Using `apply` with long lists is an easy way to run up against this. I don't know if there's a similar limit in Scheme, but the same technical implementation details would suggest that it's reasonable to try to avoid it. That said, the intermediate space usage is really the big reason. – Joshua Taylor Oct 23 '13 at 18:41
  • @JoshuaTaylor Agreed. I'll nevertheless leave my example as is because map and apply are imo easier to understand for the OP than fold. – uselpa Oct 23 '13 at 18:47
  • It took a while to find a definite link, but in SFRI 13, there's [a comment about `string-concatenate`](http://srfi.schemers.org/srfi-13/srfi-13.html#string-concatenate) that says: "Note that the `(apply string-append string-list)` idiom is not robust for long lists of strings, as some Scheme implementations limit the number of arguments that may be passed to an n-ary procedure." I wish I could find a more authoritative source on that, though, e.g., the minimum number of arguments that are guaranteed to be accepted. – Joshua Taylor Oct 23 '13 at 18:47
  • Yes, I do think you should leave it in place, too. It's fairly easy to understand, and it is mathematically correct. I do hope that ["professional and enthusiast programmers"](http://meta.stackexchange.com/q/202476/225437) will prefer the `foldl` version, though. – Joshua Taylor Oct 23 '13 at 18:50
  • SFRI 1 [notes](http://srfi.schemers.org/srfi-1/srfi-1.html#concatenate!) about concatenate: "Note that some Scheme implementations do not support passing more than a certain number (e.g., 64) of arguments to an n-ary procedure. In these implementations, the (apply append ...) idiom would fail when applied to long lists, but concatenate would continue to function properly." – Joshua Taylor Oct 23 '13 at 18:59
  • @JoshuaTaylor Thank you for your well-researched comments, they are a pleasure to read! – uselpa Oct 23 '13 at 19:14
1

Compute the distance to the car of points. Either that distance is the maximum or the maximum from the cdr of points is. Result is a simple recursive algorithm traversing the list of points.

(define (max-distance-p p points)
  (if (null? points)
      0
      (max (distance p (car points))
           (max-distance-p p (cdr points)))))

The 0 is returned if you pass in points as an empty list; that might be a bit questionable. If so:

(define (max-distance-p p points)
  (assert (not (null? points)))
  (if (null? (cdr points))
      (distance p (car points))
      (max (distance p (car points))
           (max-distance-p p (cdr points)))))
GoZoner
  • 67,920
  • 20
  • 95
  • 145