{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e4402c74",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Hiding Attributes and Container Classes\n",
    "#### CS 66: Introduction to Computer Science II"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "773a6458",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## References for this lecture\n",
    "\n",
    "Problem Solving with Algorithms and Data Structures using Python\n",
    "\n",
    "Section 1.13: [https://runestone.academy/ns/books/published/pythonds/Introduction/ObjectOrientedProgramminginPythonDefiningClasses.html](https://runestone.academy/ns/books/published/pythonds/Introduction/ObjectOrientedProgramminginPythonDefiningClasses.html)\n",
    "\n",
    "Section 2.1: [https://runestone.academy/ns/books/published/pythonds/ProperClasses/a_proper_python_class.html](https://runestone.academy/ns/books/published/pythonds/ProperClasses/a_proper_python_class.html)\n",
    "\n",
    "Section 3.6 (Big O of list operations): [https://runestone.academy/ns/books/published/pythonds/AlgorithmAnalysis/Lists.html](https://runestone.academy/ns/books/published/pythonds/AlgorithmAnalysis/Lists.html)\n",
    "\n",
    "Section 4.5, 4.6 (Stack implementation), 4.11, 4.12 (Queue Implementation): [https://runestone.academy/ns/books/published/pythonds/BasicDS/toctree.html](https://runestone.academy/ns/books/published/pythonds/BasicDS/toctree.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c2bfbe5",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Picking up the example from last time\n",
    "\n",
    "Last time, we got our `PlayingCard` class to this point"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "39a669e5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here's what the card looks like: J♢\n",
      "Player 2 wins the hand\n"
     ]
    }
   ],
   "source": [
    "class PlayingCard:\n",
    "    \n",
    "    def __init__(self,v,s):\n",
    "        self.value = v\n",
    "        self.suit = s\n",
    "        \n",
    "    def face(self):\n",
    "        if self.value == 11:\n",
    "            return \"J\"\n",
    "        elif self.value == 12:\n",
    "            return \"Q\"\n",
    "        elif self.value == 13:\n",
    "            return \"K\"\n",
    "        elif self.value == 14:\n",
    "            return \"A\"\n",
    "        else:\n",
    "            return str(self.value)\n",
    "        \n",
    "    def __repr__(self):\n",
    "        return self.face()+str(self.suit)\n",
    "        \n",
    "        \n",
    "    def __lt__(self,other):\n",
    "        #return (self.value < other.value)\n",
    "        if self.value < other.value:\n",
    "            return True\n",
    "        else:\n",
    "            return False\n",
    "        \n",
    "two_of_clubs = PlayingCard(2,\"♣\")\n",
    "two_of_hearts = PlayingCard(2,\"♡\")\n",
    "ten_of_hearts = PlayingCard(10,\"♡\")\n",
    "seven_of_spades = PlayingCard(7,\"♠\")\n",
    "four_of_diamonds = PlayingCard(4,\"♢\")\n",
    "jack_of_diamonds = PlayingCard(11,\"♢\")\n",
    "\n",
    "print(\"Here's what the card looks like:\",jack_of_diamonds)\n",
    "if two_of_clubs < ten_of_hearts:\n",
    "    print(\"Player 2 wins the hand\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "515d1471",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Controlling Access\n",
    "\n",
    "When designing a class, you should plan for how to protect against accidental misuse. \n",
    "\n",
    "For example, what happens if someone tries to do this?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad719035",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "outputs": [],
   "source": [
    "pikachu = PlayingCard(\"Pikachu\",40)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2471b63",
   "metadata": {},
   "source": [
    "This particular class is only meant to represent standard French playing cards, so this should cause some kind of error.\n",
    "\n",
    "One way to handle it might be like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1f70f35a",
   "metadata": {},
   "outputs": [
    {
     "ename": "Exception",
     "evalue": "A PlayingCard's value must be an integer in the range 2-14.",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mException\u001b[0m                                 Traceback (most recent call last)",
      "Input \u001b[0;32mIn [6]\u001b[0m, in \u001b[0;36m<cell line: 10>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      7\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvalue \u001b[38;5;241m=\u001b[39m v\n\u001b[1;32m      8\u001b[0m         \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msuit \u001b[38;5;241m=\u001b[39m s\n\u001b[0;32m---> 10\u001b[0m twentyseven_of_clubs \u001b[38;5;241m=\u001b[39m \u001b[43mPlayingCard\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m27\u001b[39;49m\u001b[43m,\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m♣\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m     11\u001b[0m pikachu \u001b[38;5;241m=\u001b[39m PlayingCard(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPikachu\u001b[39m\u001b[38;5;124m\"\u001b[39m,\u001b[38;5;241m40\u001b[39m)\n",
      "Input \u001b[0;32mIn [6]\u001b[0m, in \u001b[0;36mPlayingCard.__init__\u001b[0;34m(self, v, s)\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__init__\u001b[39m(\u001b[38;5;28mself\u001b[39m,v,s):\n\u001b[1;32m      5\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m (\u001b[38;5;28mtype\u001b[39m(v) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;28mint\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m v \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m14\u001b[39m \u001b[38;5;129;01mor\u001b[39;00m v \u001b[38;5;241m<\u001b[39m \u001b[38;5;241m2\u001b[39m:\n\u001b[0;32m----> 6\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mA PlayingCard\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124ms value must be an integer in the range 2-14.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m      7\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvalue \u001b[38;5;241m=\u001b[39m v\n\u001b[1;32m      8\u001b[0m     \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msuit \u001b[38;5;241m=\u001b[39m s\n",
      "\u001b[0;31mException\u001b[0m: A PlayingCard's value must be an integer in the range 2-14."
     ]
    }
   ],
   "source": [
    "class PlayingCard:\n",
    "    \n",
    "    def __init__(self,v,s):\n",
    "        \n",
    "        if (type(v) != int) or v > 14 or v < 2:\n",
    "            raise Exception(\"A PlayingCard's value must be an integer in the range 2-14.\")\n",
    "        self.value = v\n",
    "        self.suit = s\n",
    "        \n",
    "twentyseven_of_clubs = PlayingCard(27,\"♣\")\n",
    "pikachu = PlayingCard(\"Pikachu\",40)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d582468",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Hiding Attributes\n",
    "\n",
    "By convention, any attribute or method whose name starts with an underscore should be treated as __private__, meaning you shouldn't change the variable outside the class.\n",
    "\n",
    "`self._value`\n",
    "\n",
    "\n",
    "If it doesn't start with an underscore, it is __public__ and changing it outside the class is fair game.\n",
    "\n",
    "If you start it with _two_ underscores, then Python performs name mangling, which doesn't let you change the name outside the class (unless you do something extra to get around the mangling).\n",
    "\n",
    "`self.__value`\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "02f5c4ce",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2♣\n"
     ]
    }
   ],
   "source": [
    "class PlayingCard:\n",
    "    \n",
    "    def __init__(self,v,s):\n",
    "        \n",
    "        if type(v) != type(1) or v > 14 or v < 2:\n",
    "            raise Exception(\"A PlayingCard's value must be an integer in the range 2-14.\")\n",
    "        self.__value = v\n",
    "        self.__suit = s\n",
    "        \n",
    "    def __repr__(self):\n",
    "        return str(self.__value)+str(self.__suit)\n",
    "        \n",
    "pikachu = PlayingCard(2,\"♣\")\n",
    "pikachu.__value = \"Pikachu\"  #this doesn't actually change self.__value\n",
    "pikachu.__suit = 40\n",
    "print(pikachu)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0e8b966",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "## Group Activity Problem 1\n",
    "\n",
    "Update your class so that `self.value` and `self.suit` are hidden (private/mangled)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5c3a526",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Container Classes\n",
    "\n",
    "__Container__ types are types meant to hold collections of other data - like lists, dictionaries, sets, and tuples.\n",
    "\n",
    "We will create our own container classes in this course - it's one of the main thing this course is about.\n",
    "\n",
    "Many custom container classes are built on top of existing containers, but by using data hiding and limited methods, they can control how the container can be used."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5a47db8",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "Let's create a `Deck` class\n",
    "* purpose: keep track of a collection of playing cards\n",
    "* build on top of a list\n",
    "* only allow cards to be added and removed from the \"top\" of the deck"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "1a2a50cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Demo: let's define our Deck class here and create a method called put_on_top\n",
    "# for placing new cards on the deck, and another called remove_from_top\n",
    "# to remove a card from the top of the deck and return it"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "203c73ca",
   "metadata": {
    "slideshow": {
     "slide_type": "skip"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4♢\n",
      "7♠\n",
      "10♡\n"
     ]
    }
   ],
   "source": [
    "#Here's a working version for your notes.\n",
    "class Deck:\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.__card_list = []  #the deck will be initially empty\n",
    "        \n",
    "    def put_on_top(self,card):\n",
    "        self.__card_list.append(card)\n",
    "        \n",
    "    def remove_from_top(self):\n",
    "        if len(self.__card_list) == 0:\n",
    "            raise Exception(\"This deck has no cards left.\")\n",
    "        else:\n",
    "            return self.__card_list.pop()\n",
    "    \n",
    "\n",
    "two_of_clubs = PlayingCard(2,\"♣\")\n",
    "ten_of_hearts = PlayingCard(10,\"♡\")\n",
    "seven_of_spades = PlayingCard(7,\"♠\")\n",
    "four_of_diamonds = PlayingCard(4,\"♢\")\n",
    "\n",
    "my_deck = Deck()\n",
    "my_deck.put_on_top(two_of_clubs)\n",
    "my_deck.put_on_top(ten_of_hearts)\n",
    "my_deck.put_on_top(seven_of_spades)\n",
    "my_deck.put_on_top(four_of_diamonds)\n",
    "\n",
    "print( my_deck.remove_from_top() )\n",
    "print( my_deck.remove_from_top() )\n",
    "print( my_deck.remove_from_top() )\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17099ffa",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "#### Why is this better than just using a list?\n",
    "\n",
    "* We know that we won't accidentally insert a card on the bottom or middle of the deck - it's safer for the rest of our code.\n",
    "* We can add custom methods to do things that lists don't do"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29bcf43f",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Group Activity Problem 2\n",
    "\n",
    "Put your code for the `PlayingCard` and `Deck` into a file called `carddeck.py`, and implement the following methods for the Deck class:\n",
    "\n",
    "* `__repr__()`\n",
    "* `shuffle()` - allows the deck to be mixed around in random order (if you're stuck, Google how to shuffle a list)\n",
    "* `is_empty()` - returns True or False depending on if the deck is empty\n",
    "\n",
    "If you did it right, your `PlayingCard` and `Deck` classes should work with the following code, which is a simple game of high-card. On each turn, both players draw a card from the deck, and the one with the higher card value gets a point. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2333d9fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "high_card_deck = Deck()\n",
    "\n",
    "#create each of the 52 playing cards and put them in the deck\n",
    "suits = [\"♠\",\"♣\",\"♡\",\"♢\"]\n",
    "for s in suits:\n",
    "    for v in range(2,15):\n",
    "        curr_card = PlayingCard(v,s)\n",
    "        high_card_deck.put_on_top(curr_card)\n",
    "        \n",
    "#look at the deck both before and after shuffling        \n",
    "print(\"Here's the pre-shuffled deck:\",high_card_deck)\n",
    "high_card_deck.shuffle()\n",
    "print(\"Here's the deck after the shuffle:\",high_card_deck)\n",
    "\n",
    "#initialize both player's scores to 0\n",
    "p1score = 0\n",
    "p2score = 0\n",
    "\n",
    "#keep going until all cards are dealt out\n",
    "while not high_card_deck.is_empty():\n",
    "    \n",
    "    #draw a card for each player\n",
    "    p1card = high_card_deck.remove_from_top()\n",
    "    p2card = high_card_deck.remove_from_top()\n",
    "    \n",
    "    print(\"Player 1:\",p1card,\", Player 2:\",p2card)\n",
    "    \n",
    "    #check which player wins this hand\n",
    "    if p1card > p2card:\n",
    "        p1score += 1\n",
    "        print(\"Player 1 wins this hand.\")\n",
    "    elif p1card < p2card:\n",
    "        p2score += 1\n",
    "        print(\"Player 2 wins this hand.\")\n",
    "    else:\n",
    "        print(\"This hand is a draw.\")\n",
    "        \n",
    "        \n",
    "#Figure out who wins and display the game-end message\n",
    "print(\"Player 1 score:\",p1score,\", Player 2 score:\",p2score) \n",
    "if p1score > p2score:\n",
    "    print(\"Player 1 wins the game!!!\")\n",
    "elif p2score > p1score:\n",
    "    print(\"Player 2 wins the game!!!\")\n",
    "else:\n",
    "    print(\"The game is a tie :(\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65e929c5",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Review: The Queue Abstract Data Type\n",
    "\n",
    "These are the following operations that all queues should have:\n",
    "\n",
    "* `Queue()` creates a new queue that is empty. It needs no parameters and returns an empty queue.\n",
    "* `enqueue(item)` adds a new item to the _back_ of the queue. It needs the item and returns nothing.\n",
    "* `dequeue()` removes the item at the _front_ of the queue. It needs no parameters and returns the item. The queue is modified.\n",
    "* `isEmpty()` tests to see whether the queue is empty. It needs no parameters and returns a boolean value.\n",
    "* `size()` returns the number of items in the queue. It needs no parameters and returns an integer.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc0c5ca8",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "# Implementing the Queue\n",
    "\n",
    "Remember: An __abstract data type__, sometimes abbreviated ADT, is a logical description of how we view the data and the operations that are allowed _without regard to how they will be implemented_. \n",
    "\n",
    "Once we create a class that _implements_ an ADT, we've created a __Data Structure__. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "036d5c26",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Queue:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def isEmpty(self):\n",
    "        return self.items == []\n",
    "\n",
    "    def enqueue(self, item):\n",
    "        self.items.insert(0,item)\n",
    "\n",
    "    def dequeue(self):\n",
    "        return self.items.pop()\n",
    "\n",
    "    def size(self):\n",
    "        return len(self.items)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "797d181e",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Group Activity Problem 3\n",
    "\n",
    "Test this code out with some code that uses a queue, and discuss whether it is working properly. Here's some code we used to test out queues before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "25157d3b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Size: 4\n",
      "11\n",
      "8\n",
      "5\n",
      "9\n"
     ]
    }
   ],
   "source": [
    "my_q = Queue()\n",
    "my_q.enqueue(4)\n",
    "my_q.enqueue(7)\n",
    "my_q.enqueue(11)\n",
    "my_q.dequeue()\n",
    "my_q.enqueue(8)\n",
    "my_q.dequeue()\n",
    "my_q.enqueue(5)\n",
    "my_q.enqueue(9)\n",
    "\n",
    "print(\"Size:\",my_q.size())\n",
    "\n",
    "while not my_q.isEmpty():\n",
    "    print(my_q.dequeue())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a61b7a2",
   "metadata": {},
   "source": [
    "## Group Activity Problem 4\n",
    "\n",
    "One of the things that was annoying about working with stacks and queues from the `pythonds` module was that they didn't have a way to print them and see what values were in them. Add a `__repr__` method to this Queue class which allows you to see what's in the queue."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6703ad3",
   "metadata": {},
   "source": [
    "## Group Activity Problem 5\n",
    "\n",
    "Based on what you know about the Big O of different list operations, what are is the Big O of each of these Queue methods?\n",
    "\n",
    "* `enqueue`\n",
    "* `dequeue`\n",
    "* `size`\n",
    "\n",
    "For reference, take a look at \n",
    "* the section of the book on the computational complexity of list operations: [https://runestone.academy/ns/books/published/pythonds/AlgorithmAnalysis/Lists.html](https://runestone.academy/ns/books/published/pythonds/AlgorithmAnalysis/Lists.html)\n",
    "* the official Python wiki: [https://wiki.python.org/moin/TimeComplexity](https://wiki.python.org/moin/TimeComplexity)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "529d22ba",
   "metadata": {},
   "source": [
    "## Group Activity Problem 6\n",
    "\n",
    "Review: We've seen the textbook's description of the Stack ADT. Here it is.\n",
    "\n",
    "These are the following operations that all stacks should have:\n",
    "\n",
    "* `Stack()` creates a new stack that is empty. It needs no parameters and returns an empty stack.\n",
    "* `push(item)` adds a new item to the top of the stack. It needs the item and returns nothing.\n",
    "* `pop()` removes the top item from the stack. It needs no parameters and returns the item. The stack is modified.\n",
    "* `peek()` returns the top item from the stack but _does not remove it_. It needs no parameters. The stack is not modified.\n",
    "* `isEmpty()` tests to see whether the stack is empty. It needs no parameters and returns a boolean value.\n",
    "* `size()` returns the number of items on the stack. It needs no parameters and returns an integer.\n",
    "\n",
    "Create a class that implements this. It should be very similar to the Queue class."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e33f69bd",
   "metadata": {},
   "source": [
    "## Group Activity Problem 7\n",
    "\n",
    "What is the Big O of each of the following for your definition of a Stack?\n",
    "\n",
    "* `push`\n",
    "* `pop`\n",
    "* `peek`\n",
    "* `size`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdab171e",
   "metadata": {},
   "source": [
    "## Group Activity Problem 8\n",
    "\n",
    "Discuss: What's the difference between the `Deck` class and the `Stack` class? Can a `Deck` also be considered a stack?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30263128",
   "metadata": {},
   "source": [
    "## Group Activity Problem 9\n",
    "\n",
    "Discuss the following question, and make sure to write the answer in your notes: What is the difference between and Abstract Data Type and a Data Structure?"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Slideshow",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.10.6"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
