annotate tools/filters/join.py @ 1:cdcb0ce84a1b

Uploaded
author xuebing
date Fri, 09 Mar 2012 19:45:15 -0500
parents 9071e359b9a3
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
1 #!/usr/bin/env python
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
2 #Dan Blankenberg
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
3 """
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
4 Script to Join Two Files on specified columns.
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
5
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
6 Takes two tab delimited files, two column numbers (base 1) and outputs a new tab delimited file with lines joined by tabs.
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
7 User can also opt to have have non-joining rows of file1 echoed.
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
8
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
9 """
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
10
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
11 import optparse, os, sys, tempfile, struct
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
12 import psyco_full
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
13
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
14 try:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
15 simple_json_exception = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
16 from galaxy import eggs
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
17 from galaxy.util.bunch import Bunch
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
18 from galaxy.util import stringify_dictionary_keys
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
19 import pkg_resources
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
20 pkg_resources.require("simplejson")
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
21 import simplejson
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
22 except Exception, e:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
23 simplejson_exception = e
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
24 simplejson = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
25
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
26
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
27 class OffsetList:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
28 def __init__( self, filesize = 0, fmt = None ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
29 self.file = tempfile.NamedTemporaryFile( 'w+b' )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
30 if fmt:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
31 self.fmt = fmt
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
32 elif filesize and filesize <= sys.maxint * 2:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
33 self.fmt = 'I'
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
34 else:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
35 self.fmt = 'Q'
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
36 self.fmt_size = struct.calcsize( self.fmt )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
37 @property
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
38 def size( self ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
39 self.file.flush()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
40 return self.file_size / self.fmt_size
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
41 @property
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
42 def file_size( self ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
43 self.file.flush()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
44 return os.stat( self.file.name ).st_size
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
45 def add_offset( self, offset ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
46 if not isinstance( offset, list ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
47 offset = [offset]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
48 self.file.seek( self.file_size )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
49 for off in offset:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
50 self.file.write( struct.pack( self.fmt, off ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
51 def get_offsets( self, start = 0 ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
52 self.file.seek( start * self.fmt_size )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
53 while True:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
54 packed = self.file.read( self.fmt_size )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
55 if not packed: break
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
56 yield struct.unpack( self.fmt, packed )[0]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
57 def get_offset_by_index( self, index ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
58 self.file.seek( index * self.fmt_size )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
59 return struct.unpack( self.fmt, self.file.read( self.fmt_size ) )[0]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
60 def set_offset_at_index( self, index, offset ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
61 if not isinstance( offset, list ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
62 offset = [offset]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
63 if index >= self.size:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
64 self.add_offset( offset )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
65 else:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
66 temp_file = tempfile.NamedTemporaryFile( 'w+b' )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
67 self.file.seek( 0 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
68 temp_file.write( self.file.read( ( index ) * self.fmt_size ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
69 for off in offset:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
70 temp_file.write( struct.pack( self.fmt, off ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
71 temp_file.write( self.file.read() )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
72 self.file = temp_file
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
73
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
74 class SortedOffsets( OffsetList ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
75 def __init__( self, indexed_filename, column, split = None ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
76 OffsetList.__init__( self, os.stat( indexed_filename ).st_size )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
77 self.indexed_filename = indexed_filename
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
78 self.indexed_file = open( indexed_filename, 'rb' )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
79 self.column = column
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
80 self.split = split
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
81 self.last_identifier = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
82 self.last_identifier_merged = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
83 self.last_offset_merged = 0
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
84 def merge_with_dict( self, new_offset_dict ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
85 if not new_offset_dict: return #no items to merge in
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
86 keys = new_offset_dict.keys()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
87 keys.sort()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
88 identifier2 = keys.pop( 0 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
89
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
90 result_offsets = OffsetList( fmt = self.fmt )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
91 offsets1 = enumerate( self.get_offsets() )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
92 try:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
93 index1, offset1 = offsets1.next()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
94 identifier1 = self.get_identifier_by_offset( offset1 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
95 except StopIteration:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
96 offset1 = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
97 identifier1 = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
98 index1 = 0
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
99
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
100 while True:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
101 if identifier1 is None and identifier2 is None:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
102 self.file = result_offsets.file #self is now merged results
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
103 return
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
104 elif identifier1 is None or ( identifier2 and identifier2 < identifier1 ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
105 result_offsets.add_offset( new_offset_dict[identifier2] )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
106 if keys:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
107 identifier2 = keys.pop( 0 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
108 else:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
109 identifier2 = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
110 elif identifier2 is None:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
111 result_offsets.file.seek( result_offsets.file_size )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
112 self.file.seek( index1 * self.fmt_size )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
113 result_offsets.file.write( self.file.read() )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
114 identifier1 = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
115 offset1 = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
116 else:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
117 result_offsets.add_offset( offset1 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
118 try:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
119 index1, offset1 = offsets1.next()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
120 identifier1 = self.get_identifier_by_offset( offset1 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
121 except StopIteration:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
122 offset1 = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
123 identifier1 = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
124 index1 += 1
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
125 #methods to help link offsets to lines, ids, etc
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
126 def get_identifier_by_line( self, line ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
127 if isinstance( line, str ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
128 fields = line.rstrip( '\r\n' ).split( self.split )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
129 if self.column < len( fields ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
130 return fields[self.column]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
131 return None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
132 def get_line_by_offset( self, offset ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
133 self.indexed_file.seek( offset )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
134 return self.indexed_file.readline()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
135 def get_identifier_by_offset( self, offset ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
136 return self.get_identifier_by_line( self.get_line_by_offset( offset ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
137
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
138 #indexed set of offsets, index is built on demand
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
139 class OffsetIndex:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
140 def __init__( self, filename, column, split = None, index_depth = 3 ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
141 self.filename = filename
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
142 self.file = open( filename, 'rb' )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
143 self.column = column
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
144 self.split = split
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
145 self._offsets = {}
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
146 self._index = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
147 self.index_depth = index_depth
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
148 def _build_index( self ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
149 self._index = {}
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
150 for start_char, sorted_offsets in self._offsets.items():
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
151 self._index[start_char]={}
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
152 for i, offset in enumerate( sorted_offsets.get_offsets() ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
153 identifier = sorted_offsets.get_identifier_by_offset( offset )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
154 if identifier[0:self.index_depth] not in self._index[start_char]:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
155 self._index[start_char][identifier[0:self.index_depth]] = i
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
156 def get_lines_by_identifier( self, identifier ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
157 if not identifier: return
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
158 #if index doesn't exist, build it
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
159 if self._index is None: self._build_index()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
160
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
161 #identifier cannot exist
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
162 if identifier[0] not in self._index or identifier[0:self.index_depth] not in self._index[identifier[0]]:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
163 return
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
164 #identifier might exist, search for it
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
165 offset_index = self._index[identifier[0]][identifier[0:self.index_depth]]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
166 while True:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
167 if offset_index >= self._offsets[identifier[0]].size:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
168 return
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
169 offset = self._offsets[identifier[0]].get_offset_by_index( offset_index )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
170 identifier2 = self._offsets[identifier[0]].get_identifier_by_offset( offset )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
171 if not identifier2 or identifier2 > identifier:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
172 return
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
173 if identifier2 == identifier:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
174 yield self._offsets[identifier[0]].get_line_by_offset( offset )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
175 offset_index += 1
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
176 def get_offsets( self ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
177 keys = self._offsets.keys()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
178 keys.sort()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
179 for key in keys:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
180 for offset in self._offsets[key].get_offsets():
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
181 yield offset
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
182 def get_line_by_offset( self, offset ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
183 self.file.seek( offset )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
184 return self.file.readline()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
185 def get_identifiers_offsets( self ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
186 keys = self._offsets.keys()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
187 keys.sort()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
188 for key in keys:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
189 for offset in self._offsets[key].get_offsets():
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
190 yield self._offsets[key].get_identifier_by_offset( offset ), offset
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
191 def get_identifier_by_line( self, line ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
192 if isinstance( line, str ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
193 fields = line.rstrip( '\r\n' ).split( self.split )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
194 if self.column < len( fields ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
195 return fields[self.column]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
196 return None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
197 def merge_with_dict( self, d ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
198 if not d: return #no data to merge
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
199 self._index = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
200 keys = d.keys()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
201 keys.sort()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
202 identifier = keys.pop( 0 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
203 first_char = identifier[0]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
204 temp = { identifier: d[identifier] }
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
205 while True:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
206 if not keys:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
207 if first_char not in self._offsets:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
208 self._offsets[first_char] = SortedOffsets( self.filename, self.column, self.split )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
209 self._offsets[first_char].merge_with_dict( temp )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
210 return
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
211 identifier = keys.pop( 0 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
212 if identifier[0] == first_char:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
213 temp[identifier] = d[identifier]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
214 else:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
215 if first_char not in self._offsets:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
216 self._offsets[first_char] = SortedOffsets( self.filename, self.column, self.split )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
217 self._offsets[first_char].merge_with_dict( temp )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
218 temp = { identifier: d[identifier] }
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
219 first_char = identifier[0]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
220
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
221 class BufferedIndex:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
222 def __init__( self, filename, column, split = None, buffer = 1000000, index_depth = 3 ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
223 self.index = OffsetIndex( filename, column, split, index_depth )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
224 self.buffered_offsets = {}
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
225 f = open( filename, 'rb' )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
226 offset = f.tell()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
227 identified_offset_count = 1
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
228 while True:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
229 offset = f.tell()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
230 line = f.readline()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
231 if not line: break #EOF
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
232 identifier = self.index.get_identifier_by_line( line )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
233 if identifier:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
234 #flush buffered offsets, if buffer size reached
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
235 if buffer and identified_offset_count % buffer == 0:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
236 self.index.merge_with_dict( self.buffered_offsets )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
237 self.buffered_offsets = {}
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
238 if identifier not in self.buffered_offsets:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
239 self.buffered_offsets[identifier] = []
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
240 self.buffered_offsets[identifier].append( offset )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
241 identified_offset_count += 1
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
242 f.close()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
243
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
244 def get_lines_by_identifier( self, identifier ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
245 for line in self.index.get_lines_by_identifier( identifier ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
246 yield line
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
247 if identifier in self.buffered_offsets:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
248 for offset in self.buffered_offsets[identifier]:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
249 yield self.index.get_line_by_offset( offset )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
250
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
251
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
252 def fill_empty_columns( line, split, fill_values ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
253 if not fill_values:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
254 return line
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
255 filled_columns = []
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
256 for i, field in enumerate( line.split( split ) ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
257 if field or i >= len( fill_values ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
258 filled_columns.append( field )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
259 else:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
260 filled_columns.append( fill_values[i] )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
261 if len( fill_values ) > len( filled_columns ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
262 filled_columns.extend( fill_values[ len( filled_columns ) : ] )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
263 return split.join( filled_columns )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
264
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
265
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
266 def join_files( filename1, column1, filename2, column2, out_filename, split = None, buffer = 1000000, keep_unmatched = False, keep_partial = False, index_depth = 3, fill_options = None ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
267 #return identifier based upon line
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
268 def get_identifier_by_line( line, column, split = None ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
269 if isinstance( line, str ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
270 fields = line.rstrip( '\r\n' ).split( split )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
271 if column < len( fields ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
272 return fields[column]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
273 return None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
274 if fill_options is None:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
275 fill_options = Bunch( fill_unjoined_only = True, file1_columns = None, file2_columns = None )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
276 out = open( out_filename, 'w+b' )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
277 index = BufferedIndex( filename2, column2, split, buffer, index_depth )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
278 for line1 in open( filename1, 'rb' ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
279 identifier = get_identifier_by_line( line1, column1, split )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
280 if identifier:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
281 written = False
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
282 for line2 in index.get_lines_by_identifier( identifier ):
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
283 if not fill_options.fill_unjoined_only:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
284 out.write( "%s%s%s\n" % ( fill_empty_columns( line1.rstrip( '\r\n' ), split, fill_options.file1_columns ), split, fill_empty_columns( line2.rstrip( '\r\n' ), split, fill_options.file2_columns ) ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
285 else:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
286 out.write( "%s%s%s\n" % ( line1.rstrip( '\r\n' ), split, line2.rstrip( '\r\n' ) ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
287 written = True
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
288 if not written and keep_unmatched:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
289 out.write( fill_empty_columns( line1.rstrip( '\r\n' ), split, fill_options.file1_columns ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
290 if fill_options:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
291 if fill_options.file2_columns:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
292 out.write( "%s%s" % ( split, fill_empty_columns( "", split, fill_options.file2_columns ) ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
293 out.write( "\n" )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
294 elif keep_partial:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
295 out.write( fill_empty_columns( line1.rstrip( '\r\n' ), split, fill_options.file1_columns ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
296 if fill_options:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
297 if fill_options.file2_columns:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
298 out.write( "%s%s" % ( split, fill_empty_columns( "", split, fill_options.file2_columns ) ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
299 out.write( "\n" )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
300 out.close()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
301
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
302 def main():
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
303 parser = optparse.OptionParser()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
304 parser.add_option(
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
305 '-b','--buffer',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
306 dest='buffer',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
307 type='int',default=1000000,
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
308 help='Number of lines to buffer at a time. Default: 1,000,000 lines. A buffer of 0 will attempt to use memory only.'
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
309 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
310 parser.add_option(
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
311 '-d','--index_depth',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
312 dest='index_depth',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
313 type='int',default=3,
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
314 help='Depth to use on filebased offset indexing. Default: 3.'
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
315 )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
316 parser.add_option(
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
317 '-p','--keep_partial',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
318 action='store_true',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
319 dest='keep_partial',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
320 default=False,
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
321 help='Keep rows in first input which are missing identifiers.')
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
322 parser.add_option(
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
323 '-u','--keep_unmatched',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
324 action='store_true',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
325 dest='keep_unmatched',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
326 default=False,
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
327 help='Keep rows in first input which are not joined with the second input.')
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
328 parser.add_option(
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
329 '-f','--fill_options_file',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
330 dest='fill_options_file',
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
331 type='str',default=None,
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
332 help='Fill empty columns with a values from a JSONified file.')
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
333
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
334
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
335 options, args = parser.parse_args()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
336
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
337 fill_options = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
338 if options.fill_options_file is not None:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
339 try:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
340 if simplejson is None:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
341 raise simplejson_exception
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
342 fill_options = Bunch( **stringify_dictionary_keys( simplejson.load( open( options.fill_options_file ) ) ) ) #simplejson.load( open( options.fill_options_file ) )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
343 except Exception, e:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
344 print "Warning: Ignoring fill options due to simplejson error (%s)." % e
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
345 if fill_options is None:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
346 fill_options = Bunch()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
347 if 'fill_unjoined_only' not in fill_options:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
348 fill_options.fill_unjoined_only = True
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
349 if 'file1_columns' not in fill_options:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
350 fill_options.file1_columns = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
351 if 'file2_columns' not in fill_options:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
352 fill_options.file2_columns = None
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
353
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
354
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
355 try:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
356 filename1 = args[0]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
357 filename2 = args[1]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
358 column1 = int( args[2] ) - 1
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
359 column2 = int( args[3] ) - 1
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
360 out_filename = args[4]
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
361 except:
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
362 print >> sys.stderr, "Error parsing command line."
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
363 sys.exit()
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
364
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
365 #Character for splitting fields and joining lines
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
366 split = "\t"
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
367
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
368 return join_files( filename1, column1, filename2, column2, out_filename, split, options.buffer, options.keep_unmatched, options.keep_partial, options.index_depth, fill_options = fill_options )
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
369
9071e359b9a3 Uploaded
xuebing
parents:
diff changeset
370 if __name__ == "__main__": main()