-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.py
438 lines (370 loc) · 13.4 KB
/
search.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
import array, locale, sys, os
### Estrutura de dados simples para uma proteina.
class Protein:
"""Define uma proteina pelo seu nome e respectiva sequencia.
Nota: Quando no modo de generalized suffix array o campo suffix_array
da proteina nao tem uso. No modo em que se cria um suffix array para cada
proteina fica guardado no campo respectivo uma lista de inteiros ordenados
lexicograficamente de acordo com os sufixos da sequencia que ai se iniciam"""
def __init__(self, name, sequence):
self.name = name
self.sequence = sequence
self.suffix_array = ""
### Algoritmos de construcao de suffix arrays.
def gsa(proteins):
"""Gera uma generalized suffix array na forma de uma lista de tuplos
do tipo (int, int) sendo o primeiro elemento o numero de ordem da
proteina e o segundo a posicao onde comeca o sufixo a que o tuplo
se refere."""
gsa = []
i = 0
for protein in proteins:
l = range(len(protein.sequence))
x = [i]*len(protein.sequence)
gsa.extend(zip(x, l))
i += 1
gsa.sort(key = lambda e: proteins[e[0]].sequence[e[1]:])
return gsa
def sa(proteins):
"""Gera uma suffix array para cada proteina que consiste numa lista
de inteiros onde cada inteiro corresponde ao sufixo que se inicia nessa
posicao."""
if not proteins[0].suffix_array:
for protein in proteins:
protein.suffix_array = range(len(protein.sequence))
protein.suffix_array.sort(key=lambda x: buffer(protein.sequence, x))
### Algoritmos de online search
def pof(s, p):
"""Leva a cabo uma pesquisa online utilizando uma versao modificada
do algoritmo de Boyer-Moore incorporando as ideias de Sunday e de
Horspool.
O algoritmo aqui apresentado consiste numa versao simplificada da
implementacao em C da primitiva find do python (http://bit.ly/tsiBL0)
e foi originalmente descrito pelo Fredrik Lundh (Google).
"""
def delta1(x):
"""Versao normal da funcao que gera a tabela delta1 do algoritmo
de Boyer-Moore que basicamente para cada caracter do padrao computa
a distancia entre o ultimo caracter e a ocorrencia mais a direita do
caracter em causa"""
map = { }
for i in xrange(len(x)-1, -1, -1):
c = x[i]
if c not in map:
map[c] = abs(i-len(x))
return map
n = len(s)
m = len(p)
skip = delta1(p)[p[m-1]]
i = 0
try:
while i <= n-m:
if s[i+m-1] == p[m-1]: # (boyer-moore)
# potencial match
if s[i:i+m-1] == p[:m-1]:
yield i
if s[i+m] not in p:
i = i + m + 1 # (sunday)
else:
i = i + skip # (horspool)
else:
# saltar...
if s[i+m] not in p:
i = i + m + 1 # (sunday)
else:
i = i + 1
except IndexError:
pass
def kmp(text, pattern):
"""Knuth-Morris-Pratt: Gera uma failure function (array de n+1 posicoes
sendo n o tamanho do padrao) e usa a informacao dessa tabela para decidir
quantos shifts podem ser feitos sem que se salte nenhuma ocorrencia de
pattern em text evitando assim que se reexaminem caracteres ja matched."""
pattern = list(pattern)
shifts = [1] * (len(pattern) + 1)
shift = 1
for pos in xrange(len(pattern)):
while shift <= pos and pattern[pos] != pattern[pos-shift]:
shift += shifts[pos-shift]
shifts[pos+1] = shift
startPos = 0
matchLen = 0
for c in text:
while matchLen == len(pattern) or \
matchLen >= 0 and pattern[matchLen] != c:
startPos += shifts[matchLen]
matchLen -= shifts[matchLen]
matchLen += 1
if matchLen == len(pattern):
yield startPos
def shift_and(text, pattern):
m = len(pattern)
r = ~1L
mask = array.array('l', [~0L] * (locale.CHAR_MAX+1))
for i in xrange(m):
mask[ord(pattern[i])] &= ~(1L << i)
for i in xrange(len(text)):
r |= mask[ord(text[i])]
r <<= 1
if (r & (1L << m)) == 0:
yield i - m + 1
def fuzzy_shift_and(text, pattern, k):
m = len(pattern)
r = array.array('l', [~1L] * (k+1))
mask = array.array('l', [~0L] * (locale.CHAR_MAX+1))
for i in xrange(m):
mask[ord(pattern[i])] &= ~(1L << i)
for i in xrange(len(text)):
ord1 = r[0]
r[0] |= mask[ord(text[i])]
r[0] <<= 1
for d in xrange(1,k+1):
tmp = r[d]
r[d] = ((mask[ord(text[i])] | r[d]) & ord1) << 1
ord1 = tmp
if (r[k] & (1L << m)) == 0:
yield i - m + 1
### Algoritmos de procura binaria sobre suffix array
def bs(protein, pattern):
"""Faz uma procura binaria sobre o suffix array de uma proteina e usa o
facto de que a partir do momento que seja encontrada um match esta garantido
que os restantes estejam contiguos no suffix array bastando portanto
procurar para a esquerda e direita destes ate que deixe de fazer match."""
l = 0
lp = len(pattern)
h = len(protein.sequence)
while l < h:
m = (l+h)//2
cs = protein.sequence[protein.suffix_array[m]:]
if cs[:lp] < pattern:
l = m+1
elif cs[:lp] > pattern:
h = m
else:
positions = []
om = m
try:
while protein.sequence[protein.suffix_array[m]:protein.suffix_array[m]+lp] == pattern:
positions.append(protein.suffix_array[m])
m += 1
except IndexError:
pass
m = om
try:
while protein.sequence[protein.suffix_array[m-1]:protein.suffix_array[m-1]+lp] == pattern:
positions.append(protein.suffix_array[m-1])
m -= 1
except IndexError:
pass
return positions
return []
def bsa(proteins, gsa, pattern):
"""Faz uma procura binaria sobre o suffix array generalizado e usa o
facto de que a partir do momento que seja encontrada um match esta garantido
que os restantes estejam contiguos no suffix array bastando portanto
procurar para a esquerda e direita os limites da suffix array entre os quais
existem matches. Depois de encontrados os limites percorre esse intervalo e
devolve a lista de matches pela ordem em que aparecem na gsa."""
l = 0
lp = len(pattern)
h = len(gsa)
while l < h:
m = (l+h)//2
cs = proteins[gsa[m][0]].sequence[gsa[m][1]:]
if cs[:lp] < pattern:
l = m+1
elif cs[:lp] > pattern:
h = m
else:
positions = []
i = m
j = m - 1
while i < len(gsa) and j >= 0:
match = False
if proteins[gsa[i][0]].sequence[gsa[i][1]:gsa[i][1]+lp] == pattern:
i += 1
match = True
if proteins[gsa[j][0]].sequence[gsa[j][1]:gsa[j][1]+lp] == pattern:
j -= 1
match = True
if not match:
break
return [(proteins[gsa[x][0]], gsa[x][1], gsa[x][0]) for x in xrange(j+1, i)]
return []
def hamming(sa, sb):
"""Calcula a distancia de hamming entre duas strings, i.e.,
o numero minimo de substituicoes que sao necessarias fazer para transformar
sa em sb. Se as strings nao tiverem o mesmo tamanho devolve infinito."""
if len(sa) == len(sb):
return sum(ca is not cb for ca, cb in zip(sa, sb))
else:
return float("inf")
def slices(s, k):
"""Divide uma string s em k strings de tamanho identico. As strings
maiores (se as houverem) aparecerao no inicio da lista"""
q, r = divmod(len(s), k)
ps = [min(i, r) + q*i for i in xrange(k+1)]
return [s[ps[i]:ps[i+1]] for i in xrange(k)]
def parse_proteins(fasta_file):
"""Percorre um ficheiro no formato fasta e retira o nome e sequencia
de cada proteina encontrada e adiciona-a a lista de proteinas.
Nota: O custo de adicionar uma nova proteina a lista e O(1) amortizado
pois o append esta implementado no python de forma a que quando ha a
necessidade de fazer realloc o tamanho sera 2*(tamanho actual)."""
proteins = []
lines = []
name = ""
for line in fasta_file:
if line.startswith(">"):
if name and lines:
sequence = "".join(lines)
proteins.append(Protein(name, sequence))
lines = []
name = line.split("|", 2)[1]
else:
lines.append(line.rstrip())
if name and lines:
sequence = "".join(lines)
proteins.append(Protein(name, sequence))
return proteins
### Funcoes auxiliares
def use_kmp(proteins, pattern):
for protein in proteins:
positions = kmp(protein.sequence, pattern)
for position in positions:
print " ".join([protein.name, str(position)])
def use_shift_and(proteins, pattern):
for protein in proteins:
for pos in shift_and(protein.sequence, pattern):
if pos is not None:
print " ".join([protein.name, str(pos)])
def use_fuzzy_shift_and(proteins, pattern, k):
for protein in proteins:
for pos in fuzzy_shift_and(protein.sequence, pattern, k):
if pos is not None:
print " ".join([protein.name, str(pos)])
def use_pof(proteins, pattern):
for protein in proteins:
for pos in pof(protein.sequence, pattern):
print " ".join([protein.name, str(pos)])
def use_apof(proteins, pattern, k):
"""Usa o algoritmo pof anteriormente descrito e o truque de dividir o
pattern em k+1 strings e efectua uma procura exacta online por cada
substring. Se for encontrada compara todo o padrao com o redor da ocorrencia
para verificar se a distancia de hamming e inferior a k e nesse caso
faz match."""
def find_by_slice():
for pos in pof(protein.sequence, sl):
if hamming(protein.sequence[pos-slsz:pos-slsz+lp], pattern) <= k:
yield " ".join([protein.name, str(pos-slsz)])
lp = len(pattern)
matches = []
s = {}
for protein in proteins:
slsz = 0
for sl in slices(pattern, k+1):
for match in find_by_slice():
matches.append(match)
slsz += len(sl)
matches = [s.setdefault(m,m) for m in matches if m not in s]
for match in matches:
print match
def use_bs(proteins, pattern):
if proteins[0].suffix_array:
for protein in proteins:
for pos in bs(protein, pattern):
print " ".join([protein.name, str(pos)])
else:
print "Please index database."
def use_abs(proteins, pattern, k):
"""Usa o algoritmo bs anteriormente descrito e o truque de dividir o
pattern em k+1 strings e efectua uma procura exacta online por cada
substring. Se for encontrada compara todo o padrao com o redor da ocorrencia
para verificar se a distancia de hamming e inferior a k e nesse caso
faz match."""
def find_by_slice():
for pos in bs(protein, sl):
if hamming(protein.sequence[pos-slsz:pos-slsz+lp], pattern) <= k:
yield " ".join([protein.name, str(pos-slsz)])
lp = len(pattern)
matches = []
s = {}
for protein in proteins:
slsz = 0
for sl in slices(pattern, k+1):
for match in find_by_slice():
matches.append(match)
slsz += len(sl)
matches = [s.setdefault(m,m) for m in matches if m not in s]
for match in matches:
print match
def use_bsa(proteins, gsa, pattern):
if gsa:
for pos in bsa(proteins, gsa, pattern):
print " ".join([pos[0].name, str(pos[1])])
else:
print "Please index database."
def use_absa(proteins, gsa, pattern, k):
"""Usa o algoritmo anteriormente descrito para procura binaria sobre uma
generalized suffix array e o truque de dividir o pattern em k+1 strings
fazendo uma procura exacta por cada uma dessas strings. Na ocorrencia de um
match verifica se a distancia de hamming entre o pattern e o redor da posicao
do texto e inferior ou igual a k e nesse caso guarda o numero de ordem da
proteina e a posicao da ocorrencia.
No final e feita uma ordenacao por numero de ordem com criterio de desempate
baseado na posicao da ocorrencia."""
def find_by_slice():
for pos in bsa(proteins, gsa, sl):
if hamming(pos[0].sequence[pos[1]-slsz:pos[1]-slsz+lp], pattern) <= k:
yield (pos[2], str(pos[1]-slsz))
if not gsa:
print "Please index database."
return
lp = len(pattern)
matches = []
s = {}
slsz = 0
for sl in slices(pattern, k+1):
for prot, pos in find_by_slice():
matches.append((prot, int(pos)))
slsz += len(sl)
matches = [s.setdefault(m,m) for m in matches if m not in s]
matches.sort()
for match in matches:
print " ".join([proteins[match[0]].name, str(match[1])])
def parse_input(database_file):
proteins = parse_proteins(database_file)
gs = None
while True:
try:
line = raw_input()
line = line.split(' ')
if line[0] == "E":
#use_shift_and(proteins, line[1]) # procura exacta shift-and
use_pof(proteins, line[1]) # procura exacta com boyer-moore modificado
#use_kmp(proteins, line[1]) # procura exacta com knuth-morris-pratt
elif line[0] == "I":
gs = gsa(proteins) # suffix array generalizado
#sa(proteins) # suffix array por proteina
print "Database indexed."
elif line[0] == "B":
use_bsa(proteins, gs, line[1]) # procura binaria generalized suffix array
#use_bs(proteins, line[1]) # procura binaria em suffix array por proteina
elif line[0] == "A":
use_fuzzy_shift_and(proteins, line[2], int(line[1]))
#use_apof(proteins, line[2], int(line[1])) # procura aproximada com pof
elif line[0] == "F":
use_absa(proteins, gs, line[2], int(line[1])) # procura aproximada com bsa
#use_abs(proteins, line[2], int(line[1])) # procura aproximada com bs
else:
pass
except EOFError:
sys.exit(0)
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit("Usage: %s fasta-file" % sys.argv[0])
if not os.path.exists(sys.argv[1]):
sys.exit("Oops: Fasta file %s not found." % sys.argv[1])
parse_input(open(sys.argv[1]))