Sparse Sets
March 9, 2012
These sparse sets make for very simple code. We begin with global variables for n and the two vectors; note that we don’t assign an initial value:
(define n 0)
(define d #f)
(define s #f)
(define (make-set u)
(set! d (make-vector u))
(set! s (make-vector u)))
Clearing the set is simple:
(define (clear) (set! n 0))
We assume that a value k is not present in the set during insertion:
(define (insert k)
(vector-set! d n k)
(vector-set! s k n)
(set! n (+ n 1)))
The check for set membership takes two clauses:
(define (member? k)
(and (< (vector-ref s k) n)
(= (vector-ref d (vector-ref s k)) k)))
And finally we perform iteration; note that elements of the set are accessed in the order they entered the set:
(define (for-all proc)
(do ((i 0 (+ i 1))) ((= i n))
(proc (vector-ref d i))))
Here are some examples:
> (make-set 8)
> (clear)
> (insert 5)
> (insert 1)
> (insert 4)
> (member? 4)
#t
> (member? 3)
#f
You can run the program at http://programmingpraxis.codepad.org/CYb0TmWR.
Thanks, this was a good thing to learn.
Instead of D, S, n, u global, I packaged them in a single vector. This may not be appropriate when a program needs to manipulate a single such set very fast, but with a sufficiently smart compiler …
here is a Perl version:
use warnings;
use strict;
my @dense;
my @sparse;
my $n;
sub get_number {
my $op = shift;
my $k;
do {
print "Enter number to $op: ";
$k = <STDIN>;
chomp($k);
} while ($k !~ /^\d+$/);
return $k;
}
sub sparse_clear {
$n = 0;
}
sub sparse_insert {
my $k = get_number(‘insert’);
$dense[$n] = $k;
$sparse[$k] = $n;
$n++;
}
sub sparse_lookup {
my $k = get_number(‘lookup’);
( defined($sparse[$k]) &&
($sparse[$k] < $n) &&
($dense[$sparse[$k]] == $k)
) ? print "exists\n" : print "doesn’t exist\n";
}
sub sparse_iterate {
my $largest_sparce = 0;
print "Dense: ";
for (0 .. $n-1) {
print "$dense[$_] ";
$largest_sparce = $dense[$_] if ($dense[$_] > $largest_sparce);
}
print "\n";
print "Sparse ";
for (0 .. $largest_sparce) {
defined($sparse[$_]) ? print "$sparse[$_] " : print "u ";
}
print "\n";
}
sub sparse_quit {
exit;
}
my @ops = qw(clear insert lookup iterate quit);
while(1) {
print "\n";
for (0 .. scalar(@ops)-1) {
print "$_: $ops[$_]\n";
}
print "Enter operation: ";
my $choice = <STDIN>;
chomp($choice);
if ($choice =~ /^[0-4]$/) {
&{\&{"sparse_$ops[$choice]"}}();
}
}
Of course, the easier (and arguably better) way to do this would be to use one of the many existing perl modules which already implement sparse matrix arithmetic: http://search.cpan.org/search?query=sparse&mode=all
Very cool post. We often deal with large sparse data structures at work, but unfortunately our bottleneck is typically available memory and not processing time. Something like this is ideal for modern hardware though.
Just to emphasize your note, the maintenance of the variable n is crucial. Not only does it allow for constant-time initialization, clearing, and element-counting, but it is also the only way to guarantee all elements in the universe have unambiguous values.
Definitely going to give this a try sometime this weekend, thanks for sharing!
And C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SparseSet
{
internal sealed class SparseSet : IEnumerable
{
private ushort?[] D = new ushort?[ushort.MaxValue];
private ushort?[] S = new ushort?[ushort.MaxValue];
private ushort _size = 0;
internal bool Insert(ushort value)
{
bool result = !Lookup(value);
if (result)
{
D[_size] = value;
S[value] = _size;
_size++;
}
return result;
}
internal bool Lookup(ushort value)
{
bool result = S[value] < _size &&
S[value].HasValue &&
D[S[value].Value] == value;
return result;
}
internal void Clear()
{
_size = 0;
}
public IEnumerator GetEnumerator()
{
var result = new SparseSetEnumerator(D, _size);
return result;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
}
internal sealed class SparseSetEnumerator : IEnumerator
{
private readonly ushort?[] _values = null;
private readonly ushort _size = 0;
private int _index = -1;
internal SparseSetEnumerator(ushort?[] values, ushort size)
{
_values = values;
_size = size;
}
public ushort Current
{
get
{
return _values[_index].Value;
}
}
public void Dispose()
{
// squeclh
}
object IEnumerator.Current
{
get
{
return (object)_values[_index];
}
}
public bool MoveNext()
{
_index++;
bool moving = _index < _size &&
_values[_index].HasValue;
return moving;
}
public void Reset()
{
_index = -1;
}
}
class Program
{
static void Main(string[] args)
{
var ss = new SparseSet();
ss.Insert(5);
ss.Insert(1);
ss.Insert(4);
ss.Clear();
ss.Insert(5);
ss.Insert(1);
ss.Insert(4);
ss.Clear();
ss.Insert(3);
ss.Insert(2);
ss.Insert(7);
ss.Clear();
ss.Insert(3);
ss.Insert(2);
ss.Insert(7);
ss.Insert(3);
ss.Insert(2);
ss.Insert(7);
ss.Clear();
ss.Insert(3);
ss.Insert(2);
ss.Insert(7);
ss.Insert(5);
ss.Insert(1);
ss.Insert(4);
foreach(ushort us in ss)
{
Console.WriteLine(us);
}
Console.WriteLine("—");
foreach (ushort us in ss)
{
Console.WriteLine(us);
}
ss.Clear();
Console.WriteLine("—");
foreach (ushort us in ss)
{
Console.WriteLine(us);
}
}
}
}
An implementation in Guile (Scheme):
(also here: https://gist.github.com/2011167)
Here is how to do this in Forth. Execute the name of the set to bring it in context.
The INSERT, LOOKUP, ITERATE and SCLEAR words work on the set in context.
0 VALUE #universe
0 VALUE n
0 VALUE Dense
0 VALUE Sparse
: SPARSE-SET ( sz | “name” — )
CREATE DUP , 2* CELLS ALLOT
DOES> @+ TO #universe TO Dense
Dense #universe CELLS + TO Sparse ;
#16 SPARSE-SET test-set
test-set ( make current )
: INSERT ( x — )
DUP #universe >= ABORT” INSERT :: out of range”
DUP Dense n CELL[] !
Sparse []CELL n SWAP !
1 +TO n ;
: LOOKUP ( x — bool )
>R
Sparse R@ CELL[] @ DUP n U>= IF DROP -R FALSE EXIT ENDIF
Dense []CELL @ R> = ;
: ITERATE ( xt — )
LOCAL xt
n 0 ?DO Dense I CELL[] @ xt EXECUTE LOOP ;
: SCLEAR ( — ) CLEAR n ;
SCLEAR
CR .( Insert {5,4,1} ) 5 INSERT 1 INSERT 4 INSERT
CR .( Lookup {5,1,4,15,12} => ) 5 LOOKUP . 1 LOOKUP . 4 LOOKUP . #15 LOOKUP . #12 LOOKUP .
CR .( Iterate with ‘.’ => ) ‘ . ITERATE
:ABOUT CR .” Sparse Sets”
CR .” Try: ( n — ) INSERT”
CR .” ( n — bool ) LOOKUP ”
CR .” ( xt — ) ITERATE ”
CR .” ( — ) SCLEAR ” ;
.ABOUT -sparseset CR
DEPRIVE
[…] we created is called a sparse set, and allows for fairly quick acces times and single component […]