binary.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. # Copyright 2010 Matt Chaput. All rights reserved.
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions are met:
  5. #
  6. # 1. Redistributions of source code must retain the above copyright notice,
  7. # this list of conditions and the following disclaimer.
  8. #
  9. # 2. Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. #
  13. # THIS SOFTWARE IS PROVIDED BY MATT CHAPUT ``AS IS'' AND ANY EXPRESS OR
  14. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  15. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  16. # EVENT SHALL MATT CHAPUT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  17. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  18. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  19. # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  20. # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  21. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  22. # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23. #
  24. # The views and conclusions contained in the software and documentation are
  25. # those of the authors and should not be interpreted as representing official
  26. # policies, either expressed or implied, of Matt Chaput.
  27. from whoosh.matching import mcore
  28. class BiMatcher(mcore.Matcher):
  29. """Base class for matchers that combine the results of two sub-matchers in
  30. some way.
  31. """
  32. def __init__(self, a, b):
  33. super(BiMatcher, self).__init__()
  34. self.a = a
  35. self.b = b
  36. def reset(self):
  37. self.a.reset()
  38. self.b.reset()
  39. def __repr__(self):
  40. return "%s(%r, %r)" % (self.__class__.__name__, self.a, self.b)
  41. def children(self):
  42. return [self.a, self.b]
  43. def copy(self):
  44. return self.__class__(self.a.copy(), self.b.copy())
  45. def depth(self):
  46. return 1 + max(self.a.depth(), self.b.depth())
  47. def skip_to(self, id):
  48. if not self.is_active():
  49. raise mcore.ReadTooFar
  50. ra = self.a.skip_to(id)
  51. rb = self.b.skip_to(id)
  52. return ra or rb
  53. def supports_block_quality(self):
  54. return (self.a.supports_block_quality()
  55. and self.b.supports_block_quality())
  56. def supports(self, astype):
  57. return self.a.supports(astype) and self.b.supports(astype)
  58. class AdditiveBiMatcher(BiMatcher):
  59. """Base class for binary matchers where the scores of the sub-matchers are
  60. added together.
  61. """
  62. def max_quality(self):
  63. q = 0.0
  64. if self.a.is_active():
  65. q += self.a.max_quality()
  66. if self.b.is_active():
  67. q += self.b.max_quality()
  68. return q
  69. def block_quality(self):
  70. bq = 0.0
  71. if self.a.is_active():
  72. bq += self.a.block_quality()
  73. if self.b.is_active():
  74. bq += self.b.block_quality()
  75. return bq
  76. def weight(self):
  77. return (self.a.weight() + self.b.weight())
  78. def score(self):
  79. return (self.a.score() + self.b.score())
  80. def __eq__(self, other):
  81. return self.__class__ is type(other)
  82. def __lt__(self, other):
  83. return type(other) is self.__class__
  84. def __ne__(self, other):
  85. return not self.__eq__(other)
  86. def __gt__(self, other):
  87. return not (self.__lt__(other) or self.__eq__(other))
  88. def __le__(self, other):
  89. return self.__eq__(other) or self.__lt__(other)
  90. def __ge__(self, other):
  91. return self.__eq__(other) or self.__gt__(other)
  92. class UnionMatcher(AdditiveBiMatcher):
  93. """Matches the union (OR) of the postings in the two sub-matchers.
  94. """
  95. _id = None
  96. def replace(self, minquality=0):
  97. a = self.a
  98. b = self.b
  99. a_active = a.is_active()
  100. b_active = b.is_active()
  101. # If neither sub-matcher on its own has a high enough max quality to
  102. # contribute, convert to an intersection matcher
  103. if minquality and a_active and b_active:
  104. a_max = a.max_quality()
  105. b_max = b.max_quality()
  106. if a_max < minquality and b_max < minquality:
  107. return IntersectionMatcher(a, b).replace(minquality)
  108. elif a_max < minquality:
  109. return AndMaybeMatcher(b, a)
  110. elif b_max < minquality:
  111. return AndMaybeMatcher(a, b)
  112. # If one or both of the sub-matchers are inactive, convert
  113. if not (a_active or b_active):
  114. return mcore.NullMatcher()
  115. elif not a_active:
  116. return b.replace(minquality)
  117. elif not b_active:
  118. return a.replace(minquality)
  119. a = a.replace(minquality - b.max_quality() if minquality else 0)
  120. b = b.replace(minquality - a.max_quality() if minquality else 0)
  121. # If one of the sub-matchers changed, return a new union
  122. if a is not self.a or b is not self.b:
  123. return self.__class__(a, b)
  124. else:
  125. self._id = None
  126. return self
  127. def is_active(self):
  128. return self.a.is_active() or self.b.is_active()
  129. def skip_to(self, id):
  130. self._id = None
  131. ra = rb = False
  132. if self.a.is_active():
  133. ra = self.a.skip_to(id)
  134. if self.b.is_active():
  135. rb = self.b.skip_to(id)
  136. return ra or rb
  137. def id(self):
  138. _id = self._id
  139. if _id is not None:
  140. return _id
  141. a = self.a
  142. b = self.b
  143. if not a.is_active():
  144. _id = b.id()
  145. elif not b.is_active():
  146. _id = a.id()
  147. else:
  148. _id = min(a.id(), b.id())
  149. self._id = _id
  150. return _id
  151. # Using sets is faster in most cases, but could potentially use a lot of
  152. # memory. Comment out this method override to not use sets.
  153. #def all_ids(self):
  154. # return iter(sorted(set(self.a.all_ids()) | set(self.b.all_ids())))
  155. def next(self):
  156. self._id = None
  157. a = self.a
  158. b = self.b
  159. a_active = a.is_active()
  160. b_active = b.is_active()
  161. # Shortcut when one matcher is inactive
  162. if not (a_active or b_active):
  163. raise mcore.ReadTooFar
  164. elif not a_active:
  165. return b.next()
  166. elif not b_active:
  167. return a.next()
  168. a_id = a.id()
  169. b_id = b.id()
  170. ar = br = None
  171. # After all that, here's the actual implementation
  172. if a_id <= b_id:
  173. ar = a.next()
  174. if b_id <= a_id:
  175. br = b.next()
  176. return ar or br
  177. def spans(self):
  178. if not self.a.is_active():
  179. return self.b.spans()
  180. if not self.b.is_active():
  181. return self.a.spans()
  182. id_a = self.a.id()
  183. id_b = self.b.id()
  184. if id_a < id_b:
  185. return self.a.spans()
  186. elif id_b < id_a:
  187. return self.b.spans()
  188. else:
  189. return sorted(set(self.a.spans()) | set(self.b.spans()))
  190. def weight(self):
  191. a = self.a
  192. b = self.b
  193. if not a.is_active():
  194. return b.weight()
  195. if not b.is_active():
  196. return a.weight()
  197. id_a = a.id()
  198. id_b = b.id()
  199. if id_a < id_b:
  200. return a.weight()
  201. elif id_b < id_a:
  202. return b.weight()
  203. else:
  204. return (a.weight() + b.weight())
  205. def score(self):
  206. a = self.a
  207. b = self.b
  208. if not a.is_active():
  209. return b.score()
  210. if not b.is_active():
  211. return a.score()
  212. id_a = a.id()
  213. id_b = b.id()
  214. if id_a < id_b:
  215. return a.score()
  216. elif id_b < id_a:
  217. return b.score()
  218. else:
  219. return (a.score() + b.score())
  220. def skip_to_quality(self, minquality):
  221. self._id = None
  222. a = self.a
  223. b = self.b
  224. if not (a.is_active() or b.is_active()):
  225. raise mcore.ReadTooFar
  226. # Short circuit if one matcher is inactive
  227. if not a.is_active():
  228. return b.skip_to_quality(minquality)
  229. elif not b.is_active():
  230. return a.skip_to_quality(minquality)
  231. skipped = 0
  232. aq = a.block_quality()
  233. bq = b.block_quality()
  234. while a.is_active() and b.is_active() and aq + bq <= minquality:
  235. if aq < bq:
  236. skipped += a.skip_to_quality(minquality - bq)
  237. aq = a.block_quality()
  238. else:
  239. skipped += b.skip_to_quality(minquality - aq)
  240. bq = b.block_quality()
  241. return skipped
  242. class DisjunctionMaxMatcher(UnionMatcher):
  243. """Matches the union (OR) of two sub-matchers. Where both sub-matchers
  244. match the same posting, returns the weight/score of the higher-scoring
  245. posting.
  246. """
  247. # TODO: this class inherits from AdditiveBiMatcher (through UnionMatcher)
  248. # but it does not add the scores of the sub-matchers together (it
  249. # overrides all methods that perform addition). Need to clean up the
  250. # inheritance.
  251. def __init__(self, a, b, tiebreak=0.0):
  252. super(DisjunctionMaxMatcher, self).__init__(a, b)
  253. self.tiebreak = tiebreak
  254. def copy(self):
  255. return self.__class__(self.a.copy(), self.b.copy(),
  256. tiebreak=self.tiebreak)
  257. def replace(self, minquality=0):
  258. a = self.a
  259. b = self.b
  260. a_active = a.is_active()
  261. b_active = b.is_active()
  262. # DisMax takes the max of the sub-matcher qualities instead of adding
  263. # them, so we need special logic here
  264. if minquality and a_active and b_active:
  265. a_max = a.max_quality()
  266. b_max = b.max_quality()
  267. if a_max < minquality and b_max < minquality:
  268. # If neither sub-matcher has a high enough max quality to
  269. # contribute, return an inactive matcher
  270. return mcore.NullMatcher()
  271. elif b_max < minquality:
  272. # If the b matcher can't contribute, return a
  273. return a.replace(minquality)
  274. elif a_max < minquality:
  275. # If the a matcher can't contribute, return b
  276. return b.replace(minquality)
  277. if not (a_active or b_active):
  278. return mcore.NullMatcher()
  279. elif not a_active:
  280. return b.replace(minquality)
  281. elif not b_active:
  282. return a.replace(minquality)
  283. # We CAN pass the minquality down here, since we don't add the two
  284. # scores together
  285. a = a.replace(minquality)
  286. b = b.replace(minquality)
  287. a_active = a.is_active()
  288. b_active = b.is_active()
  289. # It's kind of tedious to check for inactive sub-matchers all over
  290. # again here after we replace them, but it's probably better than
  291. # returning a replacement with an inactive sub-matcher
  292. if not (a_active and b_active):
  293. return mcore.NullMatcher()
  294. elif not a_active:
  295. return b
  296. elif not b_active:
  297. return a
  298. elif a is not self.a or b is not self.b:
  299. # If one of the sub-matchers changed, return a new DisMax
  300. return self.__class__(a, b)
  301. else:
  302. return self
  303. def score(self):
  304. if not self.a.is_active():
  305. return self.b.score()
  306. elif not self.b.is_active():
  307. return self.a.score()
  308. else:
  309. return max(self.a.score(), self.b.score())
  310. def max_quality(self):
  311. return max(self.a.max_quality(), self.b.max_quality())
  312. def block_quality(self):
  313. return max(self.a.block_quality(), self.b.block_quality())
  314. def skip_to_quality(self, minquality):
  315. a = self.a
  316. b = self.b
  317. # Short circuit if one matcher is inactive
  318. if not a.is_active():
  319. sk = b.skip_to_quality(minquality)
  320. return sk
  321. elif not b.is_active():
  322. return a.skip_to_quality(minquality)
  323. skipped = 0
  324. aq = a.block_quality()
  325. bq = b.block_quality()
  326. while a.is_active() and b.is_active() and max(aq, bq) <= minquality:
  327. if aq <= minquality:
  328. skipped += a.skip_to_quality(minquality)
  329. aq = a.block_quality()
  330. if bq <= minquality:
  331. skipped += b.skip_to_quality(minquality)
  332. bq = b.block_quality()
  333. return skipped
  334. class IntersectionMatcher(AdditiveBiMatcher):
  335. """Matches the intersection (AND) of the postings in the two sub-matchers.
  336. """
  337. def __init__(self, a, b):
  338. super(IntersectionMatcher, self).__init__(a, b)
  339. self._find_first()
  340. def reset(self):
  341. self.a.reset()
  342. self.b.reset()
  343. self._find_first()
  344. def _find_first(self):
  345. if (self.a.is_active()
  346. and self.b.is_active()
  347. and self.a.id() != self.b.id()):
  348. self._find_next()
  349. def replace(self, minquality=0):
  350. a = self.a
  351. b = self.b
  352. a_active = a.is_active()
  353. b_active = b.is_active()
  354. if not (a_active and b_active):
  355. # Intersection matcher requires that both sub-matchers be active
  356. return mcore.NullMatcher()
  357. if minquality:
  358. a_max = a.max_quality()
  359. b_max = b.max_quality()
  360. if a_max + b_max < minquality:
  361. # If the combined quality of the sub-matchers can't contribute,
  362. # return an inactive matcher
  363. return mcore.NullMatcher()
  364. # Require that the replacements be able to contribute results
  365. # higher than the minquality
  366. a_min = minquality - b_max
  367. b_min = minquality - a_max
  368. else:
  369. a_min = b_min = 0
  370. a = a.replace(a_min)
  371. b = b.replace(b_min)
  372. a_active = a.is_active()
  373. b_active = b.is_active()
  374. if not (a_active or b_active):
  375. return mcore.NullMatcher()
  376. elif not a_active:
  377. return b
  378. elif not b_active:
  379. return a
  380. elif a is not self.a or b is not self.b:
  381. return self.__class__(a, b)
  382. else:
  383. return self
  384. def is_active(self):
  385. return self.a.is_active() and self.b.is_active()
  386. def _find_next(self):
  387. a = self.a
  388. b = self.b
  389. a_id = a.id()
  390. b_id = b.id()
  391. assert a_id != b_id
  392. r = False
  393. while a.is_active() and b.is_active() and a_id != b_id:
  394. if a_id < b_id:
  395. ra = a.skip_to(b_id)
  396. if not a.is_active():
  397. return
  398. r = r or ra
  399. a_id = a.id()
  400. else:
  401. rb = b.skip_to(a_id)
  402. if not b.is_active():
  403. return
  404. r = r or rb
  405. b_id = b.id()
  406. return r
  407. def id(self):
  408. return self.a.id()
  409. # Using sets is faster in some cases, but could potentially use a lot of
  410. # memory
  411. def all_ids(self):
  412. return iter(sorted(set(self.a.all_ids()) & set(self.b.all_ids())))
  413. def skip_to(self, id):
  414. if not self.is_active():
  415. raise mcore.ReadTooFar
  416. ra = self.a.skip_to(id)
  417. rb = self.b.skip_to(id)
  418. if self.is_active():
  419. rn = False
  420. if self.a.id() != self.b.id():
  421. rn = self._find_next()
  422. return ra or rb or rn
  423. def skip_to_quality(self, minquality):
  424. a = self.a
  425. b = self.b
  426. minquality = minquality
  427. skipped = 0
  428. aq = a.block_quality()
  429. bq = b.block_quality()
  430. while a.is_active() and b.is_active() and aq + bq <= minquality:
  431. if aq < bq:
  432. # If the block quality of A is less than B, skip A ahead until
  433. # it can contribute at least the balance of the required min
  434. # quality when added to B
  435. sk = a.skip_to_quality(minquality - bq)
  436. skipped += sk
  437. if not sk and a.is_active():
  438. # The matcher couldn't skip ahead for some reason, so just
  439. # advance and try again
  440. a.next()
  441. else:
  442. # And vice-versa
  443. sk = b.skip_to_quality(minquality - aq)
  444. skipped += sk
  445. if not sk and b.is_active():
  446. b.next()
  447. if not a.is_active() or not b.is_active():
  448. # One of the matchers is exhausted
  449. break
  450. if a.id() != b.id():
  451. # We want to always leave in a state where the matchers are at
  452. # the same document, so call _find_next() to sync them
  453. self._find_next()
  454. # Get the block qualities at the new matcher positions
  455. aq = a.block_quality()
  456. bq = b.block_quality()
  457. return skipped
  458. def next(self):
  459. if not self.is_active():
  460. raise mcore.ReadTooFar
  461. # We must assume that the ids are equal whenever next() is called (they
  462. # should have been made equal by _find_next), so advance them both
  463. ar = self.a.next()
  464. if self.is_active():
  465. nr = self._find_next()
  466. return ar or nr
  467. def spans(self):
  468. return sorted(set(self.a.spans()) | set(self.b.spans()))
  469. class AndNotMatcher(BiMatcher):
  470. """Matches the postings in the first sub-matcher that are NOT present in
  471. the second sub-matcher.
  472. """
  473. def __init__(self, a, b):
  474. super(AndNotMatcher, self).__init__(a, b)
  475. self._find_first()
  476. def reset(self):
  477. self.a.reset()
  478. self.b.reset()
  479. self._find_first()
  480. def _find_first(self):
  481. if (self.a.is_active()
  482. and self.b.is_active()
  483. and self.a.id() == self.b.id()):
  484. self._find_next()
  485. def is_active(self):
  486. return self.a.is_active()
  487. def _find_next(self):
  488. pos = self.a
  489. neg = self.b
  490. if not neg.is_active():
  491. return
  492. pos_id = pos.id()
  493. r = False
  494. if neg.id() < pos_id:
  495. neg.skip_to(pos_id)
  496. while pos.is_active() and neg.is_active() and pos_id == neg.id():
  497. nr = pos.next()
  498. if not pos.is_active():
  499. break
  500. r = r or nr
  501. pos_id = pos.id()
  502. neg.skip_to(pos_id)
  503. return r
  504. def supports_block_quality(self):
  505. return self.a.supports_block_quality()
  506. def replace(self, minquality=0):
  507. if not self.a.is_active():
  508. # The a matcher is required, so if it's inactive, return an
  509. # inactive matcher
  510. return mcore.NullMatcher()
  511. elif (minquality
  512. and self.a.max_quality() < minquality):
  513. # If the quality of the required matcher isn't high enough to
  514. # contribute, return an inactive matcher
  515. return mcore.NullMatcher()
  516. elif not self.b.is_active():
  517. # If the prohibited matcher is inactive, convert to just the
  518. # required matcher
  519. return self.a.replace(minquality)
  520. a = self.a.replace(minquality)
  521. b = self.b.replace()
  522. if a is not self.a or b is not self.b:
  523. # If one of the sub-matchers was replaced, return a new AndNot
  524. return self.__class__(a, b)
  525. else:
  526. return self
  527. def max_quality(self):
  528. return self.a.max_quality()
  529. def block_quality(self):
  530. return self.a.block_quality()
  531. def skip_to_quality(self, minquality):
  532. skipped = self.a.skip_to_quality(minquality)
  533. self._find_next()
  534. return skipped
  535. def id(self):
  536. return self.a.id()
  537. def next(self):
  538. if not self.a.is_active():
  539. raise mcore.ReadTooFar
  540. ar = self.a.next()
  541. nr = False
  542. if self.a.is_active() and self.b.is_active():
  543. nr = self._find_next()
  544. return ar or nr
  545. def skip_to(self, id):
  546. if not self.a.is_active():
  547. raise mcore.ReadTooFar
  548. if id < self.a.id():
  549. return
  550. self.a.skip_to(id)
  551. if self.b.is_active():
  552. self.b.skip_to(id)
  553. self._find_next()
  554. def weight(self):
  555. return self.a.weight()
  556. def score(self):
  557. return self.a.score()
  558. def supports(self, astype):
  559. return self.a.supports(astype)
  560. def value(self):
  561. return self.a.value()
  562. def value_as(self, astype):
  563. return self.a.value_as(astype)
  564. class AndMaybeMatcher(AdditiveBiMatcher):
  565. """Matches postings in the first sub-matcher, and if the same posting is
  566. in the second sub-matcher, adds their scores.
  567. """
  568. def __init__(self, a, b):
  569. AdditiveBiMatcher.__init__(self, a, b)
  570. self._first_b()
  571. def reset(self):
  572. self.a.reset()
  573. self.b.reset()
  574. self._first_b()
  575. def _first_b(self):
  576. a = self.a
  577. b = self.b
  578. if a.is_active() and b.is_active() and a.id() != b.id():
  579. b.skip_to(a.id())
  580. def is_active(self):
  581. return self.a.is_active()
  582. def id(self):
  583. return self.a.id()
  584. def next(self):
  585. if not self.a.is_active():
  586. raise mcore.ReadTooFar
  587. ar = self.a.next()
  588. br = False
  589. if self.a.is_active() and self.b.is_active():
  590. br = self.b.skip_to(self.a.id())
  591. return ar or br
  592. def skip_to(self, id):
  593. if not self.a.is_active():
  594. raise mcore.ReadTooFar
  595. ra = self.a.skip_to(id)
  596. rb = False
  597. if self.a.is_active() and self.b.is_active():
  598. rb = self.b.skip_to(id)
  599. return ra or rb
  600. def replace(self, minquality=0):
  601. a = self.a
  602. b = self.b
  603. a_active = a.is_active()
  604. b_active = b.is_active()
  605. if not a_active:
  606. return mcore.NullMatcher()
  607. elif minquality and b_active:
  608. if a.max_quality() + b.max_quality() < minquality:
  609. # If the combined max quality of the sub-matchers isn't high
  610. # enough to possibly contribute, return an inactive matcher
  611. return mcore.NullMatcher()
  612. elif a.max_quality() < minquality:
  613. # If the max quality of the main sub-matcher isn't high enough
  614. # to ever contribute without the optional sub- matcher, change
  615. # into an IntersectionMatcher
  616. return IntersectionMatcher(self.a, self.b)
  617. elif not b_active:
  618. return a.replace(minquality)
  619. new_a = a.replace(minquality - b.max_quality())
  620. new_b = b.replace(minquality - a.max_quality())
  621. if new_a is not a or new_b is not b:
  622. # If one of the sub-matchers changed, return a new AndMaybe
  623. return self.__class__(new_a, new_b)
  624. else:
  625. return self
  626. def skip_to_quality(self, minquality):
  627. a = self.a
  628. b = self.b
  629. minquality = minquality
  630. if not a.is_active():
  631. raise mcore.ReadTooFar
  632. if not b.is_active():
  633. return a.skip_to_quality(minquality)
  634. skipped = 0
  635. aq = a.block_quality()
  636. bq = b.block_quality()
  637. while a.is_active() and b.is_active() and aq + bq <= minquality:
  638. if aq < bq:
  639. skipped += a.skip_to_quality(minquality - bq)
  640. aq = a.block_quality()
  641. else:
  642. skipped += b.skip_to_quality(minquality - aq)
  643. bq = b.block_quality()
  644. return skipped
  645. def weight(self):
  646. if self.a.id() == self.b.id():
  647. return self.a.weight() + self.b.weight()
  648. else:
  649. return self.a.weight()
  650. def score(self):
  651. if self.b.is_active() and self.a.id() == self.b.id():
  652. return self.a.score() + self.b.score()
  653. else:
  654. return self.a.score()
  655. def supports(self, astype):
  656. return self.a.supports(astype)
  657. def value(self):
  658. return self.a.value()
  659. def value_as(self, astype):
  660. return self.a.value_as(astype)