Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Version bump to 0.1.0

  • Loading branch information...
commit e3e0f0e08329a4eb51ad42e79c99a14180ec95bb 0 parents
@dcadenas dcadenas authored
5 .document
@@ -0,0 +1,5 @@
+README.rdoc
+lib/**/*.rb
+bin/*
+features/**/*.feature
+LICENSE
26 .gitignore
@@ -0,0 +1,26 @@
+## MAC OS
+.DS_Store
+
+## TEXTMATE
+*.tmproj
+tmtags
+
+## EMACS
+*~
+\#*
+.\#*
+
+## VIM
+*.swp
+
+## PROJECT::GENERAL
+coverage
+rdoc
+pkg
+
+## PROJECT::SPECIFIC
+ext/*.bundle
+ext/*.o
+lib/*.bundle
+lib/*.o
+**/*/*.gem
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Daniel Cadenas
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
57 README.rdoc
@@ -0,0 +1,57 @@
+= rankable_graph
+
+A Ruby {PageRank}[http://en.wikipedia.org/wiki/PageRank] like implementation.
+
+== Description
+
+This gem is mostly writen in C with a pretty Ruby wrapper.
+It's intended to be used for big but not huge graphs, as
+those are better processed with a map-reduce distributed solution.
+
+== Usage
+
+ rankable_graph = RankableGraph.new
+
+ #First we draw our directed graph using the link method which receives as parameters two identifiers.
+ #The only restriction for the identifiers is that they should be integers.
+ rankable_graph.link(1234, 4312)
+ rankable_graph.link(9876, 4312)
+ rankable_graph.link(4312, 9876)
+ rankable_graph.link(8888, 4312)
+
+ probability_of_following_a_link = 0.85 # The bigger the number, less probability we have to teleport to some random link
+ tolerance = 0.0001 # the smaller the number, the more exact the result will be but more CPU cycles will be needed
+
+ rankable_graph.rank(probability_of_following_a_link, tolerance) do |identifier, rank|
+ puts "Node #{identifier} rank is #{rank}"
+ end
+
+Which outputs
+
+ Node 1234 rank is 0.0375000014901161
+ Node 4312 rank is 0.479941636323929
+ Node 9876 rank is 0.445058345794678
+ Node 8888 rank is 0.0375000014901161
+
+This ranks represent the probabilities that a certain node will be visited.
+For more examples please refer to the tests.
+
+== Requirements
+
+* Ruby 1.9
+* {glib2}[http://library.gnome.org/devel/glib/2.22/] >= 2.22.2
+
+== Note on Patches/Pull Requests
+
+* Fork the project.
+* Make your feature addition or bug fix.
+* Add tests for it. This is important so I don't break it in a
+ future version unintentionally.
+* Commit, do not mess with rakefile, version, or history.
+ (if you want to have your own version, that is fine but
+ bump version in a commit by itself I can ignore when I pull)
+* Send me a pull request. Bonus points for topic branches.
+
+== Copyright
+
+Copyright (c) 2009 {Cubox}[http://cuboxsa.com]. See LICENSE for details.
47 Rakefile
@@ -0,0 +1,47 @@
+require 'rubygems'
+require 'rake'
+
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gem|
+ gem.name = "rankable_graph"
+ gem.summary = %Q{A Ruby Pagerank implementation}
+ gem.description = %Q{A Ruby Pagerank implementation}
+ gem.email = "dev@cuboxsa.com"
+ gem.homepage = "http://github.com/cubox/rankable_graph"
+ gem.authors = ["Daniel Cadenas"]
+ gem.add_development_dependency "rspec", ">= 1.2.9"
+ gem.extensions = ["ext/extconf.rb"]
+ gem.required_ruby_version = '>= 1.9'
+ gem.requirements << 'glib2, v2.22.2 or greater'
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
+ end
+rescue LoadError
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
+end
+
+require 'spec/rake/spectask'
+Spec::Rake::SpecTask.new(:spec) do |spec|
+ spec.libs << 'lib' << 'spec'
+ spec.spec_files = FileList['spec/**/*_spec.rb']
+end
+
+Spec::Rake::SpecTask.new(:rcov) do |spec|
+ spec.libs << 'lib' << 'spec'
+ spec.pattern = 'spec/**/*_spec.rb'
+ spec.rcov = true
+end
+
+task :spec => :check_dependencies
+
+task :default => :spec
+
+require 'rake/rdoctask'
+Rake::RDocTask.new do |rdoc|
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
+
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = "rankable_graph #{version}"
+ rdoc.rdoc_files.include('README*')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
1  VERSION
@@ -0,0 +1 @@
+0.1.0
30 benchmark.rb
@@ -0,0 +1,30 @@
+#require 'rubygems'
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'ext'))
+
+require 'ext/rankable_graph'
+
+require "benchmark"
+include Benchmark
+
+n = 1000000
+bmbm(12) do |test|
+ r = RankableGraph.new
+ srand(5)
+ (0..(n-1)).map do |i|
+ #each node has an average of 30 links
+ rand(60).times do
+ j = rand(n)
+ #first three nodes are more linked to than the rest
+ r.link(i, (j > 800000 ? rand(3) : j))
+ end
+ end
+
+ test.report("c:") do
+ result = []
+ r.rank(0.85, 0.001){|key, val| result << [key, val]}
+ puts "7 first values are #{result[0..6].map{|(k,v)| "[#{k}]=#{"%.4f" % (v * 100)}, "}}"
+ end
+end
+
+
+
181 ext/Makefile
@@ -0,0 +1,181 @@
+
+SHELL = /bin/sh
+
+#### Start of system configuration section. ####
+
+srcdir = .
+topdir = /usr/local/include/ruby19-1.9.1
+hdrdir = /usr/local/include/ruby19-1.9.1
+arch_hdrdir = /usr/local/include/ruby19-1.9.1/$(arch)
+VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
+prefix = $(DESTDIR)/usr/local
+exec_prefix = $(prefix)
+vendorhdrdir = $(rubyhdrdir)/vendor_ruby
+sitehdrdir = $(rubyhdrdir)/site_ruby
+rubyhdrdir = $(includedir)/$(RUBY_INSTALL_NAME)-$(ruby_version)
+vendordir = $(libdir)/$(RUBY_INSTALL_NAME)/vendor_ruby
+sitedir = $(libdir)/$(RUBY_INSTALL_NAME)/site_ruby
+mandir = $(datarootdir)/man
+localedir = $(datarootdir)/locale
+libdir = $(exec_prefix)/lib
+psdir = $(docdir)
+pdfdir = $(docdir)
+dvidir = $(docdir)
+htmldir = $(docdir)
+infodir = $(datarootdir)/info
+docdir = $(datarootdir)/doc/$(PACKAGE)
+oldincludedir = $(DESTDIR)/usr/include
+includedir = $(prefix)/include
+localstatedir = $(prefix)/var
+sharedstatedir = $(prefix)/com
+sysconfdir = $(prefix)/etc
+datadir = $(datarootdir)
+datarootdir = $(prefix)/share
+libexecdir = $(exec_prefix)/libexec
+sbindir = $(exec_prefix)/sbin
+bindir = $(exec_prefix)/bin
+rubylibdir = $(libdir)/$(ruby_install_name)/$(ruby_version)
+archdir = $(rubylibdir)/$(arch)
+sitelibdir = $(sitedir)/$(ruby_version)
+sitearchdir = $(sitelibdir)/$(sitearch)
+vendorlibdir = $(vendordir)/$(ruby_version)
+vendorarchdir = $(vendorlibdir)/$(sitearch)
+
+CC = gcc
+CXX = g++
+LIBRUBY = $(LIBRUBY_SO)
+LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
+LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
+LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static
+OUTFLAG = -o
+COUTFLAG = -o
+
+RUBY_EXTCONF_H =
+cflags = $(optflags) $(debugflags) $(warnflags)
+optflags = -O2
+debugflags = -g
+warnflags = -Wall -Wno-parentheses
+CFLAGS = -fno-common $(cflags) -fno-common -pipe -fno-common -I/opt/local/include/glib-2.0 -I/opt/local/lib/glib-2.0/include -I/opt/local/include
+INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
+DEFS =
+CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE $(DEFS) $(cppflags)
+CXXFLAGS = $(CFLAGS) $(cxxflags)
+ldflags = -L. -L/usr/local/lib -L/opt/local/lib
+dldflags =
+archflag =
+DLDFLAGS = $(ldflags) $(dldflags) $(archflag)
+LDSHARED = cc -dynamic -bundle -undefined suppress -flat_namespace
+LDSHAREDXX = $(LDSHARED)
+AR = ar
+EXEEXT =
+
+RUBY_INSTALL_NAME = ruby19
+RUBY_SO_NAME = ruby19
+arch = i386-darwin10.2.0
+sitearch = i386-darwin10.2.0
+ruby_version = 1.9.1
+ruby = /usr/local/bin/ruby19
+RUBY = $(ruby)
+RM = rm -f
+RM_RF = $(RUBY) -run -e rm -- -rf
+RMDIRS = $(RUBY) -run -e rmdir -- -p
+MAKEDIRS = mkdir -p
+INSTALL = /usr/bin/install -c
+INSTALL_PROG = $(INSTALL) -m 0755
+INSTALL_DATA = $(INSTALL) -m 644
+COPY = cp
+
+#### End of system configuration section. ####
+
+preload =
+
+libpath = . $(libdir)
+LIBPATH = -L. -L$(libdir)
+DEFFILE =
+
+CLEANFILES = mkmf.log
+DISTCLEANFILES =
+DISTCLEANDIRS =
+
+extout =
+extout_prefix =
+target_prefix =
+LOCAL_LIBS =
+LIBS = $(LIBRUBYARG_SHARED) -lglib-2.0 -lintl -liconv -lpthread -ldl -lobjc
+SRCS = rankable_graph.c
+OBJS = rankable_graph.o
+TARGET = rankable_graph
+DLLIB = $(TARGET).bundle
+EXTSTATIC =
+STATIC_LIB =
+
+BINDIR = $(bindir)
+RUBYCOMMONDIR = $(sitedir)$(target_prefix)
+RUBYLIBDIR = $(sitelibdir)$(target_prefix)
+RUBYARCHDIR = $(sitearchdir)$(target_prefix)
+HDRDIR = $(rubyhdrdir)/ruby$(target_prefix)
+ARCHHDRDIR = $(rubyhdrdir)/$(arch)/ruby$(target_prefix)
+
+TARGET_SO = $(DLLIB)
+CLEANLIBS = $(TARGET).bundle
+CLEANOBJS = *.o *.bak
+
+all: $(DLLIB)
+static: $(STATIC_LIB)
+
+clean-rb-default::
+clean-rb::
+clean-so::
+clean: clean-so clean-rb-default clean-rb
+ @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
+
+distclean-rb-default::
+distclean-rb::
+distclean-so::
+distclean: clean distclean-so distclean-rb-default distclean-rb
+ @-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
+ @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
+ @-$(RMDIRS) $(DISTCLEANDIRS)
+
+realclean: distclean
+install: install-so install-rb
+
+install-so: $(RUBYARCHDIR)
+install-so: $(RUBYARCHDIR)/$(DLLIB)
+$(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
+install-rb: pre-install-rb install-rb-default
+install-rb-default: pre-install-rb-default
+pre-install-rb: Makefile
+pre-install-rb-default: Makefile
+$(RUBYARCHDIR):
+ $(MAKEDIRS) $@
+
+site-install: site-install-so site-install-rb
+site-install-so: install-so
+site-install-rb: install-rb
+
+.SUFFIXES: .c .m .cc .cxx .cpp .C .o
+
+.cc.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $<
+
+.cxx.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $<
+
+.cpp.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $<
+
+.C.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $<
+
+.c.o:
+ $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $<
+
+$(DLLIB): $(OBJS) Makefile
+ @-$(RM) $(@)
+ $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
+
+
+
+$(OBJS): $(hdrdir)/ruby.h $(hdrdir)/ruby/defines.h $(arch_hdrdir)/ruby/config.h
8 ext/extconf.rb
@@ -0,0 +1,8 @@
+require 'mkmf'
+
+unless pkg_config('glib-2.0')
+ abort "glib2 not found"
+end
+
+create_makefile("rankable_graph")
+
5 ext/mkmf.log
@@ -0,0 +1,5 @@
+package configuration for glib-2.0
+cflags: -I/opt/local/include/glib-2.0 -I/opt/local/lib/glib-2.0/include -I/opt/local/include
+ldflags: -L/opt/local/lib
+libs: -lglib-2.0 -lintl -liconv
+
226 ext/rankable_graph.c
@@ -0,0 +1,226 @@
+#include <ruby.h>
+#include <glib.h>
+
+typedef struct {
+ GPtrArray* in_links;
+ GPtrArray* number_out_links;
+ gint current_available_index;
+ GHashTable* key_to_index;
+ GHashTable* index_to_key;
+} RNStruct;
+
+
+static void rankable_graph_free (RNStruct *p){
+ g_hash_table_destroy(p->index_to_key);
+ g_hash_table_destroy(p->key_to_index);
+ g_ptr_array_free(p->number_out_links, TRUE);
+ g_ptr_array_free(p->in_links, TRUE);
+}
+
+static void array_of_arrays_free_func (gpointer array){
+ g_array_free(array, TRUE);
+}
+
+static VALUE rankable_graph_allocate (VALUE klass){
+ RNStruct* rn;
+ rn = ALLOC(RNStruct);
+ rn->in_links = g_ptr_array_new_with_free_func(array_of_arrays_free_func);
+ rn->number_out_links = g_ptr_array_new_with_free_func(g_free);
+ rn->key_to_index = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);
+ rn->index_to_key = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);
+
+ return Data_Wrap_Struct(klass, 0, rankable_graph_free, rn);
+}
+
+static VALUE init(VALUE self){
+ RNStruct* rn;
+ Data_Get_Struct(self, RNStruct, rn);
+ rn->current_available_index = -1;
+ return self;
+}
+
+static void fill_empty_holes_in_in_links(RNStruct *rn){
+ GArray* in_links_array;
+ const gint in_links_size = rn->in_links->len, size = g_hash_table_size(rn->key_to_index);
+ gint i;
+ if(in_links_size < size){
+ for(i = 0; i < size - in_links_size; i++){
+ in_links_array = g_array_new(FALSE, FALSE, sizeof(gint));
+ g_ptr_array_add(rn->in_links, in_links_array);
+ }
+ }
+}
+
+static void fill_empty_holes_in_number_out_links(RNStruct *rn){
+ gint i, out_links_size = rn->number_out_links->len;
+ const gint size = g_hash_table_size(rn->key_to_index);
+ gint* zero;
+ if(out_links_size < size){
+ for(i = 0; i < size - out_links_size; i++){
+ zero = g_new(gint, 1);
+ *zero = 0;
+ g_ptr_array_add(rn->number_out_links, zero);
+ }
+ }
+}
+
+static void update_in_links(RNStruct *rn, gint from, gint to){
+ fill_empty_holes_in_in_links(rn);
+ GArray* in_links_for_to = g_ptr_array_index(rn->in_links, to);
+ g_array_append_val(in_links_for_to, from);
+}
+
+static void update_number_out_links(RNStruct *rn, gint from){
+ fill_empty_holes_in_number_out_links(rn);
+ gint* current_value = (gint *)g_ptr_array_index(rn->number_out_links, from);
+ *current_value = (*current_value)++;
+}
+
+static void link_with_indices(RNStruct *rn, gint from, gint to){
+ update_in_links(rn, from, to);
+ update_number_out_links(rn, from);
+}
+
+static gint key_as_array_index(RNStruct *rn, VALUE key){
+ gpointer key_as_index_ptr;
+ gint key_as_int = FIX2INT(key);
+
+ if(!g_hash_table_lookup_extended(rn->key_to_index, GINT_TO_POINTER(key_as_int), NULL, &key_as_index_ptr)){
+ key_as_index_ptr = GINT_TO_POINTER(++rn->current_available_index);
+ g_hash_table_insert(rn->key_to_index, GINT_TO_POINTER(key_as_int), key_as_index_ptr);
+ g_hash_table_insert(rn->index_to_key, key_as_index_ptr, GINT_TO_POINTER(key_as_int));
+ }
+
+ return GPOINTER_TO_INT(key_as_index_ptr);
+}
+
+static VALUE link(VALUE self, VALUE from, VALUE to){
+ RNStruct* rn;
+ Data_Get_Struct(self, RNStruct, rn);
+
+ gint from_as_index = key_as_array_index(rn, from);
+ gint to_as_index = key_as_array_index(rn, to);
+
+ link_with_indices(rn, from_as_index, to_as_index);
+ return Qnil;
+}
+
+static gfloat calculate_change(gfloat *a, gfloat *b, gint size){
+ gint i;
+ gfloat acc = 0;
+ for(i = 0; i < size; i++){
+ acc += ABS(a[i] - b[i]);
+ }
+ return acc;
+}
+
+static GArray * calculate_dangling_nodes(RNStruct *rn){
+ GArray* dangling_nodes = g_array_new(FALSE, FALSE, sizeof(gint));
+ gint i;
+ gpointer int_as_pointer;
+ for(i = 0; i < rn->number_out_links->len; i++){
+ if(*(gint *)g_ptr_array_index(rn->number_out_links, i) == 0){
+ int_as_pointer = GINT_TO_POINTER(i);
+ g_array_append_val(dangling_nodes, int_as_pointer);
+ }
+ }
+ return dangling_nodes;
+}
+
+static gfloat* step(gfloat s, gfloat t_over_size, gfloat *p, RNStruct *rn, GArray *dangling_nodes){
+ const gint size = g_hash_table_size(rn->key_to_index);
+ gint i, j;
+ gfloat inner_product = 0;
+ for(i = 0; i < dangling_nodes->len; i++){
+ inner_product += p[GPOINTER_TO_INT(g_array_index(dangling_nodes, gint, i))];
+ }
+ const gfloat inner_product_over_size = inner_product / (gfloat)size;
+
+ gfloat ksum, vsum = 0;
+ gint index;
+ gfloat* v = g_new0(gfloat, size);
+ GArray* in_links_for_i;
+ for(i = 0; i < size; i++){
+ ksum = 0;
+ in_links_for_i = (GArray *)g_ptr_array_index(rn->in_links, i);
+ for(j = 0; j < in_links_for_i->len; j++){
+ index = GPOINTER_TO_INT(g_array_index(in_links_for_i, gint, j));
+ ksum += p[index] / *((gint *)g_ptr_array_index(rn->number_out_links, index));
+ }
+
+ v[i] = s * (ksum + inner_product_over_size) + t_over_size;
+ vsum += v[i];
+ }
+
+ const gfloat inverse_of_vsum = 1 / vsum;
+ for(i = 0; i < size; i++){
+ v[i] *= inverse_of_vsum;
+ }
+ return v;
+}
+
+static VALUE rank(VALUE self, VALUE s, VALUE tolerance){
+ if(rb_block_given_p() == Qtrue){
+ RNStruct* rn;
+ Data_Get_Struct(self, RNStruct, rn);
+
+ const gint size = g_hash_table_size(rn->key_to_index);
+ const gfloat inverse_of_size = 1.0 / size;
+ const gfloat t_over_size = (1.0 - NUM2DBL(s)) / size;
+
+ g_assert_cmpuint(rn->in_links->len, ==, size);
+ g_assert_cmpuint(rn->number_out_links->len, ==, size);
+ GArray* dangling_nodes = calculate_dangling_nodes(rn);
+ gfloat* p = g_new(gfloat, size);
+ gint i;
+ for(i = 0; i < size; i++){
+ p[i] = inverse_of_size;
+ }
+
+ gfloat* new_p;
+ gfloat change = 2;
+ while(change > NUM2DBL(tolerance)){
+ new_p = step(NUM2DBL(s), t_over_size, p, rn, dangling_nodes);
+ change = calculate_change(p, new_p, size);
+ g_free(p);
+ p = new_p;
+ }
+
+ for(i = 0; i < size; i++){
+ rb_yield_values(2, INT2FIX(g_hash_table_lookup(rn->index_to_key, GINT_TO_POINTER(i))), rb_float_new(p[i]));
+ }
+
+ g_free(p);
+ g_array_free(dangling_nodes, TRUE);
+ }
+ return Qnil;
+}
+
+// Copy across state (used by clone and dup)
+static VALUE rankable_graph_init_copy(VALUE copy, VALUE orig){
+ RNStruct* orig_rn;
+ RNStruct* copy_rn;
+
+ if (copy == orig) return copy;
+
+ if (TYPE(orig) != T_DATA || RDATA(orig)->dfree != (RUBY_DATA_FUNC)rankable_graph_free) {
+ rb_raise(rb_eTypeError, "wrong argument type");
+ }
+
+ Data_Get_Struct(orig, RNStruct, orig_rn);
+ Data_Get_Struct(copy, RNStruct, copy_rn);
+ MEMCPY(copy_rn, orig_rn, RNStruct, 1);
+ return copy;
+}
+
+static VALUE rb_cRankableGraph;
+
+void Init_rankable_graph(){
+ rb_cRankableGraph = rb_define_class("RankableGraph", rb_cObject);
+ rb_define_alloc_func(rb_cRankableGraph, rankable_graph_allocate);
+ rb_define_method(rb_cRankableGraph, "initialize", init, 0);
+ rb_define_method(rb_cRankableGraph, "initialize_copy", rankable_graph_init_copy, 1);
+ rb_define_method(rb_cRankableGraph, "link", link, 2);
+ rb_define_method(rb_cRankableGraph, "rank", rank, 2);
+}
+
62 rankable_graph.gemspec
@@ -0,0 +1,62 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{rankable_graph}
+ s.version = "0.1.0"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Daniel Cadenas"]
+ s.date = %q{2010-02-01}
+ s.description = %q{A Ruby Pagerank implementation}
+ s.email = %q{dev@cuboxsa.com}
+ s.extensions = ["ext/extconf.rb"]
+ s.extra_rdoc_files = [
+ "LICENSE",
+ "README.rdoc"
+ ]
+ s.files = [
+ ".document",
+ ".gitignore",
+ "LICENSE",
+ "README.rdoc",
+ "Rakefile",
+ "VERSION",
+ "benchmark.rb",
+ "ext/Makefile",
+ "ext/extconf.rb",
+ "ext/mkmf.log",
+ "ext/rankable_graph.c",
+ "rankable_graph.gemspec",
+ "spec/rankable_graph_spec.rb",
+ "spec/spec.opts",
+ "spec/spec_helper.rb"
+ ]
+ s.homepage = %q{http://github.com/cubox/rankable_graph}
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+ s.required_ruby_version = Gem::Requirement.new(">= 1.9")
+ s.requirements = ["glib2, v2.22.2 or greater"]
+ s.rubygems_version = %q{1.3.5}
+ s.summary = %q{A Ruby Pagerank implementation}
+ s.test_files = [
+ "spec/rankable_graph_spec.rb",
+ "spec/spec_helper.rb"
+ ]
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
+ else
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
+ end
+ else
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
+ end
+end
+
117 spec/rankable_graph_spec.rb
@@ -0,0 +1,117 @@
+require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
+
+class Float
+ # 0.666666666 -> 66.7
+ def to_percentage
+ 100 * (self * (10 ** 3)).round / (10 ** 3).to_f
+ end
+end
+
+def assert_rank(rankable_graph, expected_rank)
+ rankable_graph.rank(0.85, 0.0001){|label, rank| rank.to_percentage.should == expected_rank[label]}
+end
+
+describe RankableGraph do
+ it "should return correct results when having a dangling node" do
+ rankable_graph = RankableGraph.new
+ #node 2 is a dangling node because it has no outbound links
+ rankable_graph.link(0, 2)
+ rankable_graph.link(1, 2)
+
+ expected_rank = {
+ 0 => 21.3,
+ 1 => 21.3,
+ 2 => 57.4
+ }
+
+ assert_rank(rankable_graph, expected_rank)
+ end
+
+ it "should return correct results for a star graph" do
+ rankable_graph = RankableGraph.new
+ rankable_graph.link(0, 2)
+ rankable_graph.link(1, 2)
+ rankable_graph.link(2, 2)
+
+ expected_rank = {
+ 0 => 5,
+ 1 => 5,
+ 2 => 90,
+ }
+
+ assert_rank(rankable_graph, expected_rank)
+ end
+
+ it "should be uniform for a circular graph" do
+ rankable_graph = RankableGraph.new
+ rankable_graph.link(0, 1)
+ rankable_graph.link(1, 2)
+ rankable_graph.link(2, 3)
+ rankable_graph.link(3, 4)
+ rankable_graph.link(4, 0)
+
+ expected_rank = {
+ 0 => 20,
+ 1 => 20,
+ 2 => 20,
+ 3 => 20,
+ 4 => 20
+ }
+
+ assert_rank(rankable_graph, expected_rank)
+ end
+
+ it "should return correct results for a converging graph" do
+ rankable_graph = RankableGraph.new
+ rankable_graph.link(0, 1)
+ rankable_graph.link(0, 2)
+ rankable_graph.link(1, 2)
+ rankable_graph.link(2, 2)
+
+ expected_rank = {
+ 0 => 5,
+ 1 => 7.1,
+ 2 => 87.9
+ }
+
+ assert_rank(rankable_graph, expected_rank)
+ end
+
+ it "should correctly reproduce the wikipedia example" do
+ #http://en.wikipedia.org/wiki/File:PageRanks-Example.svg
+ rankable_graph = RankableGraph.new
+ rankable_graph.link(1, 2)
+ rankable_graph.link(2, 1)
+ rankable_graph.link(3, 0)
+ rankable_graph.link(3, 1)
+ rankable_graph.link(4, 3)
+ rankable_graph.link(4, 1)
+ rankable_graph.link(4, 5)
+ rankable_graph.link(5, 4)
+ rankable_graph.link(5, 1)
+ rankable_graph.link(6, 1)
+ rankable_graph.link(6, 4)
+ rankable_graph.link(7, 1)
+ rankable_graph.link(7, 4)
+ rankable_graph.link(8, 1)
+ rankable_graph.link(8, 4)
+ rankable_graph.link(9, 4)
+ rankable_graph.link(10, 4)
+
+ expected_rank = {
+ 0 => 3.3, #a
+ 1 => 38.4, #b
+ 2 => 34.3, #c
+ 3 => 3.9, #d
+ 4 => 8.1, #e
+ 5 => 3.9, #f
+ 6 => 1.6, #g
+ 7 => 1.6, #h
+ 8 => 1.6, #i
+ 9 => 1.6, #j
+ 10 => 1.6 #k
+ }
+
+ assert_rank(rankable_graph, expected_rank)
+ end
+end
1  spec/spec.opts
@@ -0,0 +1 @@
+--color
9 spec/spec_helper.rb
@@ -0,0 +1,9 @@
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'ext'))
+require 'ext/rankable_graph'
+require 'spec'
+require 'spec/autorun'
+
+Spec::Runner.configure do |config|
+end
Please sign in to comment.
Something went wrong with that request. Please try again.