We are starting off from the Efficient Solution published here:,
basically just reimplementing it.
→ https://www.geeksforgeeks.org/place-k-elements-such-that-minimum-distance-is-maximized/
Open it in another tab as there will be references made to it in
the below text.
Given an array representing n positions along a straight line. Find k (where k ≤ n) elements from the array such that the minimum distance between any two (consecutive points among the k points) is maximized.
For a reason that maybe the original may need to explain, we
process the input sorted and such will be exhibited in below
examples. The original has its implementations start with a call
to a sorting subroutine.
Examples:
Consider the isFeasible function from the GeeksForGeeks' Radhav
Jajodia's original. It is not too clear immediately to most and
requires a different approach to explaining it than the original.
It accepts arguments:
Going through the sorted numbers from start to end, it counts
(starting with the first one) the ones that are at least mid
distance away from the previously counted one. When the count
reaches k, it returns true. If it never does, it
returns false.
Going through the sorted numbers from start to end, it counts
them omitting the ones that were within <mid
distance from the previously counted one (less than mid
units away from it). When the count reaches k,
it returns true. If it never does, it returns false.
The function is utilized by the main algorithm in the following
manner: a binary search over possible minimum distances is
performed, with the boundaries being: 1 (lower boundary) and the
upper boundary being… the value of the greatest number.
I believe that is incorrect as suboptimal and the correct upper
boundary should be at most the distance (i.e. difference) before
the least and the greatest element. We could also further narrow
that down to maybe that distance divided by k, for an
arithmetic average.
The original is explaining why is the lower boundary one:
left is initialized with 1 and not with arr[0] because, minimum distance between each element can be one and not arr[0]. consider this example:
arr[] = {9,12} and you have to place 2 element then left = arr[0] will force the function to look the answer between range arr[0] to arr[n-1], i.e 9 to 12, but the answer is 3 so It is required that you initialize the left with 1.
Each check of the binary search is made at the integer
arithmetic average (assigned to mid) of the current
set lower and upper boundaries (i.e. integer division-by-two of
the sum of them two, which means floored in
case of positive numbers), starting with the
aforementioned initial ones. A check is a call to isFeasible —
The checks stop when the boundaries will become equal or nonsense
(lower greater than the upper), and the last mid that
isFeasible returned true for is returned.
Let's consider the check previous to the end condition being
reached. In case of {1 2 3 4}, we get the average 2 and we can get
either a 2+1=3 and a {3 4}, or a 2 and a {1 2}. With {1 2 3 4 5},
we get 3 and thus either 3+1=4 and {4 5} or 3 and {1 2 3}. From
our acquaintance with the properties of arithmetic average we know
that these ranges can be transposed by any positive offset for
these findings of ours to hold true. It becomes apparent that it
must be either {1 2 3} or {1 2}.
We thus found that the boundaries, if we didn't err, never become
nonsense, and the loop end condition could be (albeit less safely
for the case we now erred) considered to be equality.
Let's have a function that returns an array of all the distances
between elements, in order.
(defun just-distances (arr) (if (null (cdr arr)) nil (cons (- (cadr arr) (car arr)) (just-distances (cdr arr))))) |
CL-USER> (just-distances '(1 2 5 7 11 12)) |
We want to look at the distances that are at least mid.
Going over the not-far-enough can be otherwise interpreted as
summing distances. Let's write a function that will sum the
adjoining distances.
(defun remove-too-close (mid arr) |
CL-USER> (remove-too-close 1 (just-distances '(1 2 5 7 11 12))) |
We can see that the number of distances ("edges") is one less
than the number of our "nodes". We can thus see that
given mid |
best k |
---|---|
1 |
6 |
2 |
4 |
3 |
3 |
4 |
3 |
5 |
3 |
6 |
2 |
The distances could be replaced with a data structure (distance
. (left . right))
along with a summing function to
replace +
: (cons (+ (car a) (car
b)) (cons (cadr a) (cddr b)))
.
But that's more useful for maybe debugging, as it makes the code a
bit more bloated and for practical purposes the elements can be
recovered by taking the leftmost element and adding the distances
to it.
The equivalent of the function isFeasible would be taking the
result of remove-too-close and see if its length is
greater or equal to k-1.
What the above code needlessly does is calculating the distances
and summing them beyond when k elements got already found. This is
because we used eagerly evaluated lists of Common Lisp. We need to
introduce laziness. While Common Lisp seems to have more recent
standards introducing language constructs for lazy sequences, the
most plain usual way to achieve laziness in sequences in Common
Lisp has been having zero-argument lambdas as CDR.
So instead of (first . rest)
it is now going to be
(first . (lambda () rest))
.
before |
after |
|
---|---|---|
(defun just-distances (arr) (if (null (cdr arr)) nil |
(defun just-distances (arr) |
|
(defun funcall? (x) |
||
(defun remove-too-close (mid arr) |
(defun remove-too-close (mid arr) |
|
|