Skip to content

Instantly share code, notes, and snippets.

@darius
Created March 29, 2023 14:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darius/0d0c6820af4e4a9f70b96a71a9a644b5 to your computer and use it in GitHub Desktop.
Save darius/0d0c6820af4e4a9f70b96a71a9a644b5 to your computer and use it in GitHub Desktop.
withal@gmail.com
Binary search is not helpful here because, in the original nested loop, the total count of all the inner loop steps is going to be just n. But maybe we can simplify that code still: try rewriting it by fusing the cdf-computing loop with the (incf index) loop. Do you see what I'm getting at?
I see what you're getting at now. You want to compute the cdf and perform the resampling step in a single loop, effectively removing the need for the inner loop. Here's a version of the function that combines the two steps:
lisp
Copy code
(defun systematic-resample/normalized (weights)
(loop with n = (length weights)
with sum-weights = 0d0
with index = 0
with u = (random (/ 1d0 n))
for weight in weights
do (progn
(incf sum-weights weight)
(when (> u sum-weights)
(incf index)
(setf u (+ u (/ 1d0 n)))))
collect index))
In this version of the function, we removed the inner loop and combined the cdf computation with the resampling step. We maintain a running sum of the weights (sum-weights), and update index as we iterate through the weights. This should make the code more efficient and easier to understand.
withal@gmail.com
Try writing the loop as a `reduce`?
Sure, here's a version of the systematic resampling function using reduce instead of loop. Note that we need to use reduce with the :accumulate keyword argument so we can build the list of indices as we go:
lisp
Copy code
(defun systematic-resample/normalized (weights)
(reduce (lambda (state weight)
(destructuring-bind (sum-weights index u indices) state
(incf sum-weights weight)
(when (> u sum-weights)
(incf index)
(setf u (+ u (/ 1d0 (length weights)))))
(values sum-weights index u (cons index indices))))
weights
:initial-value (list 0d0 0 (random (/ 1d0 (length weights))) '())
:accumulate t))
In this version, we use reduce with a lambda function that takes the current state as its first argument and the next weight as its second argument. The state is a list consisting of sum-weights, index, u, and the list of indices indices. We update the state in the lambda function and return the new state. Note that the indices will be in reverse order due to using cons to accumulate them. If you want them in the original order, you can use (reverse indices) as the final output.
withal@gmail.com
Oh, well, that turns out less clear in Lisp. Do you have any other ideas to code this elegantly?
Here's an alternative version using the loop macro, which should be more readable:
lisp
Copy code
(defun systematic-resample/normalized (weights)
(let ((n (length weights))
(sum-weights 0d0)
(index 0)
(u (random (/ 1d0 n)))
(indices '()))
(loop for weight in weights
do (progn
(incf sum-weights weight)
(while (> u sum-weights)
(push index indices)
(setf u (+ u (/ 1d0 n)))
(incf index))))
(nreverse indices)))
In this version, we first initialize the variables sum-weights, index, u, and indices outside the loop. Then we use a single loop to iterate through the weights. Inside the loop, we update sum-weights, and then we use a while loop to handle the condition when u is greater than sum-weights. This allows us to avoid using nested loops and makes the code easier to read.
Note that the list of indices is built in reverse order using push, so we need to use nreverse at the end to get the indices in the original order.
withal@gmail.com
One more idea to try: use the `series` package.
Using the series package, you can express the systematic resampling algorithm in a more functional style. First, you need to load the series package:
lisp
Copy code
(require 'series)
(use-package :series)
Then, you can rewrite the systematic-resample/normalized function as follows:
lisp
Copy code
(defun systematic-resample/normalized (weights)
(let* ((n (length weights))
(cdf (collect-sum (scan weights)))
(u-start (random (/ 1d0 n)))
(us (collect (scan-ivectors n :initial-value u-start :generator #'(lambda (i u) (+ u (/ 1d0 n)))))))
(collect-nth (scan cdf)
:test #'(lambda (cdf-element u) (> u cdf-element))
:input t
:allow-other-keys t
:series us)))
This implementation uses scan, collect, and collect-sum functions from the series package to create a more functional and concise implementation.
We compute the cumulative distribution function cdf by summing the weights with the collect-sum function.
We generate the uniform random numbers us by using scan-ivectors with an initial value u-start and a generator function that adds (/ 1d0 n) to the previous value.
Finally, we use collect-nth to resample the indices based on the cdf and us. The :test function checks if u is greater than the current cdf-element.
This implementation avoids using nested loops and mutable state, making the code more elegant and easier to understand.
withal@gmail.com
Thanks. I'm unfamiliar with most of `series`, so that was interesting to see.
You're welcome! The series package is a powerful library for Common Lisp that provides a functional and concise way to manipulate sequences and streams of data. It is particularly useful for expressing complex algorithms and data transformations in a more declarative and composable manner.
If you're interested in learning more about series, I recommend checking out the official documentation:
Series Reference Manual
Series User's Manual
These resources provide a comprehensive overview of the library, its functions, and how to use it effectively in your programs.
If you have any more questions or need further clarification, feel free to ask!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment