{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "toc": "true"
   },
   "source": [
    "# Table of Contents\n",
    " <p><div class=\"lev1 toc-item\"><a href=\"#The-Problem\" data-toc-modified-id=\"The-Problem-1\"><span class=\"toc-item-num\">1&nbsp;&nbsp;</span>The Problem</a></div><div class=\"lev2 toc-item\"><a href=\"#The-way-I-used-previously\" data-toc-modified-id=\"The-way-I-used-previously-1.1\"><span class=\"toc-item-num\">1.1&nbsp;&nbsp;</span>The way I used previously</a></div><div class=\"lev1 toc-item\"><a href=\"#What-I-wanted\" data-toc-modified-id=\"What-I-wanted-2\"><span class=\"toc-item-num\">2&nbsp;&nbsp;</span>What I wanted</a></div><div class=\"lev1 toc-item\"><a href=\"#Initial-attempt\" data-toc-modified-id=\"Initial-attempt-3\"><span class=\"toc-item-num\">3&nbsp;&nbsp;</span>Initial attempt</a></div><div class=\"lev2 toc-item\"><a href=\"#It-works-but...\" data-toc-modified-id=\"It-works-but...-3.1\"><span class=\"toc-item-num\">3.1&nbsp;&nbsp;</span>It works but...</a></div><div class=\"lev1 toc-item\"><a href=\"#Improved\" data-toc-modified-id=\"Improved-4\"><span class=\"toc-item-num\">4&nbsp;&nbsp;</span>Improved</a></div><div class=\"lev2 toc-item\"><a href=\"#This-works-but...\" data-toc-modified-id=\"This-works-but...-4.1\"><span class=\"toc-item-num\">4.1&nbsp;&nbsp;</span>This works but...</a></div><div class=\"lev1 toc-item\"><a href=\"#Special-case-handling\" data-toc-modified-id=\"Special-case-handling-5\"><span class=\"toc-item-num\">5&nbsp;&nbsp;</span>Special case handling</a></div><div class=\"lev2 toc-item\"><a href=\"#No-lower-limit\" data-toc-modified-id=\"No-lower-limit-5.1\"><span class=\"toc-item-num\">5.1&nbsp;&nbsp;</span>No lower limit</a></div><div class=\"lev2 toc-item\"><a href=\"#No-upper-limit\" data-toc-modified-id=\"No-upper-limit-5.2\"><span class=\"toc-item-num\">5.2&nbsp;&nbsp;</span>No upper limit</a></div><div class=\"lev2 toc-item\"><a href=\"#takebetween-with-special-case-handling.\" data-toc-modified-id=\"takebetween-with-special-case-handling.-5.3\"><span class=\"toc-item-num\">5.3&nbsp;&nbsp;</span>takebetween with special case handling.</a></div><div class=\"lev1 toc-item\"><a href=\"#END.\" data-toc-modified-id=\"END.-6\"><span class=\"toc-item-num\">6&nbsp;&nbsp;</span>END.</a></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# The Problem\n",
    "In my last [blog](http://paddy3118.blogspot.com/2018/11/to-infinity-beyond.html) I had generated a sequence of primes:\n",
    "\n",
    "    ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ •••\n",
    "\n",
    "And for answers to a Rosetta Code [task](http://rosettacode.org/wiki/Extensible_prime_generator#Python:_Iterative_sieve_on_unbounded_count_from_2) I needed to select values that were in a particular range:\n",
    "\n",
    "    ⭘ ⭘ ⭘ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ •••\n",
    "\n",
    "Lets use a simple sequence of squares of the integers 0,1, ... in this post and address the problem of __selecting the squares whose values lie in a particular range__.\n",
    "\n",
    "**Square generator**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "from itertools import count, islice\n",
    "\n",
    "def squares():\n",
    "    for i in count():\n",
    "        yield i * i"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's show some values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196]"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(islice(squares(), 15))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The way I used previously\n",
    "Let us say I wanted to show the squares having values between 10 and 90?\n",
    "lets try with [takewhile](https://docs.python.org/3.7/library/itertools.html#itertools.takewhile) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from itertools import takewhile\n",
    "\n",
    "def true_when_in_range(x):\n",
    "    return 10 <= x < 90\n",
    "\n",
    "list(takewhile(true_when_in_range, squares()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It does not work because it stops passing values as soon as its predicate function is false, and the initial value from th squares iterator are not in the range.\n",
    "\n",
    "If we take the range:\n",
    "\n",
    "    ⭘ ⭘ ⭘ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⭘ ⭘ ⭘ ⭘ ⭘ ⭘ •••\n",
    "\n",
    "And take it as extending from its start (S), to just before its stop or finish (F) values. \n",
    "\n",
    "    ⭘ ⭘ ⭘ S ⬤ ⬤ ⬤ ⬤ ⬤ F ⭘ ⭘ ⭘ ⭘ ⭘ •••\n",
    "\n",
    "I realise the range filtring by using `takewhile` to filter all values up to the stop limit\n",
    "\n",
    "    ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤\n",
    "... within an enclosing comprehension that removed all values lower than the start limit of the needed range \n",
    "\n",
    "    _ _ _ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "def lt_90(x): return x < 90\n",
    "\n",
    "print([x for x in takewhile(lt_90, squares()) if x >= 10])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# What I wanted\n",
    "I would have liked to define a start predicate and a stop predicate function and have the iterator values from and including, when the start predicate is first True; up to but *not* including the value that first makes the stop prdicate true.\n",
    "\n",
    "# Initial attempt\n",
    "Let's call the function `takebetween` and define the start and stop predicate functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ge_90(x): return x >= 90\n",
    "def ge_10(x): return x >= 10\n",
    "\n",
    "def takebetween(start, stop, iterable):\n",
    "    inbetween = False\n",
    "    for x in iterable:\n",
    "        if not inbetween:\n",
    "            if start(x):\n",
    "                yield x\n",
    "                inbetween = True\n",
    "        else:\n",
    "            if stop(x):\n",
    "                break\n",
    "            yield x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "print(list(takebetween(ge_10, ge_90, squares())))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## It works but...\n",
    "I would like greater functionality.\n",
    "\n",
    "From meesing around, I think it would be good that if the `stop` predicate is not given, then it should work as if the invrse of the start function is meant, that would allow a start predicate of a range and the stop be implied\n",
    "\n",
    "Something equivalent to:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "def range_10_to_90(x): \n",
    "    return 10 <= x < 90\n",
    "def outside_range(x): \n",
    "    return not range_10_to_90(x)\n",
    "\n",
    "print(list(takebetween(range_10_to_90, outside_range, squares())))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Improved\n",
    "\n",
    "Note that now that `stop` is optional, with the standard default value of None, it becomes the last argument."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "def takebetween(start, iterable, stop=None):\n",
    "    if stop is None:\n",
    "        stop = lambda x: not start(x)\n",
    "    #\n",
    "    inbetween = False\n",
    "    for x in iterable:\n",
    "        if not inbetween:\n",
    "            if start(x):\n",
    "                yield x\n",
    "                inbetween = True\n",
    "        else:\n",
    "            if stop(x):\n",
    "                break\n",
    "            yield x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "print(list(takebetween(range_10_to_90, squares())))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## This works but...\n",
    "\n",
    "I thought of two use cases that might be common enough to make easier.\n",
    "\n",
    "# Special case handling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## No lower limit\n",
    "You can act as if there is no lower limit to the range by passing a `start` predicate that returns True:\n",
    "\n",
    "    ⬤ ⬤ ⬤ ••• ⬤ ⬤ ⬤ F ⭘ ⭘ ⭘ ⭘ ⭘ •••"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "def t(x): return True\n",
    "\n",
    "print(list(takebetween(start=t, iterable=squares(), stop=ge_90)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## No upper limit\n",
    "You can act as if there is no upper limit to the range by passing a `stop` predicate that returns False:\n",
    "\n",
    "    ⭘ ⭘ ⭘ S ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ •••"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324]\n"
     ]
    }
   ],
   "source": [
    "def f(x): return False\n",
    "\n",
    "print(list(islice(takebetween(start=ge_10, iterable=squares(), stop=f), 15)))\n",
    "# islice to limit the non-terminating output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## takebetween with special case handling.\n",
    "\n",
    "1. If `start is True` then act as if there is no lower bound.\n",
    "2. If `stop is False` then act as if there is no upper bound.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "def takebetween(start, iterable, stop=None):\n",
    "    if stop is None:\n",
    "        stop = lambda x: False if start is True else not start(x)\n",
    "    #\n",
    "    inbetween = start is True\n",
    "    for x in iterable:\n",
    "        if not inbetween:\n",
    "            if start(x):\n",
    "                yield x\n",
    "                inbetween = True\n",
    "        else:\n",
    "            if stop is not False and stop(x):\n",
    "                break\n",
    "            yield x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "print(list(takebetween(start=True, iterable=squares(), stop=ge_90)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324]\n"
     ]
    }
   ],
   "source": [
    "print(list(islice(takebetween(start=ge_10, iterable=squares(), stop=False), 15)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# END."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [default]",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  },
  "nav_menu": {},
  "toc": {
   "navigate_menu": true,
   "number_sections": true,
   "sideBar": true,
   "threshold": 6,
   "toc_cell": true,
   "toc_section_display": "block",
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}