tags:

views:

124

answers:

8

I have two files. The first file contains a list of 6 character keys (SA0001, SA1001, etc.). The second file contains a list of dates and amounts where the first six positions will match the key in the first file. I want to verify that every key in the first file has at least one match in the second file. There may be more than one match which is okay and there may be records in the second file with no key in the first file which is also okay. So basically a loop within a loop. The problem arises when I want to break out of the inner loop after the first match because the second file could be quite large. It prints out the "found" message correctly and breaks, but it won't print the "not found" message if it reaches the end of the second file with out finding a match. My code so far is:

unvalues = open("file1.txt", "r")
newfunds = open("file2.txt", "r").readlines()
i = 1
for line in newfunds:
    line = line.strip()
    for line2 in iter(unvalues.readline, ""):
        try:
            if line == line2[:6]:
                print "%s: Matching %s to %s for date %s" % (i, line, line2[:6], line2[6:14])
                break
        except StopIteration: print "%s: No match for %s" % (i, line)
    i += 1
    unvalues.seek(0)
A: 

I don't think break; throws a StopIteration.

You generally don't want to use exceptions for flow control like that.

Martin Beckett
A: 

Go through each file once, adding each record to a hash with value equal to 1. Then make sure that the keys of the first hash are a subset of the keys of the second.

hashes = []
for f in ["file1.txt","file2.txt"]:
    lines = open(f,"r").readlines()
    hash = {}
    for line in lines:
        hash[line[:6] = 1
    hashes.append(hash)

set_keys1 = set(hashes[0].keys())
set_keys2 = set(hashes[1].keys())
assert(set_keys1.issubset(set_keys2))
Sean Cavanagh
+3  A: 

Use sets instead:

set1=set(line[:6] for line in open('file1.txt'))
set2=set(line[:6] for line in open('file2.txt'))
not_found = set1 - set2
if not_found:
    print "Some keys not found: " + ', '.join(not_found)
Mark Byers
Is there a limitation on the size of a set? That is, if the text file exceeds a certain size will the script fail?
Count Boxer
Yes, there is a limitation, but it's quite large - a million items shouldn't be a problem, for example. A set only stores unique values so you won't need an entry in the set for every line in your file: only one entry for each unique key. How many unique keys might your files contain?
Mark Byers
I understand. The number of unique keys can not exceed 256. File1 can not exceed that number of lines since each key can only be listed once. File2 may have thousands of lines, it also will never have more than 256 unique keys.
Count Boxer
This code does exactly what I need. Thanks to all for the incredibly fast and varied response.
Count Boxer
-1 This code reads the whole of the second file unconditionally -- no early exit. Compare with the answer by yu_sha
John Machin
-1 for that?! For files the size he is talking about I really don't think it is necessary to optimize at the cost of readability.
Mark Byers
A: 

I think this might be closer to what you want:

unvalues = dict((line[:6], line[6:14]) for line in open("file1.txt", "r"))
newfunds = [line for line in open("file2.txt", "r")]
for i, line in enumerate(newfunds):
    key = line.strip()
    if key in unvalues:
        v = unvalues[key]
        print "%s: Matching %s to %s for date %s" % (i+1, line, key, v)
    else:
        print "%s: No match for %s" % (i+1, line)
hughdbrown
+2  A: 
first_file=open("file1.txt","r")
#save all items from first file into a set
first_file_items=set(line.strip() for line in first_file)
second_file=open("file2.txt","r")
for line in second_file:
   if line[:6] in first_file_items:
       #if this is item from the first file, remove it from the set
       first_file_items.remove(line[:6])
       #when nothing is left in the set, we found everything
       if not first_file_items: break

if first_file_items:
   print "Elements in first file but not in second", first_file_items
yu_sha
+1 This is the best answer ... a little deuglification is suggested: change `if len(first_file_items)==0:` to `if not first_file_items:` and change `if len(first_file_items):` to `if first_file_items:`
John Machin
I tried to make it clear for the beginner but you are right.
yu_sha
A: 

You cannot (and need not) catch the StopIteration exception that occurs when the iterator is finished, because it gets caught by the for loop automatically. To do what you appear to be trying to do, you could use an else block after your for block, e.g. you could replace your inner loop with this:

for line2 in iter(unvalues.readline, ""):
    if line == line2[:6]:
        print "%s: Matching %s to %s for date %s" % (i, line, line2[:6], line2[6:14])
        break
else:
    print "%s: No match for %s" % (i, line)

The else block is executed when the for loop finishes without the break statement getting hit.

However, you may well find that one of the other approaches using sets is quicker.

Weeble
A: 
from collections import defaultdict

unvalues = open("file1.txt", "r").readlines()
newfunds = open("file2.txt", "r").readlines()

unvals = defaultdict(int)

for val in unvalues:
    unvals[val] = 0

for line in newfunds:
    line = line.strip()

    if line[:6] in unvals.keys():
        unvals[line[:6]] += 1

for k in unvals.keys():
    if unvals[k] == 0:
        print "Match Not Found For %s" % k

might give you a good starting point for what you want to achieve, without being terribly messy. This gives you the performance advantage of only looping through each data set individually.

As a quick addendum, if you want line numbers, rather than building a counting variable outside the loop and incrementing it, try this instead:

for i, line in enumerate(newfunds):

enumerate() basically zips a sequential integer iterator with your list to produce the desired result without unnecessary counting operations.

KingRadical
A: 

Another approach using sets

keys = set(line[:6] for line in open('file.txt'))
missing = set(value[:6] for value in open('file2.txt') if value[:6] not in keys)
if missing:
   print "Keys Missing " + ', '.join(missing)
Robert Christie