0
Who makes the best     Asado?
What country has the best footballers?
Argentina!
river
riverNacional B???
NOOOOOOOO!!!!
Aaron Patterson
@tenderlove
Ruby core team
Rails core team
! WARNING !
ZOMG!
We are in Argentina!
José   Yo
WWFMD?
me gusta
¿Por qué Maria?
THANKS!!
ResourceManagement
Path Management
Move bet ween theory   and practice PatternMatching
Graphviz http://graphviz.org
greetinghello              world
graph.dotdigraph nfa {  rankdir=LR;    world [shape = doublecircle];    hello [shape = circle];    hello -> world [label="...
graph.dotdigraph nfa {  rankdir=LR;    world [shape = doublecircle];    hello [shape = circle];    goodbye [shape = circle...
hello          greeting                     worldgoodbye
/articles(.:format)    /       articles       .       (?-mix:[^./?]+)0       1              2       3                     ...
Journey
JourneyYes, its named after the 70s rock sensation
Provides  1: URL generation  2: Path Recognition  3: Route parsingWhat is a router?
Why a new router?
Maintenance
LOC300022501500 750   0       Journey   Journey-2   Rack-Mount
Known algorithms
References• Compilers: Principles, Techniques, &  Tools (Aho, Lam, Sethi, Ullman)• Intro to Formal Languages and  Automata...
Predictability
CPU Time
Memory Usage
Current Performance            rack-mount                  Journey   url generation    path recognition       route parsing
me gusta
Patterns
Why patterns matter
Regular Expressions       /om(g)!/
Rails Routes
resource :articles
/articles(.:format)/articles/new(.:format)/articles/:id/edit(.:format)/articles/:id(.:format)
Parens dont capture
:whatever => /([^./?]+)/
/articles(.:format)//articles(?:.([^./?]+))?$/
//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?...
GET /articles/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///art...
GET /articles/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///art...
GET /articles/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///art...
//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?...
GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///article...
GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///article...
GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///article...
GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///article...
GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///article...
GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///article...
How long does this      take?
r = routes.length
x = regexp compare
O(r ⨉ x)
Can we do better?
Parse Trees     AutomataFinite State Machines
Parse Trees
Parsing regexp
(a|b)*abb
○   Describe node types  ○ b○ ba *  ()  | a b
Parsing rails routes
Journey::Parserparser = Journey::Parser.newast    = parser.parse /articles(.:format)puts ast.to_dot
○      ○        ()/   articles    ○           .        :format
To String!parser = Journey::Parser.newast    = parser.parse /articles(.:format)puts ast.to_sputs ast.to_regexp
OR ASTparser = Journey::Parser.newtrees = [  /articles(.:format),  /articles/new(.:format),].map { |s| parser.parse s }ast...
|                 ○                   ○      ○          ()                  ○         ()/   articles     ○               ○...
SECRET FEATUREparser = Journey::Parser.newast = parser.parse /articles|books(.:format)
SECRET FEATUREparser = Journey::Parser.new                            RE T!                        ECast = parser.parse /a...
Automata
(a|b)*abb
Double circle                    is acceptance                    state        b       a            a       b    b   0    ...
baabb
b       a            a       b    b   0       1   a            a           23            b
b        b       a            a       b    b   0       1   a            a           23            b
ba        b        a            a        b    b   0        1   a            a            23            b
baa        b         a             a        b    b   0         1   a             a            23             b
baabb        b       a            a       b    b   0       1   a            a           23            b
aab        b         a             a        b    b   0         1   a             a            23             b
Deterministic Finite   Automaton
Only one path
Storageclass DFA  attr_reader :table  def initialize    @table = Hash.new { |h, from| h[from] = {} }    @table[0][a] = 1  ...
FSM Simulation
class Simulator  def initialize(dfa)    @dfa = dfa  end  def simulate(symbols)    state = 0    until symbols.empty?      s...
move functionclass DFA  ...  def move(from, symbol)    @table[from][symbol]  endend
irb> sim = Simulator.new(DFA.new)=> #<Simulator:0x007f95929a82e0 ...>irb> sim.simulate %w{ b a a b b }=> 3irb> sim.simulat...
Time: O(n) n = string.length
me gusta
Space: S + Tstates.length + transitions.length
NondeterministicFinite Automaton
Has nil edges
Cant tell direction
Simulation of NFA
a|b            a    ε   2         3   ε0   ε                 ε   6            b        4         5
nil-closure             a    ε    2       3    ε0   ε                 ε   6             b         4       5
a            a    ε   2       3   ε0   ε               ε   6            b        4       5
Storageclass NFA  def initialize    @table = Hash.new { |h, from|      h[from] = Hash.new { |i,sym| i[sym] = [] }    }    ...
FSM Simulationclass Simulator  def initialize(nfa)    @nfa = nfa  end  def simulate(symbols)    states = @nfa.nil_closure(...
Move functionclass NFA  ...  def move(states, symbol)    states.map { |s| @table[s][symbol] }.flatten  endend
irb> sim = Simulator.new(NFA.new)=> #<Simulator:0x007faa188a5f88 ...>irb> sim.simulate %w{ a }=> [6]irb> sim.simulate %w{ ...
Time: O(r ⨉ x)r = operators.length, x = string.length
Who cares about    NFA?
NFA Construction
/articles(.:format)            ○        ○        ()  /   articles    ○             .        :format
cat nodes    /0       1
/articles    /        articles0        1              2
Optional             ε0   ε       ?????       ε   3        1           2
ε    /       articles0       1              2   ε                             ε   6                                   .   ...
Journey::NFAparser = Journey::Parser.newast    = parser.parse /articles(.:format)nfa    = Journey::NFA::Builder.new asttt ...
ConvertingNFA to DFA
Eliminate nil transitions
Collapse duplicate      edges
/articles(.:format)                                       ε    /       articles0       1              2   ε               ...
/articles(.:format)    /       articles       .       :format0       1              2       3             4
CODES!parser = Journey::Parser.newast    = parser.parse /articles(.:format)nfa    = Journey::NFA::Builder.new asttt = nfa....
SHORTER CODES!parser = Journey::Parser.newast    = parser.parse /articles(.:format)dfa    = Journey::GTG::Builder.new astt...
ANY NFAconverts to DFA
O(r ⨉ x) => O(x)r = operations.length, x = string.length
me gusta
Converting Automata    to Regexp
/articles(.:format)    /       articles       .       :format0       1              2       3             4
/articles(.:format)    /articles       .       :format0               2       3             4
/articles(.:format)    /articles       .:format0               2              4
/articles(.:format)     /articles(.:format)?0                           4
GeneralizedTransition Graph
resource :users/users(.:format)/users/new(.:format)/users/:id/edit(.:format)/users/:id(.:format)
resource :users                                (?-mix:[^./?]+)                        .   3                     5    /    ...
The Plan?
Combine All Routes
Produce DFA
Simulate in O(n) Time
Routing To The   Future
JS Simulation
Table => JSONparser = Journey::Parser.newast    = parser.parse /articles(.:format)dfa    = Journey::GTG::Builder.new asttt...
Table => SVGparser = Journey::Parser.newast    = parser.parse /articles(.:format)dfa    = Journey::GTG::Builder.new asttt ...
JS Tokenizerfunction tokenize(input, callback) {  while(input.length > 0) {    callback(input.match(/^[/.?]|[^/.?]+/)[0]);...
JS Simulatortokenize(input, function(token) {  var new_states = [];  for(var key in states) {    var state = states[key]; ...
d3.js
Rails Consoleirb> File.open(out.html, wb) { |f|irb* f.write(irb* Wot::Application.routes.router.visualizerirb> )}=> 69074
routes.rb marshalling
Test suggestions
Test coverage
Usage Heat Maps
ASTREGEXP         NFA         DFA
ROFLSCALE!!!
DFA => YACC
DFA => RACC
DFA => Ragel
Open Questions
Is our GTGdeterministic?
/users/new|/users/:id                                     new          4    /       users       /       (?-mix:[^./?]+)0  ...
"new" =~ /new|[^./?]+/
Can we make itdeterministic?
L1 = {new}L2 = {[^./?]+}
/users/new|/users/:id                                   L1      4     /       users       /       L2 - L10        1       ...
Is it worth our effort?
ASTREGEXP         NFA         DFA
Is it worth our effort?
Thank You!!
<3<3<3<3<3
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
RubyConf Argentina 2011
Upcoming SlideShare
Loading in...5
×

RubyConf Argentina 2011

12,820

Published on

RubyConf Argentina 2011

Published in: Technology, Business
0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
12,820
On Slideshare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
30
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide

Transcript of "RubyConf Argentina 2011"

  1. 1. Who makes the best Asado?
  2. 2. What country has the best footballers?
  3. 3. Argentina!
  4. 4. river
  5. 5. riverNacional B???
  6. 6. NOOOOOOOO!!!!
  7. 7. Aaron Patterson
  8. 8. @tenderlove
  9. 9. Ruby core team
  10. 10. Rails core team
  11. 11. ! WARNING !
  12. 12. ZOMG!
  13. 13. We are in Argentina!
  14. 14. José Yo
  15. 15. WWFMD?
  16. 16. me gusta
  17. 17. ¿Por qué Maria?
  18. 18. THANKS!!
  19. 19. ResourceManagement
  20. 20. Path Management
  21. 21. Move bet ween theory and practice PatternMatching
  22. 22. Graphviz http://graphviz.org
  23. 23. greetinghello world
  24. 24. graph.dotdigraph nfa { rankdir=LR; world [shape = doublecircle]; hello [shape = circle]; hello -> world [label="greeting"];}
  25. 25. graph.dotdigraph nfa { rankdir=LR; world [shape = doublecircle]; hello [shape = circle]; goodbye [shape = circle]; hello -> world [label="greeting"]; goodbye -> world}
  26. 26. hello greeting worldgoodbye
  27. 27. /articles(.:format) / articles . (?-mix:[^./?]+)0 1 2 3 5 / new 4 6 /articles/new(.:format)
  28. 28. Journey
  29. 29. JourneyYes, its named after the 70s rock sensation
  30. 30. Provides 1: URL generation 2: Path Recognition 3: Route parsingWhat is a router?
  31. 31. Why a new router?
  32. 32. Maintenance
  33. 33. LOC300022501500 750 0 Journey Journey-2 Rack-Mount
  34. 34. Known algorithms
  35. 35. References• Compilers: Principles, Techniques, & Tools (Aho, Lam, Sethi, Ullman)• Intro to Formal Languages and Automata (Linz)
  36. 36. Predictability
  37. 37. CPU Time
  38. 38. Memory Usage
  39. 39. Current Performance rack-mount Journey url generation path recognition route parsing
  40. 40. me gusta
  41. 41. Patterns
  42. 42. Why patterns matter
  43. 43. Regular Expressions /om(g)!/
  44. 44. Rails Routes
  45. 45. resource :articles
  46. 46. /articles(.:format)/articles/new(.:format)/articles/:id/edit(.:format)/articles/:id(.:format)
  47. 47. Parens dont capture
  48. 48. :whatever => /([^./?]+)/
  49. 49. /articles(.:format)//articles(?:.([^./?]+))?$/
  50. 50. //articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  51. 51. GET /articles/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  52. 52. GET /articles/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  53. 53. GET /articles/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/ 200 OK!
  54. 54. //articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  55. 55. GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  56. 56. GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  57. 57. GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  58. 58. GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  59. 59. GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/
  60. 60. GET /foos/new//articles(?:.([^./?]+))?$///articles/new(?:.([^./?]+))?$///articles/([^./?]+)/edit(?:.([^./?]+))?$///articles/([^./?]+)(?:.([^./?]+))?$/ 404 Not Found
  61. 61. How long does this take?
  62. 62. r = routes.length
  63. 63. x = regexp compare
  64. 64. O(r ⨉ x)
  65. 65. Can we do better?
  66. 66. Parse Trees AutomataFinite State Machines
  67. 67. Parse Trees
  68. 68. Parsing regexp
  69. 69. (a|b)*abb
  70. 70. ○ Describe node types ○ b○ ba * () | a b
  71. 71. Parsing rails routes
  72. 72. Journey::Parserparser = Journey::Parser.newast = parser.parse /articles(.:format)puts ast.to_dot
  73. 73. ○ ○ ()/ articles ○ . :format
  74. 74. To String!parser = Journey::Parser.newast = parser.parse /articles(.:format)puts ast.to_sputs ast.to_regexp
  75. 75. OR ASTparser = Journey::Parser.newtrees = [ /articles(.:format), /articles/new(.:format),].map { |s| parser.parse s }ast = Journey::Nodes::Or.new treesputs ast.to_dot
  76. 76. | ○ ○ ○ () ○ ()/ articles ○ ○ new ○ . :format ○ / . :format / articles
  77. 77. SECRET FEATUREparser = Journey::Parser.newast = parser.parse /articles|books(.:format)
  78. 78. SECRET FEATUREparser = Journey::Parser.new RE T! ECast = parser.parse /articles|books(.:format) RS SU PE
  79. 79. Automata
  80. 80. (a|b)*abb
  81. 81. Double circle is acceptance state b a a b b 0 1 a a 23 b
  82. 82. baabb
  83. 83. b a a b b 0 1 a a 23 b
  84. 84. b b a a b b 0 1 a a 23 b
  85. 85. ba b a a b b 0 1 a a 23 b
  86. 86. baa b a a b b 0 1 a a 23 b
  87. 87. baabb b a a b b 0 1 a a 23 b
  88. 88. aab b a a b b 0 1 a a 23 b
  89. 89. Deterministic Finite Automaton
  90. 90. Only one path
  91. 91. Storageclass DFA attr_reader :table def initialize @table = Hash.new { |h, from| h[from] = {} } @table[0][a] = 1 @table[0][b] = 0 @table[1][a] = 1 @table[1][b] = 2 @table[2][a] = 1 @table[2][b] = 3 @table[3][b] = 0 @table[3][a] = 2 endend
  92. 92. FSM Simulation
  93. 93. class Simulator def initialize(dfa) @dfa = dfa end def simulate(symbols) state = 0 until symbols.empty? state = @dfa.move(state, symbols.shift) end state endend
  94. 94. move functionclass DFA ... def move(from, symbol) @table[from][symbol] endend
  95. 95. irb> sim = Simulator.new(DFA.new)=> #<Simulator:0x007f95929a82e0 ...>irb> sim.simulate %w{ b a a b b }=> 3irb> sim.simulate %w{ a a b }=> 2
  96. 96. Time: O(n) n = string.length
  97. 97. me gusta
  98. 98. Space: S + Tstates.length + transitions.length
  99. 99. NondeterministicFinite Automaton
  100. 100. Has nil edges
  101. 101. Cant tell direction
  102. 102. Simulation of NFA
  103. 103. a|b a ε 2 3 ε0 ε ε 6 b 4 5
  104. 104. nil-closure a ε 2 3 ε0 ε ε 6 b 4 5
  105. 105. a a ε 2 3 ε0 ε ε 6 b 4 5
  106. 106. Storageclass NFA def initialize @table = Hash.new { |h, from| h[from] = Hash.new { |i,sym| i[sym] = [] } } @table[0][nil] << 2 @table[0][nil] << 4 @table[2][a] << 3 @table[4][b] << 5 @table[3][nil] << 6 @table[5][nil] << 6 end def nil_closure(states) states.map { |s| @table[s][nil] }.flatten endend
  107. 107. FSM Simulationclass Simulator def initialize(nfa) @nfa = nfa end def simulate(symbols) states = @nfa.nil_closure([0]) until symbols.empty? next_s = @nfa.move(states, symbols.shift) states = @nfa.nil_closure(next_s) end states endend
  108. 108. Move functionclass NFA ... def move(states, symbol) states.map { |s| @table[s][symbol] }.flatten endend
  109. 109. irb> sim = Simulator.new(NFA.new)=> #<Simulator:0x007faa188a5f88 ...>irb> sim.simulate %w{ a }=> [6]irb> sim.simulate %w{ b }=> [6]irb> sim.simulate %w{ b b }=> []irb> sim.simulate %w{ c }=> []
  110. 110. Time: O(r ⨉ x)r = operators.length, x = string.length
  111. 111. Who cares about NFA?
  112. 112. NFA Construction
  113. 113. /articles(.:format) ○ ○ () / articles ○ . :format
  114. 114. cat nodes /0 1
  115. 115. /articles / articles0 1 2
  116. 116. Optional ε0 ε ????? ε 3 1 2
  117. 117. ε / articles0 1 2 ε ε 6 . :format 3 4 5
  118. 118. Journey::NFAparser = Journey::Parser.newast = parser.parse /articles(.:format)nfa = Journey::NFA::Builder.new asttt = nfa.transition_tableputs tt.to_dot
  119. 119. ConvertingNFA to DFA
  120. 120. Eliminate nil transitions
  121. 121. Collapse duplicate edges
  122. 122. /articles(.:format) ε / articles0 1 2 ε ε 6 . :format 3 4 5
  123. 123. /articles(.:format) / articles . :format0 1 2 3 4
  124. 124. CODES!parser = Journey::Parser.newast = parser.parse /articles(.:format)nfa = Journey::NFA::Builder.new asttt = nfa.transition_table.generalized_tableputs tt.to_dot
  125. 125. SHORTER CODES!parser = Journey::Parser.newast = parser.parse /articles(.:format)dfa = Journey::GTG::Builder.new asttt = dfa.transition_tableputs tt.to_dot
  126. 126. ANY NFAconverts to DFA
  127. 127. O(r ⨉ x) => O(x)r = operations.length, x = string.length
  128. 128. me gusta
  129. 129. Converting Automata to Regexp
  130. 130. /articles(.:format) / articles . :format0 1 2 3 4
  131. 131. /articles(.:format) /articles . :format0 2 3 4
  132. 132. /articles(.:format) /articles .:format0 2 4
  133. 133. /articles(.:format) /articles(.:format)?0 4
  134. 134. GeneralizedTransition Graph
  135. 135. resource :users/users(.:format)/users/new(.:format)/users/:id/edit(.:format)/users/:id(.:format)
  136. 136. resource :users (?-mix:[^./?]+) . 3 5 / users0 1 2 / new . (?-mix:[^./?]+) 4 6 8 11 (?-mix:[^./?]+) / edit . (?-mix:[^./?]+) 7 9 12 14 15 . (?-mix:[^./?]+) 10 13
  137. 137. The Plan?
  138. 138. Combine All Routes
  139. 139. Produce DFA
  140. 140. Simulate in O(n) Time
  141. 141. Routing To The Future
  142. 142. JS Simulation
  143. 143. Table => JSONparser = Journey::Parser.newast = parser.parse /articles(.:format)dfa = Journey::GTG::Builder.new asttt = dfa.transition_tableputs tt.to_json
  144. 144. Table => SVGparser = Journey::Parser.newast = parser.parse /articles(.:format)dfa = Journey::GTG::Builder.new asttt = dfa.transition_tableputs tt.to_svg
  145. 145. JS Tokenizerfunction tokenize(input, callback) { while(input.length > 0) { callback(input.match(/^[/.?]|[^/.?]+/)[0]); input = input.replace(/^[/.?]|[^/.?]+/, ); }}
  146. 146. JS Simulatortokenize(input, function(token) { var new_states = []; for(var key in states) { var state = states[key]; if(string_states[state] && string_states[state][token]) { var new_state = string_states[state][token]; highlight_edge(state, new_state); highlight_state(new_state); new_states.push(new_state); } } if(new_states.length == 0) { return; } states = new_states;});
  147. 147. d3.js
  148. 148. Rails Consoleirb> File.open(out.html, wb) { |f|irb* f.write(irb* Wot::Application.routes.router.visualizerirb> )}=> 69074
  149. 149. routes.rb marshalling
  150. 150. Test suggestions
  151. 151. Test coverage
  152. 152. Usage Heat Maps
  153. 153. ASTREGEXP NFA DFA
  154. 154. ROFLSCALE!!!
  155. 155. DFA => YACC
  156. 156. DFA => RACC
  157. 157. DFA => Ragel
  158. 158. Open Questions
  159. 159. Is our GTGdeterministic?
  160. 160. /users/new|/users/:id new 4 / users / (?-mix:[^./?]+)0 1 2 3 5
  161. 161. "new" =~ /new|[^./?]+/
  162. 162. Can we make itdeterministic?
  163. 163. L1 = {new}L2 = {[^./?]+}
  164. 164. /users/new|/users/:id L1 4 / users / L2 - L10 1 2 3 5
  165. 165. Is it worth our effort?
  166. 166. ASTREGEXP NFA DFA
  167. 167. Is it worth our effort?
  168. 168. Thank You!!
  169. 169. <3<3<3<3<3
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×