#Defn: A list L is almost-sorted if every element is at most one positions away from where it should be in the sorted list.

#Problem: Search for a given element x in an almost sorted list L.
#Do it efficiently.
#How efficiently?
#What is the algorithm?


def search_slow(L,x):
  """ This has the form of the O(log n) alg, but it is not actually O(log n), 
  because python doesn't have a constant-time slice operation (which it 
  should for tuples).
  
  >>> search_slow([3,1,3,5,6,11,9],11)
  True
  >>> search_slow([3,1,3,5,6,11,9],12)
  False
  """
  if len(L) < 3:
    return (len(L) >= 1 and x == L[0]) or (len(L) == 2 and x == L[1])
  mid = len(L)//2
  amax = max(L[mid-1], L[mid], L[mid+1])
  amin = min(L[mid-1], L[mid], L[mid+1])
  if x == L[mid] or x == L[mid-1] or x == L[mid+1]:
    return True
  elif x < amin: # then search within the elements to the left of mid-1
    return search_slow(L[0:mid-1], x)
  elif x > amax: # then search within the elements to the right of mid+1
    return search_slow(L[mid+2:len(L)], x)
  else:
    # This is the tricky case
    # We know amin < x < amax and that x is not one of the three midpoint 
    # elements L[mid-1], L[mid], L[mid+1].
    # So, either x is not in the list, or else it is out of place.
    # But if it's out of place, then it must be L[mid-2] or L[mid+2], since
    # it can only be out of place by 1 position.
    return ( ((mid-2 >= 0) and L[mid-2] == x) or 
             ((mid+2 < len(L)) and L[mid+2] == x) )
    
 
def search(L,x):
  """ See comments for search_slow above.
  This version is actually O(log n)
  
  >>> search([3,1,3,5,6,11,9],11)
  True
  >>> search([3,1,3,5,6,11,9],12)
  False
  >>> search([3,1,3,5,6,11,9],1)
  True
  """
  def helper(left,right):
    """Search for x within L[left:right+1], without doing any slow
    slice operations. This function follows the same form as search_slow,
    so if you don't understand a line, look at the corresponding line
    in search_slow."""    
    if (right - left) < 2:
      return ( (right-left >= 0 and x == L[left]) or 
               (right-left == 1 and x == L[left+1]) )
    mid = (left + right) // 2
    amax = max(L[mid-1], L[mid], L[mid+1])
    amin = min(L[mid-1], L[mid], L[mid+1])
    if x == L[mid] or x == L[mid-1] or x == L[mid+1]:
      return True
    elif x < amin: # search within the elements to the left of mid-1
      return helper(left, mid-2)
    elif x > amax: # search within the elements to the right of mid+1
      return helper(mid+2, right)
    else:
      # This is the tricky case
      # We know amin < x < amax and that x is not one of the three midpoint 
      # elements L[mid-1], L[mid], L[mid+1].
      # So, either x is not in the list, or else it is out of place.
      # But if it's out of place, then it must be L[mid-2] or L[mid+2], since
      # it can only be out of place by 1 position.
      return ( ((mid-2 >= left) and L[mid-2] == x) or 
               ((mid+2 <= right) and L[mid+2] == x) )
  return helper(0,len(L)-1)
    
if __name__ == '__main__':
  import doctest
  doctest.testmod()