CS30 - Spring 2015 - Class 12

Example code in this lecture


Lecture notes

  • the cfg.py code shows one way of implementing the CFG we saw in the slides
       - terminals are represented as strings   

       - each nonterminal is represented as a function, e.g. noun, verb, verb_phrase, etc.
          - they return one of two things, representing the right hand side of the rule:
             - a string
             - a list of functions (could also be a list of functions and strings, though we don't utilize that option)
          - if there are multiple options (i.e through |) then a random choice is made

       - noun, verb, article, adjective and adverb all generate only terminals
          - they randomly select between possible words (strings) using random.choice
          - they each return a string

       - pre_noun_phrase, noun_phrase, verb_phrase and sentence all generate more nonterminals (though they don't necessarily have to)
          - they each return a list of functions, representing a rule that generates a nonterminal on the right hand side

  • the process function in cfg.py code takes in a cfg rule (either a string or a list of functions) and randomly generates a string in that language
       - the function is recursive!
          - this is not too surprising since CFG have a recursive feeling to them
       - the base case is if we're dealing with a terminal (i.e a string)
       - the recursive case is if we have a list of nonterminals (i.e. functions)
          - for each item (function) in this list:
             - call the function "item()"
             - and then call process on whatever it give in return
                - this will either be a string (in the case of a rule that generates a terminal)
                - or another list of functions
             - the results are then appended together into one string

       - we can call the process function by getting it started with [sentence], i.e.
          >>> process([sentence])
          'the bagel eats intentionally'

          - for this to happen, the recursion would be something like:
             - top call: process([sentence])
             - this would then make two recursive calls to return: process(noun_phrase()) + " " + process(verb_phrase())
                - the call to noun_phrase returns [article, pre_noun_phrase]
                   - which then is passed to process
                   - article gives us 'the'
                   - and the pre_noun_phrase pics just [noun]
                      - the noun "bagel" is chosen
                - ...

  • printing out lots of random derivations from the grammar
       - if we put this call in a loop, we can get lots of random strings:

          >>> for i in range(10):
          ...    print process([sentence])

          a milk eats
          a bagel sleeps
          a idea sleeps furiously
          a green idea eats soothingly
          a green cow sleeps intentionally
          the colorless smelly cow sleeps
          the colorless cow swims soothingly
          the bagel swims intentionally
          a idea sprints
          the colorless colorless colorless green cow swims intentionally

  • optional parameters
       - recall that the range function that we use in for loops (like above), is really just generating a list that we then iterate over:
          >>> range(10)
          [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

       - besides this version, range also has two other variants (one of which we haven't seen)
          - we can optionally give it a starting point, instead of starting at 0
             >>> range(4, 10)
             [4, 5, 6, 7, 8, 9]

          - in addition, we can also give it a third parameter that is the increment, instead of just incrementing by 1
             >>> range(4, 10, 2)
             [4, 6, 8]

       - we'd like to write our own version of this function
          - the challenge is how to have one function support a differing number of arguments

       - Python has optional parameter
          - an optional parameter is included in the list of parameters, however, we give it a default value using '='
          - the function can be called with OR without that parameter
             - if it's called without it, the parameter gets the default value specified by '='
             - it it's called with it, the parameter gets whatever the user passes in

       - look at the my_range function in optional_parameters.py code
          - the function takes three parameters: start, end, step
          - we generate the list of values by starting with an empty list and append on the values
          - we set i to start out at the start
          - as long as (while) i is less than the end
             - append i to the list
             - increase i by the step size

       - my range has step as optional
          - if step isn't specified, it gets a value of 1
          - if it is, then we use the value specified

          >>> my_range(0, 10)
          [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
          >>> my_range(0, 10, 2)
          [0, 2, 4, 6, 8]

       - optional parameters must come AFTER all non-optional parameters. why?
          - say we defined a function as follows:

             def simple(a = 1, b = 2, c):

             what happens if I call it with 2 parameter?

             >>> simple(10, 15)

             - we don't know whether a or b should get 10

  • parameters by value
       - look at the optional function in optional_parameters.py code
          - what does this function do?
          - how can we call it?
             >>> optional(10)
             >>> optional(10, 11)
             >>> optional(10, 11, 2)
          - what if we wanted to specify multiplier, but not adder?
             >>> optional(10, multiplier=10)
          - Python allows you to specify ANY parameter by name
             - if you have optional parameters, you can specify them by name
             - you can also specify required parameters by name
                >>> optional(multiplier=2, val = 10)