docstr: Refines a split by looking at both the first words from a new page and the last words from the previous page.
  
  Args:
      index (int): paragraph index
      first (str): first words
      last (str): last words
      delta (int, optional): paragraph search radius. Defaults to 5.
  
  Returns:
      Tuple[int, int, float]: split prediction
docstr_tok: ["Refines","a","split","by","looking","at","both","the","first","words","from","a","new","page","and","the","last","words","from","the","previous","page",".","Args",":","index","(","int",")",":","paragraph","index","first","(","str",")",":","first","words","last","(","str",")",":","last","words","delta","(","int",",","optional",")",":","paragraph","search","radius",".","Defaults","to","5",".","Returns",":","Tuple","[","int",",","int",",","float","]",":","split","prediction"]
code: def split_first_last(
      self, index: int, first: str, last: str, delta: int = 5
  ) -> Tuple[int, int, float]:
      """Refines a split by looking at both the first words from a new page and the last words from the previous page.
  
      Args:
          index (int): paragraph index
          first (str): first words
          last (str): last words
          delta (int, optional): paragraph search radius. Defaults to 5.
  
      Returns:
          Tuple[int, int, float]: split prediction
      """
      if first:
          first_split = glob_f, char_f, score_f = self.split(
              index, first, delta=delta
          )
      if last:
          last_split = glob_l, char_l, score_l = self.split(
              index, last, delta=delta, reverse=True
          )
      if first and not last:
          return first_split
      elif not first and last:
          return last_split
      elif not first and not last:
          return index, 0, 0.0
      if (
          char_f == char_l
          and glob_f == glob_l
          and (score_f > 0.5 or score_l > 0.5)
      ):
          return glob_l, char_l, 1.0
  
      # score calculation
      first, last = self.remove_special_chars(
          first
      ), self.remove_special_chars(last)
      matching = []
      for split in (first_split, last_split):
          first_source = []
          num_chars_first = len(first)
          num_chars_last = len(last)
          last_source = []
          for i, p in enumerate(self.paragraphs[split[0] :]):
              if i == 0:
                  p = p[split[1] :]
              first_source.append(self.remove_special_chars(p))
              if sum([len(s) for s in first_source]) >= num_chars_first:
                  break
          first_source = "".join(first_source)[:num_chars_first]
          for i, p in enumerate(self.paragraphs[split[0] :: -1]):
              if i == 0:
                  p = p[: split[1]]
              last_source.insert(0, self.remove_special_chars(p))
              if sum([len(s) for s in last_source]) >= num_chars_last:
                  last_source = last_source
                  break
          last_source = "".join(last_source)[-num_chars_last:]
          matching.append(
              [
                  Levenshtein.ratio(first, first_source)
                  * Levenshtein.ratio(first[:10], first_source[:10]),
                  Levenshtein.ratio(last, last_source)
                  * Levenshtein.ratio(last[-10:], last_source[-10:]),
              ]
          )
      scores = np.asarray(matching).max(0)
      return (
          (glob_l, char_l, scores[1])
          if scores.argmax()
          else (glob_f, char_f, scores[0])
      )
  
imports: from difflib import SequenceMatcher
  from operator import itemgetter
  from typing import List, Tuple, Union
  import re
  import numpy
  from Levenshtein.StringMatcher import StringMatcher
  import Levenshtein
  from fuzzysearch import find_near_matches
label_desc: Use image node_id 4 for calling the Splitter obj's underlying member method code with example usage: obj.split_first_last(index, first, last, delta) and returns: unknown, first_split, glob_l,  char_l,  int, last_split, index,  int,  int