code: def _numba_equi_join_range_join(
      left_index,
      right_index,
      slice_starts,
      slice_ends,
      ge_arr1,
      ge_arr2,
      ge_strict,
      le_arr1,
      le_arr2,
      le_strict,
  ):
      """
      Get indices for an equi join
      and a range join
      """
      length = left_index.size
      ends = np.empty(length, dtype=np.intp)
      booleans = np.ones(length, dtype=np.bool_)
      counts = 0
      for num in prange(length):
          l1 = ge_arr1[num]
          slice_start = slice_starts[num]
          slice_end = slice_ends[num]
          r1 = ge_arr2[slice_start:slice_end]
          end = np.searchsorted(r1, l1, side="right")
          if end > 0:
              if ge_strict and (l1 == r1[end - 1]):
                  end = np.searchsorted(r1, l1, side="left")
          if end == 0:
              counts += 1
              booleans[num] = False
          else:
              ends[num] = slice_start + end
      if counts == length:
          return None, None
      # cumulative array
      # used to find the first possible match
      # for the less than section below
      max_arr = np.empty_like(le_arr2)
      counter = 0  # are all groups monotonic increasing?
      for num in prange(length):
          slice_start = slice_starts[num]
          slice_end = slice_ends[num]
          r1 = le_arr2[slice_start:slice_end]
          start = r1[0]
          max_arr[slice_start] = start
          if r1.size > 1:
              for n in range(1, r1.size):
                  new_value = r1[n]
                  check = start < new_value
                  if check:
                      start = new_value
                  else:
                      counter += 1
                  max_arr[slice_start + n] = start
  
      if counts > 0:
          left_index = left_index[booleans]
          le_arr1 = le_arr1[booleans]
          ends = ends[booleans]
          slice_starts = slice_starts[booleans]
      slice_ends = ends
      ends = None
  
      length = left_index.size
      starts = np.empty(length, dtype=np.intp)
      booleans = np.ones(length, dtype=np.bool_)
      if counter == 0:
          sizes = np.empty(length, dtype=np.intp)
      counts = 0
      for num in prange(length):
          l1 = le_arr1[num]
          slice_start = slice_starts[num]
          slice_end = slice_ends[num]
          r1 = max_arr[slice_start:slice_end]
          start = np.searchsorted(r1, l1, side="left")
          if start < r1.size:
              if le_strict and (l1 == r1[start]):
                  start = np.searchsorted(r1, l1, side="right")
          if start == r1.size:
              counts += 1
              booleans[num] = False
          else:
              starts[num] = slice_start + start
              if counter == 0:
                  sizes[num] = r1.size - start
      if counts == length:
          return None, None
      if counts > 0:
          left_index = left_index[booleans]
          le_arr1 = le_arr1[booleans]
          starts = starts[booleans]
          slice_ends = slice_ends[booleans]
          if counter == 0:
              sizes = sizes[booleans]
  
      slice_starts = starts
      starts = None
  
      # no need to run a comparison
      # since all groups are monotonic increasing
      # simply create left and right indices
      if counter == 0:
          cum_sizes = np.cumsum(sizes)
          starts = np.empty(slice_ends.size, dtype=np.intp)
          starts[0] = 0
          starts[1:] = cum_sizes[:-1]
          r_index = np.empty(cum_sizes[-1], dtype=np.intp)
          l_index = np.empty(cum_sizes[-1], dtype=np.intp)
          for num in prange(slice_ends.size):
              start = starts[num]
              r_ind = slice_starts[num]
              l_ind = left_index[num]
              width = sizes[num]
              if width == 1:
                  r_index[start] = right_index[r_ind]
                  l_index[start] = l_ind
              else:
                  for n in range(width):
                      indexer = start + n
                      r_index[indexer] = right_index[r_ind + n]
                      l_index[indexer] = l_ind
  
          return l_index, r_index
  
      # get exact no of rows for left and right index
      # sizes is used to track the exact
      # number of matches per slice_start
      sizes = np.empty(slice_starts.size, dtype=np.intp)
      counts = 0
      for num in prange(slice_ends.size):
          l1 = le_arr1[num]
          start = slice_starts[num]
          end = slice_ends[num]
          r1 = le_arr2[start:end]
          internal_count = 0
          if le_strict:
              for n in range(r1.size):
                  check = l1 < r1[n]
                  internal_count += check
                  counts += check
          else:
              for n in range(r1.size):
                  check = l1 <= r1[n]
                  internal_count += check
                  counts += check
          sizes[num] = internal_count
      # populate the left and right index
      # idea is to populate the indices
      # based on the number of matches
      # per slice_start and ensure alignment with the
      # slice_start during the iteration
      r_index = np.empty(counts, dtype=np.intp)
      l_index = np.empty(counts, dtype=np.intp)
      starts = np.empty(sizes.size, dtype=np.intp)
      starts[0] = 0
      starts[1:] = np.cumsum(sizes)[:-1]
      for num in prange(sizes.size):
          l_ind = left_index[num]
          l1 = le_arr1[num]
          starter = slice_starts[num]
          slicer = slice(starter, slice_ends[num])
          r1 = le_arr2[slicer]
          start = starts[num]
          counter = sizes[num]
          if le_strict:
              for n in range(r1.size):
                  if not counter:
                      break
                  check = l1 < r1[n]
                  if not check:
                      continue
                  l_index[start] = l_ind
                  r_index[start] = right_index[starter + n]
                  counter -= 1
                  start += 1
          else:
              for n in range(r1.size):
                  if not counter:
                      break
                  check = l1 <= r1[n]
                  if not check:
                      continue
                  l_index[start] = l_ind
                  r_index[start] = right_index[starter + n]
                  counter -= 1
                  start += 1
      return l_index, r_index
  
file_path: pyjanitor/janitor/functions/_numba.py
imports: import numpy
  import pandas
  from numba import njit, prange
  from pandas.api.types import is_datetime64_dtype, is_extension_array_dtype
  from janitor.functions.utils import _generic_func_cond_join, _null_checks_cond_join, greater_than_join_types, less_than_join_types
label_desc: Use image node_id 5 for calling a global function with example usage: _numba_equi_join_range_join(left_index, right_index, slice_starts, slice_ends, ge_arr1, ge_arr2, ge_strict, le_arr1, le_arr2, le_strict) and returns: l_index,  r_index, None,  None, None,  None, l_index,  r_index