Skip to content
Browse files

Converted to ffi.

  • Loading branch information...
1 parent 97b4e85 commit 34d2609e1bbd697ae6ff24167d8c7891c600f36e @dbalatero committed Nov 2, 2010
View
8 Gemfile
@@ -0,0 +1,8 @@
+source :rubygems
+
+gem 'ffi'
+
+group :test do
+ gem 'rspec', '1.3.1'
+ gem 'jeweler'
+end
View
19 Rakefile
@@ -0,0 +1,19 @@
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gemspec|
+ gemspec.name = "levenshtein-ffi"
+ gemspec.summary = "An FFI version of the levenshtein gem."
+ gemspec.description = "Provides a fast, cross-Ruby implementation of the levenshtein distance algorithm."
+ gemspec.email = "dbalatero@gmail.com"
+ gemspec.homepage = "http://github.com/dbalatero/levenshtein-ffi"
+ gemspec.authors = ["David Balatero"]
+ gemspec.add_dependency "ffi"
+ gemspec.add_development_dependency "rspec"
+ gemspec.add_development_dependency "jeweler"
+ end
+
+ Jeweler::GemcutterTasks.new
+rescue LoadError
+ puts "Jeweler not available. Install it with: gem install jeweler"
+end
+
View
3 ext/levenshtein/.gitignore
@@ -0,0 +1,3 @@
+*.bundle
+*.o
+Makefile
View
2 ext/levenshtein/extconf.rb
@@ -0,0 +1,2 @@
+require 'mkmf'
+create_makefile('levenshtein')
View
68 ext/levenshtein/levenshtein.c
@@ -0,0 +1,68 @@
+# include <string.h>
+# include <stdlib.h>
+
+# ifdef LEV_CASE_INSENSITIVE
+# include <ctype.h>
+# define eq(x, y) (tolower(x) == tolower(y))
+# else
+# define eq(x, y) ((x) == (y))
+# endif
+
+# define min(x, y) ((x) < (y) ? (x) : (y))
+
+unsigned int levenshtein (const char *word1, const char *word2) {
+ size_t len1 = strlen(word1),
+ len2 = strlen(word2);
+ unsigned int *v = calloc(len2 + 1, sizeof(unsigned int));
+ unsigned int i, j, current, next, cost;
+
+ /* strip common prefixes */
+ while (len1 > 0 && len2 > 0 && eq(word1[0], word2[0]))
+ word1++, word2++, len1--, len2--;
+
+ /* handle degenerate cases */
+ if (!len1) return len2;
+ if (!len2) return len1;
+
+ /* initialize the column vector */
+ for (j = 0; j < len2 + 1; j++)
+ v[j] = j;
+
+ for (i = 0; i < len1; i++) {
+ /* set the value of the first row */
+ current = i + 1;
+ /* for each row in the column, compute the cost */
+ for (j = 0; j < len2; j++) {
+ /*
+ * cost of replacement is 0 if the two chars are the same, or have
+ * been transposed with the chars immediately before. otherwise 1.
+ */
+ cost = !(eq(word1[i], word2[j]) || (i && j &&
+ eq(word1[i-1], word2[j]) && eq(word1[i],word2[j-1])));
+ /* find the least cost of insertion, deletion, or replacement */
+ next = min(min( v[j+1] + 1,
+ current + 1 ),
+ v[j] + cost );
+ /* stash the previous row's cost in the column vector */
+ v[j] = current;
+ /* make the cost of the next transition current */
+ current = next;
+ }
+ /* keep the final cost at the bottom of the column */
+ v[len2] = next;
+ }
+ free(v);
+ return next;
+}
+
+# ifdef TEST
+# include <stdio.h>
+# include "levenshtein.h"
+
+int main (int argc, char **argv) {
+ unsigned int distance;
+ if (argc < 3) return -1;
+ distance = levenshtein(argv[1], argv[2]);
+ printf("%s vs %s: %u\n", argv[1], argv[2],distance);
+}
+# endif
View
1 ext/levenshtein/levenshtein.h
@@ -0,0 +1 @@
+unsigned levenshtein(const char *, const char *);
View
14 lib/levenshtein.rb
@@ -0,0 +1,14 @@
+require 'ffi'
+
+module Levenshtein
+ extend FFI::Library
+
+ library = File.dirname(__FILE__) + "/../ext/levenshtein/levenshtein"
+ begin
+ ffi_lib(library)
+ rescue LoadError
+ ffi_lib(library + ".bundle")
+ end
+
+ attach_function :distance, :levenshtein, [:string, :string], :int
+end
View
24 spec/levenshtein_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Levenshtein do
+ before(:each) do
+ @fixtures = [
+ ["hello", "hello", 0],
+ ["hello", "helo", 1],
+ ["hello", "jello", 1],
+ ["hello", "helol", 1],
+ ["hello", "hellol", 1],
+ ["hello", "heloll", 2],
+ ["hello", "cheese", 4],
+ ["hello", "saint", 5],
+ ["hello", "", 5],
+ ]
+ end
+
+ it "should calculate correct distances" do
+ @fixtures.each do |w1, w2, d|
+ Levenshtein.distance(w1, w2).should == d
+ Levenshtein.distance(w2, w1).should == d
+ end
+ end
+end
View
3 spec/spec_helper.rb
@@ -0,0 +1,3 @@
+require 'spec'
+
+require File.dirname(__FILE__) + "/../lib/levenshtein"

0 comments on commit 34d2609

Please sign in to comment.
Something went wrong with that request. Please try again.