tags:

views:

199

answers:

4

I have two large (~100 GB) text files that must be iterated through simultaneously.

Zip works well for smaller files but I found out that it's actually making a list of lines from my two files. This means that every line gets stored in memory. I don't need to do anything with the lines more than once.

handle1 = open('filea', 'r'); handle2 = open('fileb', 'r')

for i, j in zip(handle1, handle2):
    do something with i and j.
    write to an output file.
    no need to do anything with i and j after this.

Is there an alternative to zip() that acts as a generator that will allow me to iterate through these two files without using >200GB of ram?

+14  A: 

itertools has a function izip that does that

from itertools import izip
for i, j in izip(handle1, handle2):
    ...

If the files are of different sizes you may use izip_longest, as izip will stop at the smaller file.

Anurag Uniyal
A: 

Something like this? Wordy, but it seems to be what you're asking for.

It can be adjusted to do things like a proper merge to match keys between the two files, which is often more what's needed than the simplistic zip function. Also, this doesn't truncate, which is what the SQL OUTER JOIN algorithm does, again, different from what zip does and more typical of files.

with open("file1","r") as file1:
    with open( "file2", "r" as file2:
        for line1, line2 in parallel( file1, file2 ):
            process lines

def parallel( file1, file2 ):
    if1_more, if2_more = True, True
    while if1_more or if2_more:
        line1, line2 = None, None # Assume simplistic zip-style matching
        # If you're going to compare keys, then you'd do that before
        # deciding what to read.
        if if1_more:
            try:
                line1= file1.next()
            except StopIteration:
                if1_more= False
        if if2_more:
            try:
                line2= file2.next()
            except StopIteration:
                if2_more= False
        yield line1, line2
S.Lott
Did you not mean 'while if1_more OR if2_more:'? And why wrap file1 and file2 in iters, when files are already iters? And lastly, was this just an academic "how would I do this for myself if I had to?" exercise? Surely one would prefer to use izip or izip_longest from the itertools module in the std lib, instead of writing 20 lines of homebrewed code that does the same thing, but would have to be maintained and supported (and debugged!).
Paul McGuire
@Paul McGuire: Yes, OR is correct. The explicit iter is required to use next and get a proper StopIteraction exception at EOF. No this was not "academic". This is an answer to the question. The question is vague and itertools may not provide the required features. This may not either, but this can be tailored.
S.Lott
I'm running Py2.5.4, and calling `next()` on a file object at the end of the file raises StopIteration for me.
Paul McGuire
@Paul: Good enough. I didn't bother testing it, but jotted it down from memory.
S.Lott
A: 

If you want to truncate to the shortest file:

handle1 = open('filea', 'r')
handle2 = open('fileb', 'r')

try:
    while 1:
        i = handle1.next()
        j = handle2.next()

        do something with i and j.
        write to an output file.

except StopIteration:
    pass

finally:
    handle1.close()
    handle2.close()

Else

handle1 = open('filea', 'r')
handle2 = open('fileb', 'r')

i_ended = False
j_ended = False
while 1:
    try:
        i = handle1.next()
    except StopIteration:
        i_ended = True
    try:
        j = handle2.next()
    except StopIteration:
        j_ended = True

        do something with i and j.
        write to an output file.
    if i_ended and j_ended:
        break

handle1.close()
handle2.close()

Or

handle1 = open('filea', 'r')
handle2 = open('fileb', 'r')

while 1:
    i = handle1.readline()
    j = handle2.readline()

    do something with i and j.
    write to an output file.

    if not i and not j:
        break
handle1.close()
handle2.close()
voyager
And if the two files are different lengths? This will truncate at the shorter one. Hopefully, that's the desired behavior.
S.Lott
@S.Lott: isn't that what `zip` does?
voyager
@S.Lott - this only breaks out of the while-forever loop when both i_ended AND j_ended, so it will read until the end of the longer file. But there is definitely room for improvement. If one file is much shorter than the other, the current code will call .next() and catch StopIteration *many* times, when we have already learned that the file has ended. Simple enough to do: `if not i_ended: try: i=handel1.next() ...` (as you do in your `if if1_more:` code). (Ah! I see that your comment was responding to the original code, not the edited version - sorry for butting in!)
Paul McGuire
@Paul I was aware of the `if not ended` improvement, but the idea was to give the shortest code that would run and work as expected. I always assume that anything post on a forum comes with a disclosure saying **Not intended for production use. Learn from the general algorithm, and implement as appropriate.** The second version non truncating version is a bit better IMO anyway :)
voyager
@voyager: Yes, it's what ZIP does. Is that what the problem requires?
S.Lott
@voyager - no your second version is actually worse. First, read() reads the entire file, I think you meant readline(). When at the end of the file, readline() returns an empty string. Second, your loop exiting code should *not* call readline() again, as this will destructively read another line from the input file. Instead, you should do `if not i and not j: break` or `if not i or not j: break`, depending on whether you want to stop at the shorter file or not. And I'm surprised to see i and j used for strings, convention says these would be integer loop indexes. Why not line1 and line2?
Paul McGuire
@Paul stupid me, I did mean `readline`. You are correct, I didn't think of that. This is why I no longer code at 2 in the morning :S I used `j` and `i` because that's what the OP used, no other reason.Do I remember correctly and `readline` keeps in memory just the part being read of the file?
voyager
Yes, I believe readline() internally does some I/O readahead buffering to optimize I/O performance, but only returns one line at a time, and does not read the entire file. This can sometimes be confused with readlines(), which *does* read the entire file into a list of newline-terminated strings.
Paul McGuire
+10  A: 

You can use izip_longest like this to pad the shorter file with empty lines

in python 2.6

from itertools import izip_longest
with handle1 as open('filea', 'r'):
    with handle2 as open('fileb', 'r'): 
        for i, j in izip_longest(handle1, handle2, fillvalue=""):
            ...

or in python3.1

from itertools import izip_longest
with handle1 as open('filea', 'r'), handle2 as open('fileb', 'r'): 
    for i, j in izip_longest(handle1, handle2, fillvalue=""):
        ...
gnibbler
+1 for `with` - I like the Py3.1 syntax to keep down the indent levels.
Paul McGuire