The maze book for programmers!

Algorithms, circle mazes, hex grids, masking, weaving, braiding, 3D and 4D grids, spheres, and more!

DRM-Free Ebook

The Buckblog

assorted ramblings by Jamis Buck

Reducing a Number to Its Sign

5 August 2015 — A simple technique is presented for extracting the sign of a number — 2-minute read

When implementing certain kinds of algorithms, I often seem to find myself needing to move a token a given number of steps toward some location. In the “Back from the Klondike” puzzle described previously, for example, I needed to verify that a given path between two cells was valid, which I did by walking the path between those cells.

To do that, I typically compute the difference between the two locations to get a vector, and then assign each component of the vector to dx and dy variables. If dx is positive, x will increment by one each step. If dx is negative, x will decrement by one at each step, instead. Likewise for dy and y, and if either is zero, that coordinate will not change.

In the past, I’ve implemented this calculation like this:

dx = to_x - from_x
dy = to_y - from_y

if dx > 0
  dx = 1
elsif dx < 0
  dx = -1

if dy > 0
  dy = 1
elsif dy < 0
  dy = -1

This works, but it is annoyingly verbose. All I want, is for dx and dy to be +1 or -1 or 0, depending on the sign of the difference between the two locations.

This brought to mind Ruby’s <=> operator (sometimes affectionately referred to as the “spaceship” operator). As implemented on integers and floating point numbers, a <=> b will return -1 if a is less than b, +1 if a is greater than b, and 0 if they’re equal. (This is used by the Comparable module to allow you to define how your own objects ought to be compared against each other.)

Using this operator, we can implement the above logic like this, now:

dx = (to_x - from_x) <=> 0
dy = (to_y - from_y) <=> 0

Lovely! I love it when an overly verbose bit of code condenses down to a one-liner. Still, because <=> is not a commonly seen operator, if you use this trick you might want to add a comment to indicate what the purpose of the code is, or even better, encapsulate it in a method:

# Numeric is the superclass of Float and Integer
class Numeric
  # -1 if self is negative
  #  0 if self is zero
  # +1 if self is positive
  def sign
    self <=> 0

That snippet becomes even simpler (and more self-documenting) now:

dx = (to_x - from_x).sign
dy = (to_y - from_y).sign

Viva la Ruby!