About Me

My photo
Ravi is an armchair futurist and an aspiring mad scientist. His mission is to create simplicity out of complexity and order out of chaos.

Saturday, July 2, 2011

Hyperwebster - an uncountable dictionary


Introduction
There are an infinite number of points on the real line. In fact, there are more points on any segment of the real line, no matter how small, than all of natural numbers combined. This was proved by Cantor using the diagonal argument.

To get an idea of the number of points on a real line, Dr. Ian Stewart gave the following construction of an infinite dictionary, the "hyperwebster".

Construction of the Hyperwebster


An enterprising publishing company decides to print a book containing all the words that can possibly created from the English alphabet A-Z. Since it is really a collection of letters of the alphabet in any order, of any length, we will see:
  • some non-sensical words like "XWPBQI" and "NKPMZ",
  • some real words like "SUN" and "MOON" and
  • even some composite words like "SUNMOON" and "MOONSUN".
Since not all such words have meaning, the publishing company decides to include only the words in the book, without their associated meaning, if any. 

So the "Hyperwebster" book looks like this:
A, AA, AAA, ..., AB, ABA, ABAA, ..., AC, ..., AZ, AZA, ...

B, BA, BAA, ..., BB, BBA, BBAA, ..., BC, ..., BZ, BZA, ...

C, CA, CAA, ..., CB, CBA, CBAA, ..., CC, ..., CZ, CZA, ...

Z, ZA, ZAA, ..., ZB, ZBA, ZBAA, ..., ZC, ..., ZZ, ZZA, ...

The staff at the publishing company realizes that it can partition the words into 26 volumes, one for each letter of the alphabet. So the Hyperwebster now looks like the following:

Volume A: A, AA, AAA, ..., AB, ABA, ABAA, ..., AC, ..., AZ, AZA, ...
Volume B: B, BA, BAA, ..., BB, BBA, BBAA, ..., BC, ..., BZ, BZA, ...

Volume C: C, CA, CAA, ..., CB, CBA, CBAA, ..., CC, ..., CZ, CZA, ...

Volume Z: Z, ZA, ZAA, ..., ZB, ZBA, ZBAA, ..., ZC, ..., ZZ, ZZA, ...

Next, the staff realizes that all words in volume A start with the letter A, all words in volume B start with the letter B and so on. This means that the first letter in each word can be inferred from its volume and hence, the first letter can be dropped. Excellent! The publishing company just saved some ink by not printing an infinite number of letters.

The new volumes now look like this:

Volume A: A, AA, AAA, ..., B, BA, BAA, ..., C, ..., Z, ZA, ...
Volume B: A, AA, AAA, ..., B, BA, BAA, ..., C, ..., Z, ZA, ...
Volume C: A, AA, AAA, ..., B, BA, BAA, ..., C, ..., Z, ZA, ...
Volume Z: A, AA, AAA, ..., B, BA, BAA, ..., C, ..., Z, ZA, ...

The staff realizes that each volume now looks identical, except for the name of the volume. Why would anyone buy 26 identical copies of the same content? So, the decision is made to publish a single volume called "Hyperwebster", which looks like the following:

New hyperwebster:
A, AA, AAA, ..., B, BA, BAA, ..., C, ..., Z, ZA, ...

This turns out to be identical to the original hyperwebster that they started out with.

Original hyperwebster:
A, AA, AAA, ..., AB, ABA, ABAA, ..., AC, ..., AZ, AZA, ...

B, BA, BAA, ..., BB, BBA, BBAA, ..., BC, ..., BZ, BZA, ...

C, CA, CAA, ..., CB, CBA, CBAA, ..., CC, ..., CZ, CZA, ...

Z, ZA, ZAA, ..., ZB, ZBA, ZBAA, ..., ZC, ..., ZZ, ZZA, ...

The staff realizes that:

  1. the original volume can be partitioned into 26 different volumes,
  2. the first letter in each volume can be dropped, making each volume identical,
  3. and each volume now is really identical to the original volume
  4. and steps 1-3 can be applied ad infinitum.
The publishing company wisely abandons publishing the hyperwebster, even though each execution of steps 1-4 represent an infinite amount of savings!

Moral of the story
The content of the hyperwebster is equivalent to points on a real line (replace A-Z above with 0-9 and observe that it generates all real numbers). Any subset of the real line can be chopped up into infinitely many parts, each of which has the same number of points as the original. Each of the parts in turn can be chopped up into infinitely many subparts, each having the same number of points as the original, ad infinitum. Yeah, that's a lot of points! Continuum hypothesis states that the number of such points is aleph-1.

References
  • Leonard M. Wapner, "The Pea and the Sun", 2005.

Wednesday, June 29, 2011

Continuum Hypothesis

This post looks at Cantor's continuum hypothesis and the relevant historical results around it.

Georg Cantor
Georg Cantor defined infinity in two steps. First he defined an infinite set and then "infinity":

  • an infinite set is one that can be put in one-to-one correspondence with a proper subset of itself. E.g. natural numbers can be put in a one-to-one correspondence with the set of even numbers, which is a proper subset of the natural numbers. Here is the correspondence {0, 1, 2, 3, ... } maps to {0, 2, 4, 6, ...}.
  • The cardinality of such a set is "infinity".
Cantor went a step ahead and defined a family of "infinities", each larger than the previous one.
  • Cantor proved that even an infinite set cannot be put in one-to-one correspondence with its power set (i.e. the set of all its subsets), whose cardinality is 2^n (if the cardinality of the original set is n).
  • This defines a family of "transfinite" cardinalities, each larger than the one before:
    • aleph-0 (the cardinality of natural numbers),
    • aleph-1 = 2^aleph-0,
    • aleph-2 = 2^aleph-1
    • and so on.
  • Real numbers cannot be put in one-to-one correspondence with natural numbers. (See Cantor's diagonal argument). The cardinality of real numbers is called the cardinality of the continuum, c.
  • Cantor hypothesized that c = aleph-1, which became known as the "Continuum Hypothesis". In other words, there is no transfinite cardinality between aleph-0 (cardinality of natural numbers) and c (cardinality of real numbers). But Cantor could not prove it. It turns out that there is a very good reason for that!
  • By the way, Cantor called natural numbers or any subset thereof as countable, since they can be counted, i.e. put in one-to-one correspondence with 0, 1, 2, 3, ... . He called real numbers and higher transfinite cardinalities as uncountable, because they cannot be put in one-to-one correspondence with 0, 1, 2, 3, ... . (See Cantor's diagonal argument).

Kurt Godel
Kurt Godel came along and gave the world two "incompleteness" theorems.
  • Informally, the first incompleteness theorem says that in any axiomatic system involving natural numbers, there are statements that cannot be proved or disproved within that system. In other words, there are some "undecidable" statements within the system.
  • Stated differently, we can never come up with a finite set of axioms that can prove or disprove every statement in that system. Hence the system is always "incomplete".
  • The intuition behind this is that there are only countably many provable statements, but uncountably many statements in the system. By the "pigeon hole" principle, some statements are unprovable. In fact, a vast majority of the statements are unprovable, since "uncountable" is much, much larger than "countable"!
  • The second incompleteness theorem says that for some axiomatic systems, consistency cannot be proved within the system itself.
Axiom of Choice
  • Simply stated, the axiom of choice says that given a bunch of non-empty sets, it is always possible to choose one element from each set to construct a new set.
  • It seems logical and harmless. But complications arise when the original set has a large cardinality, e.g. aleph-1. How do we go about choosing one element from uncountably many sets? Where do we start - it cannot be put in one-to-one correspondence with natural numbers and hence cannot be labeled 1, 2, 3, ..., etc. So we wouldn't know which set is the first one, which is second and so on.
  • One way to go about this complication is to just assume that there exists such a choice set and circumvent the above complexity.
  • Not everyone agrees! So there are two different axiomatic set theories - one without the axiom of choice (ZF for Zermelo-Frankl, the formulators of set theory) and another with the axiom of choice (ZFC).
  • Godel proved that the Axiom of Choice was consistent with ZF. In other words, ZFC is consistent.

The Finale
  • Godel also proved that the continuum hypothesis was consistent with axiomatic set theory. In other words, it cannot be disproved within ZF.
  • Paul Cohen comes along and proves that continuum hypothesis is independent of the other axioms in ZF. In other words, neither the continuum hypothesis nor its opposite can be proved within ZF. No wonder Cantor couldn't prove the "Continuum Hypothesis" from ZF axioms!
  • Well, what does it all mean? Continuum hypothesis must either be true or be false, since aleph-1 must be either equal to c or not equal to c. The bottom line is that the result by Cohen and Godel say that neither the equality nor the inequality can be proved within the axiomatic system, no matter how smart you are or how hard you try!
  • I, for one, believe that aleph-1 is indeed equal to c. In other words, there are no cardinalities in between that of the natural numbers and that of the real numbers.

References
  • Leonard M. Wapner, "The Pea and the Sun", 2005.

Friday, May 27, 2011

Experiments in graph 3-coloring - block-cutpoint graph

Introduction
One way to reduce the runtime complexity of an algorithm is to partition the problem into smaller sub-problems, solve the sub-problems and combine these solutions to solve the original, bigger problem. This approach can be used when coloring a graph.

I investigated an approach that uses a block-cutpoint representation of the given graph. On a high level, here is the solution:

  1. Given an undirected graph, construct its block-cutpoint graph.
  2. Solve the 3-coloring problem for each of the blocks.
  3. Combine the solution for each of the blocks to 3-color the original graph.

Definitions
Bi-connected graph
An undirected graph is bi-connected if any two vertices lie on a cycle. In other words, there are at least 2 distinct, disjoint paths between any two vertices. This concept is similar to that of a strongly connected component in a directed graph.

Block (aka Bi-connected component)
A bi-connected subgraph of a graph is called a block.

Cutpoint (aka cut vertex)
A vertex whose removal disconnects a graph is called a cut point or a cut vertex. Such vertices are "on the boundary" of a block. In Figure 1, they are shown in red color.

Block-Cutpoint graph

  • A graph where a block is collapsed into a vertex,
  • a cut vertex is represented as a vertex in this graph,
  • there is an edge between a block vertex and a cut vertex only if the cut vertex is part of the block,
  • there is an edge between two cut vertices only if there are adjacent in the original graph.



Interesting point to note is that any undirected graph can be represented as a tree of blocks and cutpoints, as shown in the figure above.


Coloring algorithm

  1. Partition given graph G into blocks B1, B2, ..., Bm.
  2. Color each of the blocks independently.
  3. The reconciliation step: two blocks, say B1 and B2, can share only 1 vertex (the cut vertex), say X.
    1. If X is colored with the same color, then we simply merge the colorings for B1 and B2.
    2. If X is colored differently in B1 and B2, say c1 and c2, respectively, then:
      1. we make X's color in B2 as c1 (its color in B1)
      2. All vertices in B2 colored as c2 are now colored as c1 and
      3. all vertices in B2 colored as c1 are now colored as c2.
    3. So in essence, we swapped colors c1 and c2 in B2.
  4. If we perform the reconciliation step in breadth first search order (BFS) of the block-cutpoint tree, we have a valid coloring for the original graph, provided each of the blocks has a valid coloring.


Runtime complexity

  • Let the size of G be n.
  • Let the sizes of the blocks B1, B2, ..., Bm be n1, n2, ..., nm, respectively.

Since the worst case running time is exponential in the number of nodes, say a^n for G, the runtime complexity of the above algorithm is a^n1 + a^n2 + ... + a^nm + O(n). This translates to O(a^max(n1, n2, ..., nm)) asymptotically.

In closing
This algorithm can be applied to n-coloring, not just 3-coloring. It reduces the time complexity if there are cut vertices in the graph. The more even the size of the blocks, the more the speed-up in run time.

Wednesday, May 18, 2011

Experiments in graph 3-coloring

3-coloring using independent sets
  • By definition, vertices in an independent set (IS) share no edges. So these vertices can all be colored with one color.
  • If after the removal of these vertices (and incident edges), the graph is 2-colorable, then we have a valid 3-coloring.
Fig 1: An independent set (red vertices)
Fig 2: Graph without independent set
Fig 3: Graph without independent set is 2-colorable
Fig 4: Fully colored graph with 3 colors


Interesting points about this approach
  • Not all independent sets work, since G - IS may not be 2-colorable.
  • Enumerating all independent sets is equivalent to enumerating the power set of the vertices and determining if they form an independent set. This has 2^v iterations, each taking about v^2 time. This is asymptotically better than 3^v iterations required for a brute force 3-coloring.
  • If a graph is 3-colorable, then we can always find one color in a valid 3-coloring that is applied to at most floor(v/3) vertices. This implies that when enumerating all independent sets, we need to only find independent sets of size floor(v/3) or less. This is still O(2^n), by the way.
Optimization for this approach

  • We start with brute force enumeration, as below, where 1 implies inclusion of the vertex and 0 its exclusion from the independent set.
    • {1, 0, 0, ..., 0} - start by choosing v0
    • {1, 1, 0, ...., 0} - after we choose v0 and v1 in the independent set.
    • {1, 0, 1, ...., 0} - if we decide that v0 and v1 cannot be in an independent set (because they share an edge) and move on to v0 and v2.
  • In general, if current selection is an independent set, keep the latest added vertex and include the next available vertex.
  • In general, if current selection is not an independent set (because the latest vertex has one or more edges with previous vertices in the independent set), drop the latest added vertex and choose the next available one.

3-coloring with brute force
  • This is exhaustive, brute force approach. We try each combination, till we find a valid coloring.
  • Let the colors be 0, 1 and 2. Each color combination is an n-tuple. E.g. {0, 2, 1, 0, 0, 1, 1, 2, ...}. This colors vertex v0 with color 0, v1 with color 2, v2 with color 1 and so on.
  • This is O(3^n), but seems to run faster in practice. Perhaps it has a tighter bound than O(3^n).
Optimization for this approach
  • We order vertices arbitrarily as v0, v1, v2, ..., vn.
  • We enumerate current color selection lexicographically. E.g.
    1. 0
    2. 0, 0, if the previous selection is a valid coloring
    3. 0, 0, 0 if the previous selection is a valid coloring
    4. 0, 0, 1 if the previous selection is not a valid coloring
    5. 0, 0, 2 if the previous selection is not a valid coloring
    6. 0, 1 if the previous selection is not a valid coloring
    7. 0, 1, 0 if the previous selection is a valid coloring
    8. and so on.

3-coloring greedily
  • This is a simple approach, very fast in practice, but cannot color all colorable graphs.
  • Order vertices by descending order of degree.
  • Greedily choose the lowest color that is valid.
  • Repeat above step till all vertices are colored.
  • Runs in O(v^2) time.

Sunday, April 3, 2011

Flajolet-Martin algorithm

Flajolet-Martin algorithm approximates the number of unique objects in a stream or a database in one pass. If the stream contains $n$ elements with $m$ of them unique, this algorithm runs in $O(n)$ time and needs $O(log(m))$ memory. So the real innovation here is the memory usage, in that an exact, brute-force algorithm would need $O(m)$ memory (e.g. think "hash map").

As noted, this is an approximate algorithm. It gives an approximation for the number of unique objects, along with a standard deviation $\sigma$, which can then be used to determine bounds on the approximation with a desired maximum error $\epsilon$, if needed.

Given below are the following:
  • intuition behind the algorithm
  • the algorithm itself
  • a java-based implementation
  • some results using that implementation and
  • some closing thoughts.

Intuition
If we had a good, random hash function that acted on strings and generated integers, what can we say about the generated integers? Since they are random themselves, we would expect:
  • $1/2$ of them to have their binary representation end in $0$ (i.e. divisible by $2$),
  • $1/4$ of them to have their binary representation end in $00$ (i.e. divisible by $4$)
  • $1/8$ of them to have their binary representation end in $000$ (i.e. divisible by $8$)
  • and in general, $1/2^n$ of them to have their binary representation end in $0^n$.
Turning the problem around, if the hash function generated an integer ending in $0^m$ bits (and it also generated integers ending in $0^{m-1}$ bits, $0^{m-2}$ bits, ..., $0^1$ bits), intuitively, the number of unique strings is around $2^m$.

To facilitate the above, this algorithm maintains 1 bit for each $0^i$ seen - i.e. 1 bit for 0, another for 00, another for 000, and so on. The output of the algorithm is based on the maximum of consecutive $0^i$ seen.

The Flajolet-Martin algorithm
This is an informal description. Formal treatment can be found in the original paper listed in the reference section.
  1. Create a bit vector (bit array) of sufficient length $L$, such that $2^L>n$, the number of elements in the stream. Usually a 64-bit vector is sufficient since $2^{64}$ is quite large for most purposes.
  2. The i-th bit in this vector/array represents whether we have seen a hash function value whose binary representation ends in $0^i$. So initialize each bit to 0.
  3. Generate a good, random hash function that maps input (usually strings) to natural numbers.
  4. Read input. For each word, hash it and determine the number of trailing zeros. If the number of trailing zeros is k, set the k-th bit in the bit vector to 1.
  5. Once input is exhausted, get the index of the first 0 in the bit array (call this R). By the way, this is just the number of consecutive 1s (i.e. we have seen 0, 00, ...,  as the output of the hash function) plus one.
  6. Calculate the number of unique words as $2^R/\phi$, where $\phi$ is 0.77351. A proof for this can be found in the original paper listed in the reference section.
  7. The standard deviation of R is a constant: $\sigma(R)=1.12$. (In other words, R can be off by about 1 for 1-0.68=32% of the observations, off  by 2 for about 1-0.95=5% of the observations, off by 3 for 1-0.997=0.3% of the observations using the Empirical rule of statistics). This implies that our count can be off by a factor of 2 for 32% of the observations, off by a factory of 4 for 5% of the observations, off by a factor of 8 for 0.3% of the observations and so on.
To improve accuracy of this approximation algorithm, we do the following:
  1. (Averaging) Use multiple hash functions and use the average R instead.
  2. (Bucketing) Averages are susceptible to large fluctuations. So use multiple buckets of hash functions from the above step and use the median of the average R. This gives fairly good accuracy.
  3. Overall accuracy of this algorithm can be tuned by using appropriate number of hash functions in the averaging and bucketing steps. Of course, if more accuracy is desired, more hash functions need to be used, which implies higher computation cost.

Java-based implementation
The code can be found here: FlajoletMartin.java. It uses lucene-core-3.0.3.jar or better.

Results using the above implementation
  • Wikipedia article on "United States Constitution" had 3978 unique words. When run ten times, Flajolet-Martin algorithm reported values of 4902, 4202, 4202, 4044, 4367, 3602, 4367, 4202, 4202 and 3891 for an average of 4198. As can be seen, the average is about right, but the deviation is between -400 to 1000.
  • Wikipedia article on "George Washington" had 3252 unique words. When run ten times, the reported values were 4044, 3466, 3466, 3466, 3744, 3209, 3335, 3209, 3891 and 3088, for an average of 3492.
Closing thoughts
  • The Flajolet-Martin algorithm approximates the number of unique elements quite well, using just O(log m) memory, where m is the number of unique words.
  • During implementation, it was observed that this algorithm is quite sensitive to the hash function parameters. The hash functions suggested in the original paper ($h(x)=(M+N\sum{ord(x_j)*128^j)\mod(2^L)}$) work properly only when n is odd. Otherwise, the algorithm always reports the number of unique elements as 1 or 2! I chose both $M$ and $N$ as odd, as can be seen in the implementation.
References
  1. Flajolet, P. and Martin, N., Journal of Computer and System Sciences, 1985. PDF - http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.81.3869&rep=rep1&type=pdf
  2. J. Ullman and A. Rajaraman, Mining of Massive Datasets,  Chapter 3 - available at http://infolab.stanford.edu/~ullman/mmds/ch4.pdf

Wednesday, March 2, 2011

Locality Sensitive Hashing Explored

I was catching up on some new developments in the field of computer science in the past decade and came across Locality Sensitive Hashing (LSH) among other things. It piqued my interest, especially its theoretical aspect. Here I explore this topic informally.

Intuition
The idea is to have a hash function that is sensitive to distances. By that we mean that if two points are "close" to each other, the probability that this function hashes them to the same bucket is "high". Conversely, if the points are "far" apart, the probability that they get hashed to the same bucket is "low".

The beauty of such a function is that we can probabilistically determine if two points are "close enough" by hashing them, without determining their actual distance. This can be useful, especially when points are in high dimensions, their distance computation is complex or we just have too many points to work with. We sacrifice accuracy (since the computation is probabilistic) for speed (since the hash function is presumably computationally cheap). However, as indicated later, we can increase this accuracy arbitrarily by sacrificing some speed. :-)

Distance measure
Central to LSH is the concept of distance. In fact, it is the starting point for LSH. Here are some popular distance measures:

  • Jaccard distance, if your points are sets
  • edit distance, if your points are strings
  • euclidean distance, if points are in n dimensions.
Regardless of which distance measure you choose for your purpose (you can invent one if you like), it must satisfy 3 conditions:
  1. d(x, x) = 0, i.e. distance between a point and itself is zero.
  2. d(x, y) = d(y, x), i.e. the distance measure is symmetric.
  3. d(x, y) + d(y, z) >= d(x, z) - known as the triangle inequality.

Hash function
Once a distance measure is decided upon, we need to come up with a locality sensitive hash function for that distance measure. Any random hash function may not necessarily work. In fact, it is not necessary that a distance measure will have an LSH at all! However, most well-known distance measures have known locality sensitive hash functions. E.g. Jaccard distance has "min-hash" functions that are locality sensitive.

More formally, a hash function f is said to be (d1, d2, p1, p2)-sensitive, if for any two points x and y:

  • if d(x, y) < d1, then Probability(f(x) = f(y)) > p1, i.e. the probability that the two points get mapped by the hash function f to the same bucket is at least p1.
  • and if d(x, y) > d2, then probability (f(x) = f(y)) < p2, i.e. the probability that the two points get mapped by the hash function f to the same bucket is at most p2.
There are some interesting observations here including that we only talk about d(x, y) being less than d1 or greater than d2, but nothing about when d1 < d(x, y) < d2, which as it turns out, is not important to the theory of LSH.

Family of hash functions
A locality sensitive hash function is useful in probabilistically determining if two points are "close" to each other or not. However, we are limited by the probabilities. E.g. a probability of p1 = 0.5 does not necessarily instill a reasonable confidence that the two points are close. However, if we have a family of such hash functions, then using probability theory, we can construct functions that give us arbitrarily high probabilities.

Here's how the construction goes:
  • if f1, f2, ..., fn are (d1, d2, p1, p2)-sensitive, then we can AND them together to construct a (d1, d2, p1n, p2n)-sensitive hash function. This has the effect of reducing the probability.
  • if f1, f2, ..., fn are (d1, d2, p1, p2)-sensitive, then we can OR them together to construct a (d1, d2, 1 - (1- p1)n, 1 - (1 - p2)n)-sensitive hash function. This has the effect of increasing the probability.
A combination of ANDs and ORs can get probabilities arbitrarily close to 1. The closer we want an LSH's p1 to 1 (and p2 to 0), the more ANDs and ORs we need to perform.

In closing
LSH is a cheap way to determine proximity of points in any space provided we can define a distance measure and come up with multiple hash functions that are locality sensitive. This technique has been applied to diverse problems like finding plagiarisms in student papers, detecting similar web pages, finger print matching, etc.

In my next post, I examine jaccard distance and min-hash function. Additionally, I look at other distance measures and some of their locality sensitive hashes.

References
  1. J. Ullman and A. Rajaraman, Mining of Massive Datasets,  Chapter 3 - available at http://infolab.stanford.edu/~ullman/mmds/ch3.pdf

Friday, July 23, 2010

REST

REST (REpresentational State Transfer) is a simple architectural style or philosophy
  1. that needs you to identify or address entities in the system (called "resources")
  2. and that defines the actions or operations on those entities ("access methods").
The addressing mechanism is the URI - uniform resource identifier. e.g. http://www. google.com. The supported actions or operations are PUT, GET, POST and DELETE. In particular, PUT has creation semantics, GET has fetch semantics, POST has update semantics and DELETE has remove semantics.

Universal applicability
Because of its simplicity, REST has almost universal applicability. As an example, consider a book:
  • It can be considered as a resource and referred to by its book number (ISBN, e.g. 9871234567890). So its URI can be isbn://9871234567890.
  • You can write a new book by PUTting a new resource accessible at this URI.
  • You can fetch the book by GETing it from the URI.
  • You can modify the book by POSTing to the URI.
  • You can delete the book by DELETEing the URI.

HTTP is the best known usage of REST.

Details
One of the biggest values offered by REST is the standardization of its access methods. If you came up with different access methods (i.e. verbs) to access different resources, that proliferation would be so hard to track as to be of little value. Imagine that for dealing with books, your methods are "createBook", "getBook", "updateBook", "deleteBook" and for dealing with printers, they are "createPrinter", "getPrinter", "submitPrintJob", "deletePrinter". You need to know the resource type (in this case, book v/s printer) to know the operations that it supports. This customization leads to chaos even with a small number of resource types. With uniformity of access methods comes confidence that (a) you know beforehand what access methods are supported and (b) using an access method will result in (more or less) what you think it should result in.

REST standardizes the addressing mechanism (URI) and the access methods (GET/PUT/etc.). It does not standardize the message format, i.e. the data/information flowing over the REST mechanism. E.g. you can use binary, XML, JSON or your favorite message format and still conform with REST principles.

Since PUT has creation semantics, it must be idempotent, i.e. multiple executions of PUT with the same message must be no different than a single execution. Additionally, GET must not change the state of the system. This implies that GET must be idempotent too, i.e. multiple GETs (without any POSTs in between!) should return the same representation. POSTs are expected to change the state of the entity and are not expected to be idempotent. Similarly, DELETEs are not expected to be idempotent either. One of the implications of state change and idempotence is the opportunity to cache resource representations between the resource and its clients, which can improve performance.

How to apply REST to your system
One way to RESTify your system is to:
  1. Identify the top-level, first-class nouns in the system. These become your resources.
  2. Choose meaningful identifiers in your URIs for these resources. Usually these identifiers should be long-lived, i.e. their commonly-accepted meaning should rarely change with time. They should feel relevant and meaningful to the largest subset of the client population. E.g. instead of an obscure, numeric id (e.g. user id) for a resource, a more descriptive identifier (e.g. user name) may be a better choice.
  3. Verbs/operations in the system are restricted to one of create (PUT), get (GET), modify (POST) or delete (DELETE). Their semantics should be defined as they apply to the resources. It is acceptable for POST (for example) to mean differently to different resources. E.g. POST for a book resource may mean updating the book's contents, while POST for a printer may mean "submit a print job".
  4. Make sure that GETs and PUTs are idempotent. Specifically, make sure that GETs don't change the state of the system.
That's it! Your system is now REST-compliant (or RESTful). Of course, this is a simplication and each of the steps above take non-trivial time. But on a high-level, that's all that's usually involved.

Common mistakes
  1. Sometimes, system designers make GET change the state of the system. This is the most widespread violation in my experience, e.g. when GETs are used to submit data to resources to change their state. E.g. HTTP URL like "AddToCart?item=candy" - this is a violation of REST principles. (In this specific case, POST is the right access method, since your resource is really the "cart" and you are updating it.)
  2. Another common violation is PUTing to a URI that isn't being created. E.g. when creating a new print job to the printer, you usually don't know the resource to create. But you do know the printer URI. In this case, POSTing is the right option. 

Comparing REST with SOAP RPC
This uniformity/standardization of access methods is the fundamental difference between REST and SOAP RPC. While REST allows only PUT/GET/POST/DELETE, SOAP RPC encourages ad-hoc or custom access methods (GetOrders, AddToCart, SubmitPrintJob, etc). This implies that to use SOAP RPC, you need to know the access methods a priori. This can be a big disadvantage if you are targeting universal access.

Another fundamental difference is that SOAP is a protocol, REST is not. REST is more of a guideline or a principle. If someone says, "I am using the REST protocol", now you know how much they really know about REST!

One comment I frequently hear is "we can either use REST or XML, not both". This implies that XML over REST is impossible. That's not the case. Recall that REST does not define a message format. Here's how you use XML using REST principles. You can define resources in your system, define URIs for them, restrict access methods to PUT/GET/POST/DELETE and then allow these access methods to use XML. Viola! You are now using XML with REST.

The flip side
When it comes to generality of access pattern, nothing comes close to REST/HTTP. Using a single browser, you can access almost any resource (text (txt, html, etc.), images (gif, jpg, png, etc.), sounds (mp3, ram), video (mp3, mp4), etc.) on the web. This is a clear advantage of REST. However, this doesn't mean that you have to use REST all the time. REST thrives when clients know and use generic access patterns (e.g. GET/PUT). If that is not the case, then REST is not needed. E.g. when a resource is being used by a small number of clients, each of them can have knowledge of the specific operations supported by the resource. In this case, it can be argued that REST doesn't add much value.

In Closing
REST, as an architectural principle, uniformalizes resource identification and access. This uniformity is of enormous value in general and has led to some great things, e.g. HTTP over the internet. However, before you jump onto the bandwagon, it never hurts to know your reasons.

References:
  1. http://www.prescod.net/rest/
  2. http://en.wikipedia.org/wiki/REST