added some computer science concepts in individual text files in the algorithms section of programming_book_examples and added some intro programs from the K & R C book
This commit is contained in:
parent
a4b08f3b6d
commit
9890119fe5
4 changed files with 371 additions and 0 deletions
133
algorithms/wikipedia_info/decorator
Normal file
133
algorithms/wikipedia_info/decorator
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
<WikipediaPage 'Decorator pattern'>
|
||||||
|
|
||||||
|
|
||||||
|
In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern. The decorator pattern is structurally nearly identical to the chain of responsibility pattern, the difference being that in a chain of responsibility, exactly one of the classes handles the request, while for the decorator, all classes handle the request.
|
||||||
|
|
||||||
|
|
||||||
|
== Overview ==
|
||||||
|
The Decorator design pattern is one of the twenty-three well-known GoF design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.
|
||||||
|
What problems can the Decorator design pattern solve?
|
||||||
|
Responsibilities should be added to (and removed from) an object dynamically at run-time.
|
||||||
|
A flexible alternative to subclassing for extending functionality should be provided.
|
||||||
|
When using subclassing, different subclasses extend a class in different ways. But an extension is bound to the class at compile-time and can't be changed at run-time.
|
||||||
|
What solution does the Decorator design pattern describe?
|
||||||
|
Define Decorator objects that
|
||||||
|
implement the interface of the extended (decorated) object (Component) transparently by forwarding all requests to it and
|
||||||
|
perform additional functionality before/after forwarding a request.
|
||||||
|
This enables to work through different Decorator objects to extend the functionality of an object dynamically at run-time.
|
||||||
|
See also the UML class and sequence diagram below.
|
||||||
|
|
||||||
|
|
||||||
|
== Intent ==
|
||||||
|
|
||||||
|
The decorator pattern can be used to extend (decorate) the functionality of a certain object statically, or in some cases at run-time, independently of other instances of the same class, provided some groundwork is done at design time. This is achieved by designing a new Decorator class that wraps the original class. This wrapping could be achieved by the following sequence of steps:
|
||||||
|
Subclass the original Component class into a Decorator class (see UML diagram);
|
||||||
|
In the Decorator class, add a Component pointer as a field;
|
||||||
|
In the Decorator class, pass a Component to the Decorator constructor to initialize the Component pointer;
|
||||||
|
In the Decorator class, forward all Component methods to the Component pointer; and
|
||||||
|
In the ConcreteDecorator class, override any Component method(s) whose behavior needs to be modified.
|
||||||
|
This pattern is designed so that multiple decorators can be stacked on top of each other, each time adding a new functionality to the overridden method(s).
|
||||||
|
Note that decorators and the original class object share a common set of features. In the previous diagram, the operation() method was available in both the decorated and undecorated versions.
|
||||||
|
The decoration features (e.g., methods, properties, or other members) are usually defined by an interface, mixin (a.k.a. trait) or class inheritance which is shared by the decorators and the decorated object. In the previous example the class Component is inherited by both the ConcreteComponent and the subclasses that descend from Decorator.
|
||||||
|
The decorator pattern is an alternative to subclassing. Subclassing adds behavior at compile time, and the change affects all instances of the original class; decorating can provide new behavior at run-time for selective objects.
|
||||||
|
This difference becomes most important when there are several independent ways of extending functionality. In some object-oriented programming languages, classes cannot be created at runtime, and it is typically not possible to predict, at design time, what combinations of extensions will be needed. This would mean that a new class would have to be made for every possible combination. By contrast, decorators are objects, created at runtime, and can be combined on a per-use basis. The I/O Streams implementations of both Java and the .NET Framework incorporate the decorator pattern.
|
||||||
|
|
||||||
|
|
||||||
|
== Motivation ==
|
||||||
|
|
||||||
|
As an example, consider a window in a windowing system. To allow scrolling of the window's contents, one may wish to add horizontal or vertical scrollbars to it, as appropriate. Assume windows are represented by instances of the Window class, and assume this class has no functionality for adding scrollbars. One could create a subclass ScrollingWindow that provides them, or create a ScrollingWindowDecorator that adds this functionality to existing Window objects. At this point, either solution would be fine.
|
||||||
|
Now, assume one also desires the ability to add borders to windows. Again, the original Window class has no support. The ScrollingWindow subclass now poses a problem, because it has effectively created a new kind of window. If one wishes to add border support to many but not all windows, one must create subclasses WindowWithBorder and ScrollingWindowWithBorder etc. This problem gets worse with every new feature or window subtype to be added. For the decorator solution, we simply create a new BorderedWindowDecorator—at runtime, we can decorate existing windows with the ScrollingWindowDecorator or the BorderedWindowDecorator or both, as we see fit. Notice that if the functionality needs to be added to all Windows, you could modify the base class and that will do. On the other hand, sometimes (e.g., using external frameworks) it is not possible, legal, or convenient to modify the base class.
|
||||||
|
Note, in the previous example, that the "SimpleWindow" and "WindowDecorator" classes implement the "Window" interface, which defines the "draw()" method and the "getDescription()" method, that are required in this scenario, in order to decorate a window control.
|
||||||
|
|
||||||
|
|
||||||
|
== Usage ==
|
||||||
|
A decorator makes it possible to add or alter behavior of an interface at run-time. Alternatively, the adapter can be used when the wrapper must respect a particular interface and must support polymorphic behavior, and the Facade when an easier or simpler interface to an underlying object is desired.
|
||||||
|
|
||||||
|
|
||||||
|
== Structure ==
|
||||||
|
|
||||||
|
|
||||||
|
=== UML class and sequence diagram ===
|
||||||
|
|
||||||
|
In the above UML class diagram, the abstract Decorator class maintains a reference (component) to the decorated object (Component) and forwards all requests to it (component.operation()). This makes Decorator transparent (invisible) to clients of Component.
|
||||||
|
Subclasses (Decorator1,Decorator2) implement additional behavior (addBehavior()) that should be added to the Component (before/after forwarding a request to it).
|
||||||
|
The sequence diagram shows the run-time interactions: The Client object works through Decorator1 and Decorator2 objects to extend the functionality of a Component1 object.
|
||||||
|
The Client calls operation() on Decorator1, which forwards the request to Decorator2. Decorator2 performs addBehavior() after forwarding the request to Component1 and returns to Decorator1, which performs addBehavior() and returns to the Client.
|
||||||
|
|
||||||
|
|
||||||
|
== Examples ==
|
||||||
|
|
||||||
|
|
||||||
|
=== C++ ===
|
||||||
|
Two options are presented here, first a dynamic, runtime-composable decorator (has issues with calling decorated functions unless proxied explicitly) and a decorator that uses mixin inheritance.
|
||||||
|
|
||||||
|
|
||||||
|
==== Dynamic Decorator ====
|
||||||
|
|
||||||
|
|
||||||
|
==== Static Decorator (Mixin Inheritance) ====
|
||||||
|
This example demonstrates a static Decorator implementation, which is possible due to C++ ability to inherit from the template argument.
|
||||||
|
|
||||||
|
|
||||||
|
=== Java ===
|
||||||
|
|
||||||
|
|
||||||
|
==== First example (window/scrolling scenario) ====
|
||||||
|
The following Java example illustrates the use of decorators using the window/scrolling scenario.
|
||||||
|
|
||||||
|
The following classes contain the decorators for all Window classes, including the decorator classes themselves.
|
||||||
|
|
||||||
|
Here's a test program that creates a Window instance which is fully decorated (i.e., with vertical and horizontal scrollbars), and prints its description:
|
||||||
|
|
||||||
|
Below is the JUnit test class for the Test Driven Development
|
||||||
|
|
||||||
|
The output of this program is "simple window, including vertical scrollbars, including horizontal scrollbars". Notice how the getDescription method of the two decorators first retrieve the decorated Window's description and decorates it with a suffix.
|
||||||
|
|
||||||
|
|
||||||
|
==== Second example (coffee making scenario) ====
|
||||||
|
The next Java example illustrates the use of decorators using coffee making scenario. In this example, the scenario only includes cost and ingredients.
|
||||||
|
|
||||||
|
The following classes contain the decorators for all Coffee classes, including the decorator classes themselves..
|
||||||
|
|
||||||
|
Here's a test program that creates a Coffee instance which is fully decorated (with milk and sprinkles), and calculate cost of coffee and prints its ingredients:
|
||||||
|
|
||||||
|
The output of this program is given below:
|
||||||
|
|
||||||
|
Cost: 1.0; Ingredients: Coffee
|
||||||
|
Cost: 1.5; Ingredients: Coffee, Milk
|
||||||
|
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles
|
||||||
|
|
||||||
|
|
||||||
|
=== PHP ===
|
||||||
|
|
||||||
|
|
||||||
|
=== Python ===
|
||||||
|
Python includes a more natural way of decorating a function by using an annotation on the function that is decorated.
|
||||||
|
This example is incorrect: "The DecoratorPattern is a pattern described in the DesignPatternsBook. It is a way of apparently modifying an object's behavior, by enclosing it inside a decorating object with a similar interface. This is not to be confused with PythonDecorators, which is a language feature for dynamically modifying a function or class."
|
||||||
|
|
||||||
|
A correct example taken from: https://wiki.python.org/moin/DecoratorPattern
|
||||||
|
|
||||||
|
|
||||||
|
=== Crystal ===
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
Cost: 1.0; Ingredients: Coffee
|
||||||
|
Cost: 1.5; Ingredients: Coffee, Milk
|
||||||
|
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles
|
||||||
|
|
||||||
|
|
||||||
|
== See also ==
|
||||||
|
Composite pattern
|
||||||
|
Adapter pattern
|
||||||
|
Abstract class
|
||||||
|
Abstract factory
|
||||||
|
Aspect-oriented programming
|
||||||
|
Immutable object
|
||||||
|
|
||||||
|
|
||||||
|
== References ==
|
||||||
|
|
||||||
|
|
||||||
|
== External links ==
|
||||||
|
Decorator pattern description from the Portland Pattern Repository
|
210
algorithms/wikipedia_info/memoization
Normal file
210
algorithms/wikipedia_info/memoization
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
<WikipediaPage 'Memoization'>
|
||||||
|
|
||||||
|
|
||||||
|
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. Memoization has also been used in other contexts (and for purposes other than speed gains), such as in simple mutually recursive descent parsing. Although related to caching, memoization refers to a specific case of this optimization, distinguishing it from forms of caching such as buffering or page replacement. In the context of some logic programming languages, memoization is also known as tabling; see also lookup table.
|
||||||
|
|
||||||
|
|
||||||
|
== Etymology ==
|
||||||
|
The term "memoization" was coined by Donald Michie in 1968 and is derived from the Latin word "memorandum" ("to be remembered"), usually truncated as "memo" in American English, and thus carries the meaning of "turning [the results of] a function into something to be remembered." While "memoization" might be confused with "memorization" (because they are etymological cognates), "memoization" has a specialized meaning in computing.
|
||||||
|
|
||||||
|
|
||||||
|
== Overview ==
|
||||||
|
A memoized function "remembers" the results corresponding to some set of specific inputs. Subsequent calls with remembered inputs return the remembered result rather than recalculating it, thus eliminating the primary cost of a call with given parameters from all but the first call made to the function with those parameters. The set of remembered associations may be a fixed-size set controlled by a replacement algorithm or a fixed set, depending on the nature of the function and its use. A function can only be memoized if it is referentially transparent; that is, only if calling the function has exactly the same effect as replacing that function call with its return value. (Special case exceptions to this restriction exist, however.) While related to lookup tables, since memoization often uses such tables in its implementation, memoization populates its cache of results transparently on the fly, as needed, rather than in advance.
|
||||||
|
Memoization is a way to lower a function's time cost in exchange for space cost; that is, memoized functions become optimized for speed in exchange for a higher use of computer memory space. The time/space "cost" of algorithms has a specific name in computing: computational complexity. All functions have a computational complexity in time (i.e. they take time to execute) and in space.
|
||||||
|
Although a time-space trade-off occurs (i.e., space used is speed gained), this differs from some other optimizations that involve time-space trade-off, such as strength reduction, in that memoization is a run-time rather than compile-time optimization. Moreover, strength reduction potentially replaces a costly operation such as multiplication with a less costly operation such as addition, and the results in savings can be highly machine-dependent, non-portable across machines, whereas memoization is a more machine-independent, cross-platform strategy.
|
||||||
|
Consider the following pseudocode function to calculate the factorial of n:
|
||||||
|
|
||||||
|
function factorial (n is a non-negative integer)
|
||||||
|
if n is 0 then
|
||||||
|
return 1 [by the convention that 0! = 1]
|
||||||
|
else
|
||||||
|
return factorial(n – 1) times n [recursively invoke factorial
|
||||||
|
with the parameter 1 less than n]
|
||||||
|
end if
|
||||||
|
end function
|
||||||
|
|
||||||
|
For every integer n such that n≥0, the final result of the function factorial is invariant; if invoked as x = factorial(3), the result is such that x will always be assigned the value 6. A non-memoized version of the above, given the nature of the recursive algorithm involved, would require n + 1 invocations of factorial to arrive at a result, and each of these invocations, in turn, has an associated cost in the time it takes the function to return the value computed. Depending on the machine, this cost might be the sum of:
|
||||||
|
The cost to set up the functional call stack frame.
|
||||||
|
The cost to compare n to 0.
|
||||||
|
The cost to subtract 1 from n.
|
||||||
|
The cost to set up the recursive call stack frame. (As above.)
|
||||||
|
The cost to multiply the result of the recursive call to factorial by n.
|
||||||
|
The cost to store the return result so that it may be used by the calling context.
|
||||||
|
In a non-memoized implementation, every top-level call to factorial includes the cumulative cost of steps 2 through 6 proportional to the initial value of n.
|
||||||
|
A memoized version of the factorial function follows:
|
||||||
|
|
||||||
|
function factorial (n is a non-negative integer)
|
||||||
|
if n is 0 then
|
||||||
|
return 1 [by the convention that 0! = 1]
|
||||||
|
else if n is in lookup-table then
|
||||||
|
return lookup-table-value-for-n
|
||||||
|
else
|
||||||
|
let x = factorial(n – 1) times n [recursively invoke factorial
|
||||||
|
with the parameter 1 less than n]
|
||||||
|
store x in lookup-table in the nth slot [remember the result of n! for later]
|
||||||
|
return x
|
||||||
|
end if
|
||||||
|
end function
|
||||||
|
|
||||||
|
In this particular example, if factorial is first invoked with 5, and then invoked later with any value less than or equal to five, those return values will also have been memoized, since factorial will have been called recursively with the values 5, 4, 3, 2, 1, and 0, and the return values for each of those will have been stored. If it is then called with a number greater than 5, such as 7, only 2 recursive calls will be made (7 and 6), and the value for 5! will have been stored from the previous call. In this way, memoization allows a function to become more time-efficient the more often it is called, thus resulting in eventual overall speed up.
|
||||||
|
|
||||||
|
|
||||||
|
== Some other considerations ==
|
||||||
|
|
||||||
|
|
||||||
|
=== Functional programming ===
|
||||||
|
|
||||||
|
Memoization is heavily used in compilers for functional programming languages, which often use call by name evaluation strategy. To avoid overhead with calculating argument values, compilers for these languages heavily use auxiliary functions called thunks to compute the argument values, and memoize these functions to avoid repeated calculations.
|
||||||
|
|
||||||
|
|
||||||
|
=== Automatic memoization ===
|
||||||
|
While memoization may be added to functions internally and explicitly by a computer programmer in much the same way the above memoized version of factorial is implemented, referentially transparent functions may also be automatically memoized externally. The techniques employed by Peter Norvig have application not only in Common Lisp (the language in which his paper demonstrated automatic memoization), but also in various other programming languages. Applications of automatic memoization have also been formally explored in the study of term rewriting and artificial intelligence.
|
||||||
|
In programming languages where functions are first-class objects (such as Lua, Python, or Perl [1]), automatic memoization can be implemented by replacing (at run-time) a function with its calculated value once a value has been calculated for a given set of parameters. The function that does this value-for-function-object replacement can generically wrap any referentially transparent function. Consider the following pseudocode (where it is assumed that functions are first-class values):
|
||||||
|
|
||||||
|
function memoized-call (F is a function object parameter)
|
||||||
|
if F has no attached array values then
|
||||||
|
allocate an associative array called values;
|
||||||
|
attach values to F;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if F.values[arguments] is empty then
|
||||||
|
F.values[arguments] = F(arguments);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return F.values[arguments];
|
||||||
|
end function
|
||||||
|
|
||||||
|
In order to call an automatically memoized version of factorial using the above strategy, rather than calling factorial directly, code invokes memoized-call(factorial(n)). Each such call first checks to see if a holder array has been allocated to store results, and if not, attaches that array. If no entry exists at the position values[arguments] (where arguments are used as the key of the associative array), a real call is made to factorial with the supplied arguments. Finally, the entry in the array at the key position is returned to the caller.
|
||||||
|
The above strategy requires explicit wrapping at each call to a function that is to be memoized. In those languages that allow closures, memoization can be effected implicitly by a functor factory that returns a wrapped memoized function object. In pseudocode, this can be expressed as follows:
|
||||||
|
|
||||||
|
function construct-memoized-functor (F is a function object parameter)
|
||||||
|
allocate a function object called memoized-version;
|
||||||
|
|
||||||
|
let memoized-version(arguments) be
|
||||||
|
if self has no attached array values then [self is a reference to this object]
|
||||||
|
allocate an associative array called values;
|
||||||
|
attach values to self;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if self.values[arguments] is empty then
|
||||||
|
self.values[arguments] = F(arguments);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return self.values[arguments];
|
||||||
|
end let;
|
||||||
|
|
||||||
|
return memoized-version;
|
||||||
|
end function
|
||||||
|
|
||||||
|
Rather than call factorial, a new function object memfact is created as follows:
|
||||||
|
|
||||||
|
memfact = construct-memoized-functor(factorial)
|
||||||
|
|
||||||
|
The above example assumes that the function factorial has already been defined before the call to construct-memoized-functor is made. From this point forward, memfact(n) is called whenever the factorial of n is desired. In languages such as Lua, more sophisticated techniques exist which allow a function to be replaced by a new function with the same name, which would permit:
|
||||||
|
|
||||||
|
factorial = construct-memoized-functor(factorial)
|
||||||
|
|
||||||
|
Essentially, such techniques involve attaching the original function object to the created functor and forwarding calls to the original function being memoized via an alias when a call to the actual function is required (to avoid endless recursion), as illustrated below:
|
||||||
|
|
||||||
|
function construct-memoized-functor (F is a function object parameter)
|
||||||
|
allocate a function object called memoized-version;
|
||||||
|
|
||||||
|
let memoized-version(arguments) be
|
||||||
|
if self has no attached array values then [self is a reference to this object]
|
||||||
|
allocate an associative array called values;
|
||||||
|
attach values to self;
|
||||||
|
allocate a new function object called alias;
|
||||||
|
attach alias to self; [for later ability to invoke F indirectly]
|
||||||
|
self.alias = F;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if self.values[arguments] is empty then
|
||||||
|
self.values[arguments] = self.alias(arguments); [not a direct call to F]
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return self.values[arguments];
|
||||||
|
end let;
|
||||||
|
|
||||||
|
return memoized-version;
|
||||||
|
end function
|
||||||
|
|
||||||
|
(Note: Some of the steps shown above may be implicitly managed by the implementation language and are provided for illustration.)
|
||||||
|
|
||||||
|
|
||||||
|
=== Parsers ===
|
||||||
|
When a top-down parser tries to parse an ambiguous input with respect to an ambiguous context-free grammar (CFG), it may need an exponential number of steps (with respect to the length of the input) to try all alternatives of the CFG in order to produce all possible parse trees. This eventually would require exponential memory space. Memoization was explored as a parsing strategy in 1991 by Norvig, who demonstrated that an algorithm similar to the use of dynamic programming and state-sets in Earley's algorithm (1970), and tables in the CYK algorithm of Cocke, Younger and Kasami, could be generated by introducing automatic memoization to a simple backtracking recursive descent parser to solve the problem of exponential time complexity. The basic idea in Norvig’s approach is that when a parser is applied to the input, the result is stored in a memotable for subsequent reuse if the same parser is ever reapplied to the same input. Richard Frost also used memoization to reduce the exponential time complexity of parser combinators, which can be viewed as “Purely Functional Top-Down Backtracking” parsing technique. He showed that basic memoized parser combinators can be used as building blocks to construct complex parsers as executable specifications of CFGs. It was again explored in the context of parsing in 1995 by Johnson and Dörre. In 2002, it was examined in considerable depth by Ford in the form called packrat parsing.
|
||||||
|
In 2007, Frost, Hafiz and Callaghan described a top-down parsing algorithm that uses memoization for refraining redundant computations to accommodate any form of ambiguous CFG in polynomial time (Θ(n4) for left-recursive grammars and Θ(n3) for non left-recursive grammars). Their top-down parsing algorithm also requires polynomial space for potentially exponential ambiguous parse trees by 'compact representation' and 'local ambiguities grouping'. Their compact representation is comparable with Tomita’s compact representation of bottom-up parsing. Their use of memoization is not only limited to retrieving the previously computed results when a parser is applied to a same input position repeatedly (which is essential for polynomial time requirement); it is specialized to perform the following additional tasks:
|
||||||
|
The memoization process (which could be viewed as a ‘wrapper’ around any parser execution) accommodates an ever-growing direct left-recursive parse by imposing depth restrictions with respect to input length and current input position.
|
||||||
|
The algorithm’s memo-table ‘lookup’ procedure also determines the reusability of a saved result by comparing the saved result’s computational context with the parser’s current context. This contextual comparison is the key to accommodate indirect (or hidden) left-recursion.
|
||||||
|
When performing a successful lookup in a memotable, instead of returning the complete result-set, the process only returns the references of the actual result and eventually speeds up the overall computation.
|
||||||
|
During updating the memotable, the memoization process groups the (potentially exponential) ambiguous results and ensures the polynomial space requirement.
|
||||||
|
Frost, Hafiz and Callaghan also described the implementation of the algorithm in PADL’08 as a set of higher-order functions (called parser combinators) in Haskell, which enables the construction of directly executable specifications of CFGs as language processors. The importance of their polynomial algorithm’s power to accommodate ‘any form of ambiguous CFG’ with top-down parsing is vital with respect to the syntax and semantics analysis during natural language processing. The X-SAIGA site has more about the algorithm and implementation details.
|
||||||
|
While Norvig increased the power of the parser through memoization, the augmented parser was still as time complex as Earley's algorithm, which demonstrates a case of the use of memoization for something other than speed optimization. Johnson and Dörre demonstrate another such non-speed related application of memoization: the use of memoization to delay linguistic constraint resolution to a point in a parse where sufficient information has been accumulated to resolve those constraints. By contrast, in the speed optimization application of memoization, Ford demonstrated that memoization could guarantee that parsing expression grammars could parse in linear time even those languages that resulted in worst-case backtracking behavior.
|
||||||
|
Consider the following grammar:
|
||||||
|
|
||||||
|
S → (A c) | (B d)
|
||||||
|
A → X (a|b)
|
||||||
|
B → X b
|
||||||
|
X → x [X]
|
||||||
|
|
||||||
|
(Notation note: In the above example, the production S → (A c) | (B d) reads: "An S is either an A followed by a c or a B followed by a d." The production X → x [X] reads "An X is an x followed by an optional X.")
|
||||||
|
This grammar generates one of the following three variations of string: xac, xbc, or xbd (where x here is understood to mean one or more x's.) Next, consider how this grammar, used as a parse specification, might effect a top-down, left-right parse of the string xxxxxbd:
|
||||||
|
The rule A will recognize xxxxxb (by first descending into X to recognize one x, and again descending into X until all the x's are consumed, and then recognizing the b), and then return to S, and fail to recognize a c. The next clause of S will then descend into B, which in turn again descends into X and recognizes the x's by means of many recursive calls to X, and then a b, and returns to S and finally recognizes a d.
|
||||||
|
The key concept here is inherent in the phrase again descends into X. The process of looking forward, failing, backing up, and then retrying the next alternative is known in parsing as backtracking, and it is primarily backtracking that presents opportunities for memoization in parsing. Consider a function RuleAcceptsSomeInput(Rule, Position, Input), where the parameters are as follows:
|
||||||
|
Rule is the name of the rule under consideration.
|
||||||
|
Position is the offset currently under consideration in the input.
|
||||||
|
Input is the input under consideration.
|
||||||
|
Let the return value of the function RuleAcceptsSomeInput be the length of the input accepted by Rule, or 0 if that rule does not accept any input at that offset in the string. In a backtracking scenario with such memoization, the parsing process is as follows:
|
||||||
|
When the rule A descends into X at offset 0, it memoizes the length 5 against that position and the rule X. After having failed at d, B then, rather than descending again into X, queries the position 0 against rule X in the memoization engine, and is returned a length of 5, thus saving having to actually descend again into X, and carries on as if it had descended into X as many times as before.
|
||||||
|
In the above example, one or many descents into X may occur, allowing for strings such as xxxxxxxxxxxxxxxxbd. In fact, there may be any number of x's before the b. While the call to S must recursively descend into X as many times as there are x's, B will never have to descend into X at all, since the return value of RuleAcceptsSomeInput(X, 0, xxxxxxxxxxxxxxxxbd) will be 16 (in this particular case).
|
||||||
|
Those parsers that make use of syntactic predicates are also able to memoize the results of predicate parses, as well, thereby reducing such constructions as:
|
||||||
|
|
||||||
|
S → (A)? A
|
||||||
|
A → /* some rule */
|
||||||
|
|
||||||
|
to one descent into A.
|
||||||
|
If a parser builds a parse tree during a parse, it must memoize not only the length of the input that matches at some offset against a given rule, but also must store the sub-tree that is generated by that rule at that offset in the input, since subsequent calls to the rule by the parser will not actually descend and rebuild that tree. For the same reason, memoized parser algorithms that generate calls to external code (sometimes called a semantic action routine) when a rule matches must use some scheme to ensure that such rules are invoked in a predictable order.
|
||||||
|
Since, for any given backtracking or syntactic predicate capable parser not every grammar will need backtracking or predicate checks, the overhead of storing each rule's parse results against every offset in the input (and storing the parse tree if the parsing process does that implicitly) may actually slow down a parser. This effect can be mitigated by explicit selection of those rules the parser will memoize.
|
||||||
|
|
||||||
|
|
||||||
|
== See also ==
|
||||||
|
Approximate computing – category of techniques to improve efficiency
|
||||||
|
Computational complexity theory – more information on algorithm complexity
|
||||||
|
Director string – rapidly locating free variables in expressions
|
||||||
|
Dynamic programming – some applications of memoizing techniques
|
||||||
|
Flyweight pattern – an object programming design pattern, that also uses a kind of memoization
|
||||||
|
Hashlife – a memoizing technique to speed up the computation of cellular automata
|
||||||
|
Higher-Order Perl – a free book by Mark Jason Dominus contains an entire chapter on implementing memoization, along with some background
|
||||||
|
Lazy evaluation – shares some concepts with memoization
|
||||||
|
Lookup table – a key data structure used in memoization
|
||||||
|
Materialized view – analogous caching in database queries
|
||||||
|
Partial evaluation – a related technique for automatic program optimization
|
||||||
|
Strength reduction – a compiler optimization that replaces a costly operation with an equivalent, less costly one
|
||||||
|
|
||||||
|
|
||||||
|
== References ==
|
||||||
|
|
||||||
|
|
||||||
|
== External links ==
|
||||||
|
Examples of memoization in various programming languages
|
||||||
|
groovy.lang.Closure#memoize() – Memoize is an Apache Groovy 1.8 language feature.
|
||||||
|
Memoize – Memoize is a small library, written by Tim Bradshaw, for performing memoization in Common Lisp.
|
||||||
|
IncPy – A custom Python interpreter that performs automatic memoization (with no required user annotations)
|
||||||
|
Dave Herman's Macros for defining memoized procedures in Racket.
|
||||||
|
Memoize.pm – a Perl module that implements memoized functions.
|
||||||
|
Java memoization – an example in Java using dynamic proxy classes to create a generic memoization pattern.
|
||||||
|
memoization.java - A Java memoization library.
|
||||||
|
C++Memo – A C++ memoization framework.
|
||||||
|
C-Memo – Generic memoization library for C, implemented using pre-processor function wrapper macros.
|
||||||
|
Tek271 Memoizer – Open source Java memoizer using annotations and pluggable cache implementations.
|
||||||
|
memoizable – A Ruby gem that implements memoized methods.
|
||||||
|
Python memoization – A Python example of memoization.
|
||||||
|
OCaml memoization – Implemented as a Camlp4 syntax extension.
|
||||||
|
Memoization in Lua – Two example implementations of a general memoize function in Lua.
|
||||||
|
Memoization in Mathematica – Memoization and limited memoization in Mathematica.
|
||||||
|
Memoization in Javascript – Extending the Function prototype in JavaScript ( archived version of http://talideon.com/weblog/2005/07/javascript-memoization.cfm ).
|
||||||
|
Memoization in Javascript – Examples of memoization in javascript using own caching mechanism and using the YUI library
|
||||||
|
X-SAIGA – eXecutable SpecificAtIons of GrAmmars. Contains publications related to top-down parsing algorithm that supports left-recursion and ambiguity in polynomial time and space.
|
||||||
|
Memoization in Scheme – A Scheme example of memoization on a class webpage.
|
||||||
|
Memoization in Combinatory Logic – A web service to reduce Combinatory Logic while memoizing every step in a database.
|
||||||
|
MbCache – Cache method results in .NET.
|
20
the_c_programming_language/fahr_to_cels.c
Normal file
20
the_c_programming_language/fahr_to_cels.c
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* print Fahrenheit-Celsius table
|
||||||
|
for fahr = 0, 20 ..., 3000 */
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
int fahr, celsius;
|
||||||
|
int lower, upper, step;
|
||||||
|
|
||||||
|
lower = 0; /* lower limit of temperature table */
|
||||||
|
upper = 300; /* upper limit */
|
||||||
|
step = 20; /* step size */
|
||||||
|
|
||||||
|
fahr = lower;
|
||||||
|
while (fahr <= upper) {
|
||||||
|
celsius = 5 * (fahr-32) / 9;
|
||||||
|
printf("%d\t%d\n", fahr, celsius);
|
||||||
|
fahr = fahr + step;
|
||||||
|
}
|
||||||
|
}
|
8
the_c_programming_language/hello_world.c
Normal file
8
the_c_programming_language/hello_world.c
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
{
|
||||||
|
printf("hello, world\n");
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue