fae 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- OTQ4YmUyNTU0ODY2YjViODVkNjFkY2FmMzY4ZDM0NmVhZTUyMGFlYQ==
5
- data.tar.gz: !binary |-
6
- ZDVmMDY1NTE0ZTRiZTFlMzc2YzUyYTViYmI3NDAzYmUxZDM1OTg0MQ==
2
+ SHA1:
3
+ metadata.gz: 0f109cfeb573be9efac846742d47aa671c7c1839
4
+ data.tar.gz: 04c261c86d6381f25b127c254687df95bb43a6be
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- OThiOTUyNTk5MDc2NzBkNzkyZjk3ZGM2M2VkNWJmOTkxNDEyZWI0MTZjNzkz
10
- ZjFlMzIzMzk5YmRjNDkzOWMxMzM2ZWVmMjIwZGEyZjRjYTY1MGQxYzkwMWRi
11
- MTY4NzdlOTQ3ZDAwZDVlMmEyMWYyZmM5ZTRmZGIzNzAxMmVkMmY=
12
- data.tar.gz: !binary |-
13
- MjA0NmJiZTFmYWQ3Njk0OTc0YzRlM2ZlM2YxNDljYjM2NGIyOTA0MDM4MzIy
14
- OTA1OTNjMzhhNmZiNDI5NzRkYTA1OGNkYTYxY2YyMGM4NTU0YjhhMjNlYTQy
15
- ZjEwZmZhYjE5NTIwNTM2OGQ1OWU5YzJiZTliMzIyNDA5NjQwNjg=
6
+ metadata.gz: 4a28786b8b6c65b38cf8660d7368749ccf283e211308e06698bc87bc85526fd523f2bea7741295f781c280a9a1078b66c1506752ea524a1f724304c6cbc002c5
7
+ data.tar.gz: c6369d569cfb2a877ccb3f6c90b37070184e53dd23228cadb292bd20becbb0d896eee936cef2f998e3767a60835f18c35e64e440c958ae6d8fde73e86864e2d1
data/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/fae.svg)](http://badge.fury.io/rb/fae)
4
4
 
5
- This is a small Ruby gem that evaluates a Finite Automata State Diagram for validity.
5
+ This is a small Ruby gem that evaluates a Finite Automata State Diagram for validity, and can generate Finite Automata from set operations of two or more different Finite Automata.
6
+
7
+ The gem currently only handles Deterministic Finite Automata.
6
8
 
7
9
  ## Dependencies
8
10
 
@@ -16,7 +18,7 @@ Install the gem by running the following command:
16
18
  gem install fae
17
19
  ```
18
20
 
19
- ## Usage
21
+ ## Evaluation Usage
20
22
 
21
23
  The gem comes with an executable, `fae`, and can be run in two different modes, interactive or file.
22
24
 
@@ -138,6 +140,43 @@ If your state diagram is incorrect, the program will give you feedback about you
138
140
 
139
141
  This can help you figure out where your diagram is going wrong.
140
142
 
143
+ ## Generation Usage
144
+
145
+ You can use the gem to generate the union, intersection, or difference of two state diagrams. See the following:
146
+
147
+ ```ruby
148
+ fa_1 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings where the number of a's is odd")
149
+ fa_2 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings that include the substring 'bb'")
150
+
151
+ # These states were determined by drawing the state diagram.
152
+ fa_1.add_states([
153
+ Fae::State.new('A', { :a => 'B', :b => 'A' }, false),
154
+ Fae::State.new('B', { :a => 'A', :b => 'B' }, true),
155
+ ])
156
+
157
+ # These states were determined by drawing the state diagram.
158
+ fa_2.add_states([
159
+ Fae::State.new('C', { :a => 'C', :b => 'D' }, false),
160
+ Fae::State.new('D', { :a => 'C', :b => 'E' }, false),
161
+ Fae::State.new('E', { :a => 'E', :b => 'E' }, true),
162
+ ])
163
+
164
+ # Get the intersection, union, and difference of the two finite automata
165
+ intersection = fa_1.intersection(fa_2)
166
+ union = fa_1.union(fa_2)
167
+ difference = fa_1.difference(fa_2)
168
+ ```
169
+
170
+ You can then output the new finite automata to see the states, paths, and accepting states:
171
+
172
+ ```ruby
173
+ puts intersection
174
+ ```
175
+
176
+ ![Example Intersection Output](https://raw.githubusercontent.com/caseyscarborough/fae/master/etc/example_intersection_output.png)
177
+
178
+ See [`intersection_union_difference.rb`](https://github.com/caseyscarborough/fae/blob/master/examples/intersection_union_difference.rb) for an example of generating the diagram and evaluating them.
179
+
141
180
  ## Examples
142
181
 
143
182
  An example data file and direct API usage can be seen in the [examples directory](https://github.com/caseyscarborough/fae/tree/master/examples).
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This script demonstrates the usage of the fae API and
4
+ # checks the state diagram for the intersection, union,
5
+ # and difference of the following two languages:
6
+ #
7
+ # - The language of all strings with an odd number of a's
8
+ # - The language of all strings that include the substring 'bb'
9
+ #
10
+ require_relative '../lib/fae'
11
+
12
+ CHARACTERS = ['a', 'b']
13
+ LANGUAGE = Fae::Language.new(CHARACTERS)
14
+
15
+ def string_has_odd_number_of_the_letter_a?(string)
16
+ string.gsub('b', '').length % 2 == 1
17
+ end
18
+
19
+ def string_has_bb_substring?(string)
20
+ !string.match(/bb/).nil?
21
+ end
22
+
23
+ def valid_for_intersection?(string)
24
+ string_has_odd_number_of_the_letter_a?(string) && string_has_bb_substring?(string)
25
+ end
26
+
27
+ def valid_for_union?(string)
28
+ string_has_odd_number_of_the_letter_a?(string) || string_has_bb_substring?(string)
29
+ end
30
+
31
+ def valid_for_difference?(string)
32
+ string_has_odd_number_of_the_letter_a?(string) && !string_has_bb_substring?(string)
33
+ end
34
+
35
+ # Returns 100 random strings in the language, 50 of length 15 and 50 of length 5.
36
+ def get_random_strings
37
+ generate_string = lambda do |num|
38
+ string = ""
39
+ num.times { string << CHARACTERS.sample }
40
+ string
41
+ end
42
+
43
+ strings = []
44
+ 50.times { strings << generate_string.call(15) }
45
+ 50.times { strings << generate_string.call(5) }
46
+ strings
47
+ end
48
+
49
+ fa_1 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings where the number of a's is odd")
50
+ fa_2 = Fae::FiniteAutomata.new(LANGUAGE, "the language of all strings that include the substring 'bb'")
51
+
52
+ # These states were determined by drawing the state diagram language 1.
53
+ fa_1.add_states([
54
+ Fae::State.new('A', { :a => 'B', :b => 'A' }, false),
55
+ Fae::State.new('B', { :a => 'A', :b => 'B' }, true),
56
+ ])
57
+
58
+ # These states were determined by drawing the state diagram for language 2.
59
+ fa_2.add_states([
60
+ Fae::State.new('C', { :a => 'C', :b => 'D' }, false),
61
+ Fae::State.new('D', { :a => 'C', :b => 'E' }, false),
62
+ Fae::State.new('E', { :a => 'E', :b => 'E' }, true),
63
+ ])
64
+
65
+ # Perform the intersection
66
+ intersection = fa_1.intersection(fa_2)
67
+ get_random_strings.each { |s| intersection.add_string(String.new(s, valid_for_intersection?(s))) }
68
+ intersection.evaluate!
69
+
70
+ # Perform the union
71
+ union = fa_1.union(fa_2)
72
+ get_random_strings.each { |s| union.add_string(String.new(s, valid_for_union?(s))) }
73
+ union.evaluate!
74
+
75
+ # Perform the difference
76
+ difference = fa_1.difference(fa_2)
77
+ get_random_strings.each { |s| difference.add_string(String.new(s, valid_for_difference?(s))) }
78
+ union.evaluate!
@@ -5,6 +5,7 @@ module Fae
5
5
  # Takes in a language, states, and strings, and checks them
6
6
  # to validate a state diagram.
7
7
  class FiniteAutomata
8
+ attr_reader :language, :description, :states, :strings
8
9
 
9
10
  # Initializes a new instance of the FiniteAutomata.
10
11
  #
@@ -27,6 +28,13 @@ module Fae
27
28
  end
28
29
  end
29
30
 
31
+ # Adds a single string to the strings array.
32
+ #
33
+ # @param string [String] the string to add
34
+ def add_string(string)
35
+ @strings << string
36
+ end
37
+
30
38
  # Adds strings to check against when evaluating.
31
39
  #
32
40
  # @param states [Array] an array of states
@@ -53,6 +61,30 @@ module Fae
53
61
  return retrieved_state
54
62
  end
55
63
 
64
+ # Generates the finite automata for the intersection
65
+ # of two different finite automatas.
66
+ #
67
+ # @param fa [FiniteAutomata] the finite automata to intersect this one with
68
+ def intersection(fa)
69
+ perform_set_operation(:intersection, fa)
70
+ end
71
+
72
+ # Generates the finite automata for the union
73
+ # of two different finite automatas.
74
+ #
75
+ # @param fa [FiniteAutomata] the finite automata to union this one with
76
+ def union(fa)
77
+ perform_set_operation(:union, fa)
78
+ end
79
+
80
+ # Generates the finite automata for the difference
81
+ # of two different finite automatas.
82
+ #
83
+ # @param fa [FiniteAutomata] the finite automata to difference this one with
84
+ def difference(fa)
85
+ perform_set_operation(:difference, fa)
86
+ end
87
+
56
88
  # Runs the evaluation on the finite automata.
57
89
  def evaluate!
58
90
  @invalids = []
@@ -84,7 +116,55 @@ module Fae
84
116
  puts
85
117
  end
86
118
 
119
+ def to_s
120
+ output = ""
121
+ output << "Description: ".colorize(:yellow) + @description
122
+ output << "\nLanguage: ".colorize(:yellow) + language.characters.to_s
123
+ output << "\nStates:".colorize(:yellow)
124
+ @states.each do |state|
125
+ output << "\n State #{state.name}: ".colorize(:blue)
126
+ state.paths.keys.each do |key|
127
+ output << "\n #{'~'.colorize(:yellow)} #{key} #{'->'.colorize(:light_black)} #{state.paths[key]}"
128
+ end
129
+ accepting = state.accepting ? "accepting".colorize(:green) : "not accepting".colorize(:red)
130
+ output << "\n #{'~'.colorize(:yellow)} #{accepting}"
131
+ end
132
+ return output
133
+ end
134
+
87
135
  private
136
+ def perform_set_operation(type, fa)
137
+ if (@language.characters.uniq.sort != fa.language.characters.uniq.sort)
138
+ puts "Performing the #{type.to_s} requires the languages to be the same.".colorize(:red)
139
+ return
140
+ end
141
+ i_states = []
142
+ i_strings = []
143
+ i_description = "the #{type.to_s} of #{@description} and #{fa.description}"
144
+ i_fa = FiniteAutomata.new(@language, i_description)
145
+
146
+ @states.each do |state|
147
+ fa.states.each do |fa_state|
148
+ name = state.name + fa_state.name
149
+ accepting = false
150
+ paths = {}
151
+ state.paths.keys.each do |key|
152
+ path = state.paths[key] + fa_state.paths[key]
153
+ paths[key] = path
154
+ end
155
+ if ((type == :intersection && (state.accepting && fa_state.accepting)) ||
156
+ (type == :difference && (state.accepting && !fa_state.accepting )) ||
157
+ (type == :union && (state.accepting || fa_state.accepting)))
158
+ accepting = true
159
+ end
160
+ i_states << State.new(name, paths, accepting)
161
+ end
162
+ end
163
+
164
+ i_fa.add_states(i_states)
165
+ return i_fa
166
+ end
167
+
88
168
  def add_state(new_state)
89
169
  valid = true
90
170
  @states.each do |state|
@@ -99,7 +179,7 @@ module Fae
99
179
  print "#{string.colorize(:blue)}: "
100
180
  if (@language.string_is_valid(string))
101
181
  result = @states.first.evaluate(string, self)
102
- if (result[:valid] != string.expected)
182
+ if (result[:accepting] != string.expected)
103
183
  puts "\u2717".encode("utf-8").colorize(:red)
104
184
  return false
105
185
  else
@@ -2,7 +2,7 @@ module Fae
2
2
 
3
3
  # A state in the state diagram.
4
4
  class State
5
- attr_accessor :name, :next_states, :valid
5
+ attr_accessor :name, :paths, :accepting
6
6
 
7
7
  # Creates a new state instance.
8
8
  #
@@ -11,24 +11,27 @@ module Fae
11
11
  # @param valid [Boolean] whether or not this is an accepting state
12
12
  # @example
13
13
  # State.new('A', { :a => 'B', :b => 'A' }, true)
14
- def initialize(name, next_states, valid)
14
+ def initialize(name, paths, accepting)
15
15
  @name = name
16
- @next_states = next_states
17
- @valid = valid
16
+ @paths = paths
17
+ @accepting = accepting
18
18
  end
19
19
 
20
- # Evaluates a string at this state, and passes it to the next state.
20
+ # Evaluates a string at this state, and passes the next string to the next state.
21
21
  #
22
22
  # @param string [String] the string to evaluate
23
23
  # @param fa [FiniteAutomata] the finite automata that this state belongs to
24
24
  def evaluate(string, fa)
25
25
  if (string.first.empty?)
26
- output = @valid ? "valid".colorize(:green) : "invalid".colorize(:red)
26
+ output = @accepting ? "accepting".colorize(:green) : "not accepting".colorize(:red)
27
27
  print "#{@name} (#{output}) "
28
- return { :output => output, :valid => @valid }
28
+ return { :output => output, :accepting => @accepting }
29
29
  end
30
30
  print "#{@name} #{'->'.colorize(:light_black)} "
31
- fa.get_state(next_states[string.first.to_sym]).evaluate(string.shift_left, fa)
31
+
32
+ next_state = fa.get_state(paths[string.first.to_sym])
33
+ next_string = string.shift_left
34
+ next_state.evaluate(next_string, fa)
32
35
  end
33
36
  end
34
37
  end
@@ -1,3 +1,3 @@
1
1
  module Fae
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,55 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fae
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Casey Scarborough
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-08 00:00:00.000000000 Z
11
+ date: 2014-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.6'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: colorize
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ! '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ! '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  description: ''
@@ -60,7 +60,7 @@ executables:
60
60
  extensions: []
61
61
  extra_rdoc_files: []
62
62
  files:
63
- - .gitignore
63
+ - ".gitignore"
64
64
  - Gemfile
65
65
  - LICENSE.txt
66
66
  - README.md
@@ -69,9 +69,11 @@ files:
69
69
  - etc/example_failed_output.png
70
70
  - etc/example_file_mode_output.png
71
71
  - etc/example_interactive_mode_output.png
72
+ - etc/example_intersection_output.png
72
73
  - etc/example_state_diagram.png
73
74
  - examples/diagrams.yml
74
75
  - examples/example.rb
76
+ - examples/intersection_union_difference.rb
75
77
  - fae.gemspec
76
78
  - lib/fae.rb
77
79
  - lib/fae/finite_automata.rb
@@ -89,12 +91,12 @@ require_paths:
89
91
  - lib
90
92
  required_ruby_version: !ruby/object:Gem::Requirement
91
93
  requirements:
92
- - - ! '>='
94
+ - - ">="
93
95
  - !ruby/object:Gem::Version
94
96
  version: '0'
95
97
  required_rubygems_version: !ruby/object:Gem::Requirement
96
98
  requirements:
97
- - - ! '>='
99
+ - - ">="
98
100
  - !ruby/object:Gem::Version
99
101
  version: '0'
100
102
  requirements: []