Filter a large file in small memory The challenge is a follow-up to Filter a large file quickly  which had two submissions where were as fast as wc!  This time the difficulty is that RAM is severely restricted which means that new methods will have to be used.


Input: Each line has three space separated positive integers. 
Output: All input lines A B, Tthat satisfy either of the following criterion.


There exists another input line C, D, U where D = A and 0 &lt;= T - U &lt; 100.
There exists another input line C, D, U where B = C and 0 &lt;= U - T &lt; 100.



To make a test file use the following python script which will also be used for testing. It will make a 1.3G file. You can of course reduce nolines for testing.

import random    
nolines = 50000000 # 50 million
for i in xrange(nolines):
    print random.randint(0,nolines-1), random.randint(0,nolines-1), random.randint(0,nolines-1)


Rules. Your code must use no more than 200MB of RAM.  I will enforce this using ulimit -v 200000.    Your code must therefore also run on ubuntu using only easily installable and free software.

Score. You can report your unofficial score by testing on your machine as (the time in seconds needed by Keith Randall's submission from Filter a large file quickly divided by the time in seconds taken by your submission )*100 .

Winning criterion. The winner will be the one with the highest score when I test the code on my computer using a file created by the sample python code.  I will first run 

sync &amp;&amp; sudo bash -c 'echo  3 &gt; /proc/sys/vm/drop_caches'


before each test run.

The deadline is one week from the time of the first correct entry.

My Machine The timings will be run on my machine. This is a standard 8GB RAM ubuntu install on an AMD FX-8350 Eight-Core Processor.  This also means I need to be able to run your code.

Some relevant timing information  

largefile.file is a file created using the sample python code.

sync &amp;&amp; sudo bash -c 'echo  3 &gt; /proc/sys/vm/drop_caches'

time wc largefile.file

real    0m26.835s
user    0m18.363s
sys     0m0.495s

time sort -n largefile.file  &gt; /dev/null

real    1m32.344s
user    2m9.530s
sys     0m6.543s
Tcl with SQLite, score less than 0.05

Shove the lines into an SQL database, then make SQLite do the work.  Requires the Tcl bindings for SQLite 3, OpenBSD package sqlite3-tcl, Ubuntu package libsqlite3-tcl.

Usage: tclsh filter.tcl &lt;input.txt &gt;output.txt

package require sqlite3

sqlite3 db ""
db eval {
    begin transaction;
    create table lines(i integer primary key,
               a integer, b integer, t integer);
}
while {[gets stdin line] &gt;= 0} {
    lassign [split $line] a b t
    db eval {insert into lines values(null,$a,$b,$t)}
}
db eval {
    create view pairs as
      select first.i, second.i as j
      from lines as first, lines as second
      where first.a == second.b and
        first.t - second.t between 0 and 99 and
        first.i != second.i;
    select a, b, t from lines
      where i in (select i from pairs union select j from pairs);
} {
    puts "$a $b $t"
}
db close


Comparison of time

The motto of SQLite is, "Small. Fast. Reliable. Choose any three," but my SQL query is not fast. My program took more than six hours with my slow hard disk. I compare times for three programs:


cfilter is the large-memory C program by Keith Randall
hfilter is the large-memory C program by James_pic
filter.tcl is my small-memory Tcl program


With 50 million lines of input, cfilter and hfilter both crash because my data size limit is only 1 GB. (The default limit in OpenBSD is 512 MB.) If I raise my limit to 8 GB with ulimit -Sd 8388608, then I can run both cfilter and hfilter. I also lower my physical memory limit with -m 3000000 so that other programs stay in RAM, not in swap space. I have only 4 GB of RAM.

$ ulimit -Sd 8388608 -m 3000000
$ time ./cfilter input.txt &gt;output.txt 
    0m9.03s real     0m5.17s user     0m1.97s system
$ ln -sf input.txt test.file
$ time ./hfilter &gt;output2.txt
    0m25.62s real     0m22.08s user     0m2.04s system
$ time tclsh8.6 filter.tcl &lt;input.txt &gt;output3.txt
^Z  371m19.57s real     0m0.00s user     0m0.00s system
[1] + Suspended            tclsh8.6 filter.tcl &lt; input.txt &gt; output3.txt


I suspended filter.tcl after more than 6 hours. top shows that filter.tcl used less than 15 MB of memory and less than 12 minutes of processor time. Most of the 6 hours went to wait for my slow hard disk (5400 rpm).

For score, 10 seconds for cfilter, divided by more than 6 hours for filter.tcl, times 100, equals less than 0.05.

Comparison of results

The different filters do not select the same lines!  Consider these 6 lines of input:

$ cat example.txt
1 801 33
802 1 33
2 803 499
804 2 400
805 2 400
3 3 555


My filter.tcl outputs five lines, all except 3 3 555.

$ tclsh8.6 filter.tcl &lt;example.txt
1 801 33
802 1 33
2 803 499
804 2 400
805 2 400


But cfilter only outputs three lines:

$ ./cfilter example.txt         
804 2 400
805 2 400
2 803 499


cfilter seems to have a bug when T - U == 0. It fails to output the lines 1 801 33 and 802 1 33, though I believe them to be a matching pair.

Now for hfilter and the Scala program by the same author:

$ ln -sf example.txt test.file
$ ./hfilter
804 2 400
2 803 499
805 2 400
2 803 499
3 3 555
3 3 555
$ scala Filterer example.txt /dev/stdout
804 2 400
2 803 499
802 1 33
1 801 33
805 2 400
2 803 499
3 3 555
3 3 555


hfilter also misses the lines 1 801 33 and 802 1 33, but the Scala version finds them. Both programs output 2 803 499 twice (because it is in two matching pairs), but I might ignore such duplication. Both programs also output 3 3 355. This line is in a pair with itself because A = B. This is only correct if the phrase "another input line" includes the same line.

When the input is the random 50 million lines from the Python script, these differences might disappear. The probability that any matching pair has T - U = 0, or any line has A = B, is close to zero when the input has random numbers up to 50 million.