fpgrowth 0.0.2 → 1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,7 +5,7 @@ module FpGrowth
5
5
  module Miner
6
6
  class PatternBaseExtractor
7
7
 
8
- def initialize(tree=FpTree.new, item)
8
+ def initialize(tree=FpTree::FpTree.new() , item)
9
9
  @tree = tree
10
10
  @horizontal_cursor = tree.heads[item]
11
11
  @conditional_item = item
@@ -52,7 +52,7 @@ module FpGrowth
52
52
  def down_to_top_traversal(current_branch=@current_branch, vertical_cursor=@vertical_cursor)
53
53
  @vertical_cursor = vertical_cursor
54
54
  while @vertical_cursor != nil and @vertical_cursor.item != nil
55
- down_to_top_traversal_step()
55
+ down_to_top_traversal_step(current_branch)
56
56
  @vertical_cursor = @vertical_cursor.parent
57
57
  end
58
58
  current_branch.reverse!
@@ -62,9 +62,57 @@ module FpGrowth
62
62
  #
63
63
  # It just gather items
64
64
  #
65
- def down_to_top_traversal_step(current_branch=@current_branch, vertical_cursor=@vertical_cursor, min_support=@min_support)
65
+ def down_to_top_traversal_step(current_branch=@current_branch, vertical_cursor=@vertical_cursor)
66
66
  current_branch << vertical_cursor.item
67
67
  end
68
+
69
+
70
+ #fonction qui sert uniquement pour les tests
71
+ def test_conditionnal_item(item )
72
+ if item == @conditional_item
73
+ then return true
74
+ end
75
+ return false
76
+ end
77
+
78
+ def test_patterns(patterns = [])
79
+ if patterns == @patterns
80
+ then return true
81
+ end
82
+ return false
83
+ end
84
+
85
+ def test_tree(tree = FpTree::FpTree.new() )
86
+ if tree.threshold == @tree.threshold and tree.root == @tree.root
87
+ then return true
88
+ end
89
+ return false
90
+ end
91
+
92
+ def test_min_support( min_support )
93
+ if min_support == @min_support
94
+ then return true
95
+ end
96
+ return false
97
+ end
98
+
99
+ def test_current_branch (current_branch)
100
+ if current_branch == @current_branch
101
+ then return true
102
+ end
103
+ return false
104
+ end
105
+
106
+ def vertical_cursor (vertical_cursor)
107
+ if vertical_cursor == @vertical_cursor
108
+ then return true
109
+ end
110
+ return false
111
+ end
112
+
113
+
114
+
115
+
68
116
  end
69
117
  end
70
118
  end
@@ -1,3 +1,3 @@
1
1
  module FpGrowth
2
- VERSION = "0.0.2"
2
+ VERSION = "1"
3
3
  end
@@ -17,6 +17,8 @@ class TestConditionalTreeBuilder < Test::Unit::TestCase
17
17
  @tableau_pattern_one_element << FpGrowth::Miner::Pattern.new(['a'], 12)
18
18
 
19
19
  @supports_exemple = {'a' => 1, 'b' => 5, 'c' => 4}
20
+ @supports_exemple_for_pruning = {'a' => 4, 'b' => 5, 'c' => 3}
21
+
20
22
  @pattern_exemple = FpGrowth::Miner::Pattern.new(['a', 'b'], 2)
21
23
  end
22
24
 
@@ -52,14 +54,51 @@ class TestConditionalTreeBuilder < Test::Unit::TestCase
52
54
 
53
55
  def test_execute
54
56
 
57
+ # no argument
58
+ conditional_tree_builder = nil
59
+ assert_nothing_raised { conditional_tree_builder = FpGrowth::Miner::ConditionalTreeBuilder.new() }
60
+ assert_nothing_raised { conditional_tree_builder.execute }
61
+ assert_equal(true, conditional_tree_builder.test_execute_threshold(1))
62
+ assert_equal(true, conditional_tree_builder.test_execute_pattern_base())
63
+
64
+ # One element
65
+ assert_nothing_raised { conditional_tree_builder = FpGrowth::Miner::ConditionalTreeBuilder.new(@tableau_pattern_one_element , 1) }
66
+ assert_nothing_raised { conditional_tree_builder.execute }
67
+ assert_equal(true, conditional_tree_builder.test_execute_threshold(1))
68
+ assert_equal(true, conditional_tree_builder.test_execute_pattern_base(@tableau_pattern_one_element))
69
+
70
+ # Three element
71
+ assert_nothing_raised { conditional_tree_builder = FpGrowth::Miner::ConditionalTreeBuilder.new(@tableau_pattern , 1) }
72
+ assert_nothing_raised { conditional_tree_builder.execute }
73
+ assert_equal(true, conditional_tree_builder.test_execute_threshold(1))
74
+ assert_equal(true, conditional_tree_builder.test_execute_pattern_base(@tableau_pattern))
55
75
 
56
76
  end
57
77
 
58
78
  def test_second_pass
79
+ # Test unit impossible (Fp-tree)
59
80
 
60
81
  end
61
82
 
62
83
  def test_first_pass
84
+ # no elements
85
+ conditional_tree_builder = nil
86
+ assert_nothing_raised { conditional_tree_builder = FpGrowth::Miner::ConditionalTreeBuilder.new() }
87
+ assert_nothing_raised { conditional_tree_builder.first_pass}
88
+ assert_equal(true, conditional_tree_builder.test_execute_threshold(1))
89
+ assert_equal(true, conditional_tree_builder.test_execute_pattern_base())
90
+
91
+ #One element
92
+ assert_nothing_raised { conditional_tree_builder = FpGrowth::Miner::ConditionalTreeBuilder.new(@tableau_pattern_one_element , 1) }
93
+ assert_nothing_raised { conditional_tree_builder.first_pass}
94
+ assert_equal(true, conditional_tree_builder.test_execute_threshold(1))
95
+ assert_equal(true, conditional_tree_builder.test_execute_pattern_base(@tableau_pattern_one_element))
96
+
97
+ #Three element
98
+ assert_nothing_raised { conditional_tree_builder = FpGrowth::Miner::ConditionalTreeBuilder.new(@tableau_pattern , 1) }
99
+ assert_nothing_raised { conditional_tree_builder.first_pass}
100
+ assert_equal(true, conditional_tree_builder.test_execute_threshold(1))
101
+ assert_equal(true, conditional_tree_builder.test_execute_pattern_base(@tableau_pattern))
63
102
 
64
103
  end
65
104
 
@@ -83,6 +122,33 @@ class TestConditionalTreeBuilder < Test::Unit::TestCase
83
122
 
84
123
  def test_pruning
85
124
 
125
+ conditional_tree_builder = nil
126
+ support_prining = nil
127
+
128
+ assert_nothing_raised { conditional_tree_builder = FpGrowth::Miner::ConditionalTreeBuilder.new() }
129
+ # with arguments
130
+ supports = @supports_exemple
131
+ pattern_base = @tableau_pattern_one_element
132
+ supports_pruning = nil
133
+ assert_nothing_raised {supports_pruning = conditional_tree_builder.pruning(pattern_base , supports , 1)}
134
+
135
+ # There must be no pruning, considering the very few element there is
136
+ assert_equal(3, supports_pruning.size, "Supports : "+@support_non_random.to_s)
137
+
138
+ # with arguments
139
+ supports = @supports_exemple
140
+ pattern_base = @tableau_pattern_one_element
141
+ supports_pruning = nil
142
+ assert_nothing_raised {supports_pruning = conditional_tree_builder.pruning(pattern_base , supports , 33)}
143
+
144
+ assert_equal(2, supports_pruning.size, "Supports : "+@support_non_random.to_s)
145
+ assert_equal(5, supports_pruning['b'])
146
+ assert_equal(4, supports_pruning['c'])
147
+
148
+ supports = @supports_exemple_for_pruning
149
+ pattern_base = @tableau_pattern
150
+ supports_pruning = conditional_tree_builder.pruning(pattern_base , supports , 1 )
151
+ assert_equal(4, supports_pruning['a'])
86
152
  end
87
153
 
88
154
  def test_sort
@@ -118,18 +184,23 @@ class TestConditionalTreeBuilder < Test::Unit::TestCase
118
184
  assert_equal(['b', 'a'], pattern_base.content, "en plus, #{fp_tree.supports.to_s}")
119
185
  assert_equal(2, pattern_base.support)
120
186
 
121
- #Two element
187
+ #Three element
122
188
  assert_nothing_raised { pattern_base = conditional_tree_builder.sort_by_support(FpGrowth::Miner::Pattern.new(['a','b','c'],2), fp_tree) }
123
189
 
124
190
  assert_equal(['b', 'c', 'a'], pattern_base.content, "en plus, #{fp_tree.supports.to_s}")
125
191
  assert_equal(2, pattern_base.support)
126
-
127
-
128
192
  end
129
193
 
130
194
  def test_traverse
195
+ # Test unit impossible
196
+ end
131
197
 
198
+ def test_fork_pattern
199
+ # Test unit impossible (like traverse )
132
200
  end
133
201
 
202
+ def test_continue_pattern
203
+ # Test unit impossible (like traverse )
204
+ end
134
205
 
135
206
  end
@@ -23,32 +23,32 @@ class TestFpTree < Test::Unit::TestCase
23
23
 
24
24
  fp_tree = nil
25
25
  # no arguments
26
- assert_nothing_raised {fp_tree = FpGrowth::FpTree::FpTree.new()}
27
- assert_not_nil( fp_tree.root )
28
- assert_instance_of( FpGrowth::FpTree::Node , fp_tree.root )
29
- assert_instance_of( Hash , fp_tree.heads )
30
- assert_equal( {} , fp_tree.supports )
26
+ assert_nothing_raised { fp_tree = FpGrowth::FpTree::FpTree.new() }
27
+ assert_not_nil(fp_tree.root)
28
+ assert_instance_of(FpGrowth::FpTree::Node, fp_tree.root)
29
+ assert_instance_of(Hash, fp_tree.heads)
30
+ assert_equal({}, fp_tree.supports)
31
31
 
32
32
  # list empty
33
- assert_nothing_raised {fp_tree = FpGrowth::FpTree::FpTree.new({})}
34
- assert_not_nil( fp_tree.root )
35
- assert_instance_of( FpGrowth::FpTree::Node , fp_tree.root )
36
- assert_instance_of( Hash , fp_tree.heads )
37
- assert_equal( {} , fp_tree.supports )
33
+ assert_nothing_raised { fp_tree = FpGrowth::FpTree::FpTree.new({}) }
34
+ assert_not_nil(fp_tree.root)
35
+ assert_instance_of(FpGrowth::FpTree::Node, fp_tree.root)
36
+ assert_instance_of(Hash, fp_tree.heads)
37
+ assert_equal({}, fp_tree.supports)
38
38
 
39
39
 
40
40
  # list with arguments
41
- support = { 'a' => 1, 'b' => 2}
42
- assert_nothing_raised {fp_tree = FpGrowth::FpTree::FpTree.new(support)}
43
- assert_not_nil( fp_tree.root )
44
- assert_instance_of( FpGrowth::FpTree::Node , fp_tree.root )
45
- assert_instance_of( Hash , fp_tree.heads )
46
- assert( fp_tree.heads.has_key?('a') , "a n'existe pas")
47
- assert( fp_tree.heads.has_key?('b') ,"b n'existe pas !" )
48
- assert_equal( 2 , fp_tree.heads.length )
49
- assert( fp_tree.supports.has_key?('a') , "a n'existe pas")
50
- assert( fp_tree.supports.has_key?('b') ,"b n'existe pas !" )
51
- assert_equal( 2 , fp_tree.supports.length )
41
+ support = {'a' => 1, 'b' => 2}
42
+ assert_nothing_raised { fp_tree = FpGrowth::FpTree::FpTree.new(support) }
43
+ assert_not_nil(fp_tree.root)
44
+ assert_instance_of(FpGrowth::FpTree::Node, fp_tree.root)
45
+ assert_instance_of(Hash, fp_tree.heads)
46
+ assert(fp_tree.heads.has_key?('a'), "a n'existe pas")
47
+ assert(fp_tree.heads.has_key?('b'), "b n'existe pas !")
48
+ assert_equal(2, fp_tree.heads.length)
49
+ assert(fp_tree.supports.has_key?('a'), "a n'existe pas")
50
+ assert(fp_tree.supports.has_key?('b'), "b n'existe pas !")
51
+ assert_equal(2, fp_tree.supports.length)
52
52
 
53
53
  end
54
54
 
@@ -58,16 +58,16 @@ class TestFpTree < Test::Unit::TestCase
58
58
  # look up with fp_tree nul
59
59
  fp_tree = nil
60
60
  # no arguments
61
- assert_nothing_raised {fp_tree = FpGrowth::FpTree::FpTree.new()}
61
+ assert_nothing_raised { fp_tree = FpGrowth::FpTree::FpTree.new() }
62
62
  lookup = fp_tree.item_order_lookup
63
- assert_equal( {} , lookup )
63
+ assert_equal({}, lookup)
64
64
 
65
65
  # look up with fp_tree non null
66
- support = { 'a' => 1, 'b' => 2}
67
- assert_nothing_raised {fp_tree = FpGrowth::FpTree::FpTree.new(support)}
66
+ support = {'a' => 1, 'b' => 2}
67
+ assert_nothing_raised { fp_tree = FpGrowth::FpTree::FpTree.new(support) }
68
68
  lookup = fp_tree.item_order_lookup
69
- assert_equal( 0 , lookup['a'] )
70
- assert_equal( 1 , lookup['b'] )
69
+ assert_equal(0, lookup['a'])
70
+ assert_equal(1, lookup['b'])
71
71
 
72
72
  end
73
73
 
@@ -145,7 +145,7 @@ class TestFpTree < Test::Unit::TestCase
145
145
  def test_single_path
146
146
  child = FpGrowth::FpTree::Node.new('a')
147
147
 
148
- fptree = FpGrowth::FpTree.build([ ['b','a']*3])
148
+ fptree = FpGrowth::FpTree.build([['b', 'a']*3])
149
149
 
150
150
  assert_equal(true, fptree.single_path?)
151
151
 
@@ -156,13 +156,64 @@ class TestFpTree < Test::Unit::TestCase
156
156
  end
157
157
 
158
158
  def test_combination
159
+
159
160
  fp_tree = FpGrowth::FpTree.build([['a', 'b'], ['b'], ['b', 'c', 'a'], ['a', 'b']], 0)
160
161
  assert_equal(true, fp_tree.single_path?)
161
162
  power_set = nil
162
163
  assert_nothing_raised { power_set = fp_tree.combinations }
163
164
 
164
- fail("ToDo")
165
+ assert_not_nil(power_set)
166
+ assert_not_nil(power_set[1])
167
+ assert_not_nil(power_set[2])
168
+ assert_not_nil(power_set[3])
169
+ assert_not_nil(power_set[4])
170
+
171
+ end
172
+
173
+ def test_remove_from_lateral
174
+ fp_tree = FpGrowth::FpTree.build([['a', 'b'], ['b'], ['b', 'c'], ['a', 'b'], ['a', 'b', 'c']], 0)
175
+
176
+ assert_equal(false, fp_tree.has_lateral_cycle?)
177
+ fp_tree.remove_from_lateral(fp_tree.heads['c'].lateral)
178
+ assert_equal(false, fp_tree.has_lateral_cycle?)
179
+ assert_nil(fp_tree.heads['c'].lateral)
180
+
181
+
182
+ fp_tree = FpGrowth::FpTree.build([['a', 'b'], ['b'], ['b', 'c'], ['a', 'b'], ['a', 'b', 'c']], 0)
183
+ lebon = fp_tree.heads['c'].lateral
184
+
185
+ assert_equal(false, fp_tree.has_lateral_cycle?)
186
+ fp_tree.remove_from_lateral(fp_tree.heads['c'])
187
+ assert_equal(false, fp_tree.has_lateral_cycle?)
188
+ assert_nil(fp_tree.heads['c'].lateral)
189
+ assert_same(lebon,fp_tree.heads['c'])
190
+
191
+ fp_tree = FpGrowth::FpTree.build([['a', 'b'], ['b'], ['b', 'c'], ['a', 'b'], ['a', 'b', 'c']], 0)
192
+ fp_tree.graphviz
193
+ fp_tree.heads['c'].lateral.lateral = FpGrowth::FpTree::Node.new('c')
194
+ apres = fp_tree.heads['c'].lateral.lateral
195
+ avant = fp_tree.heads['c']
196
+
197
+ assert_equal(false, fp_tree.has_lateral_cycle?)
198
+
199
+ fp_tree.remove_from_lateral(fp_tree.heads['c'].lateral)
200
+
201
+ assert_equal(false, fp_tree.has_lateral_cycle?)
202
+ assert_nil(fp_tree.heads['c'].lateral.lateral)
203
+ assert_same(apres,fp_tree.heads['c'].lateral)
204
+ assert_same(avant,fp_tree.heads['c'])
205
+
206
+
207
+ end
208
+
209
+ def test_clone
210
+ fp_tree = FpGrowth::FpTree.build([['a', 'b'], ['b'], ['b', 'c'], ['a', 'b'], ['a', 'b', 'c']], 0)
211
+
212
+ # fail("ToDo")
213
+ end
165
214
 
215
+ def test_remove
216
+ #fail("ToDo")
166
217
  end
167
218
 
168
219
  end
@@ -12,8 +12,8 @@ class TestMiner < Test::Unit::TestCase
12
12
 
13
13
  conditional_tree = FpGrowth::Miner.build_conditional_tree(fp_tree, 'a')
14
14
 
15
- fp_tree.graphviz()
16
- conditional_tree.graphviz("conditional")
15
+ #fp_tree.graphviz()
16
+ #conditional_tree.graphviz("conditional")
17
17
 
18
18
 
19
19
  assert_equal('b', conditional_tree.root.children.first.item)
@@ -53,8 +53,8 @@ class TestMiner < Test::Unit::TestCase
53
53
  fp_tree = FpGrowth::FpTree.build(@random_transactions, 1)
54
54
  conditional_tree = FpGrowth::Miner.build_conditional_tree(fp_tree, fp_tree.heads.keys[-2])
55
55
 
56
- fp_tree.graphviz()
57
- conditional_tree.graphviz("conditional-#{fp_tree.heads.keys[-2]}")
56
+ #fp_tree.graphviz()
57
+ #conditional_tree.graphviz("conditional-#{fp_tree.heads.keys[-2]}")
58
58
 
59
59
  end
60
60
 
@@ -4,6 +4,9 @@ class TestEnelOpenData < Test::Unit::TestCase
4
4
 
5
5
 
6
6
  def setup
7
+
8
+ puts "Setup Test : Open Data Enel"
9
+
7
10
  @transactions_canada_processi_prodotti = []
8
11
  CSV.foreach("test/enel/3_canada_i_processi_e_i_prodotti_1.csv", {:headers => true, :header_converters => :symbol, :header_converters => :symbol, :converters => :all, :col_sep => "\t"}) do |row|
9
12
  @transactions_canada_processi_prodotti << row.to_a
@@ -32,19 +35,52 @@ class TestEnelOpenData < Test::Unit::TestCase
32
35
 
33
36
  def test_canada_processi_prodotti
34
37
 
38
+
39
+ total_item = 0
40
+ min = @transactions_canada_processi_prodotti[0].size
41
+ max = 0
42
+ @transactions_canada_processi_prodotti.each { |transaction|
43
+ total_item += transaction.size
44
+ min = transaction.size if transaction.size < min
45
+ max = transaction.size if transaction.size > max
46
+ }
47
+ average = total_item / @transactions_canada_processi_prodotti.size
48
+
49
+ puts "Test Canada Processi Prodotti"
50
+ puts "Extracted #{@transactions_canada_processi_prodotti.size} transactions"
51
+ puts "With a total of #{total_item} items"
52
+ puts "min:#{min} avg:#{average} max:#{max} items/sets"
53
+ transactions_canada_processi_prodotti1 = @transactions_canada_processi_prodotti.clone
54
+ transactions_canada_processi_prodotti2 = @transactions_canada_processi_prodotti.clone
55
+
35
56
  start = Time.now
36
- fp_tree = FpGrowth::FpTree.build(@transactions_canada_processi_prodotti, 1)
57
+ fp_tree = FpGrowth::FpTree.build(transactions_canada_processi_prodotti1, 1)
58
+
37
59
  loop = Time.now
38
- puts "Tree built in #{loop - start}"
60
+ puts "Tree built of size #{fp_tree.size} in #{loop - start}"
39
61
 
40
62
  patterns = FpGrowth::Miner.fp_growth(fp_tree)
41
63
 
42
64
  finish = Time.now
43
- puts "Tree Mined in #{finish -start}"
65
+ puts "Tree Mined in #{finish - loop}"
44
66
 
45
67
  patterns.sort! { |a, b| a.support <=> b.support }.reverse!
46
68
 
47
- #patterns.each { |pattern| puts "<#{pattern.content}:#{pattern.support}>"}
69
+ assert_not_equal(0, patterns.size)
70
+
71
+ start_td = Time.now
72
+ fp_tree_td = FpGrowth::FpTree.build(transactions_canada_processi_prodotti2, 1)
73
+
74
+ loop_td = Time.now
75
+ puts "Tree built of size #{fp_tree.size} in #{loop_td - start_td}"
76
+
77
+ patterns_td = FpGrowth::Miner.td_fp_growth(fp_tree_td)
78
+
79
+ finish_td = Time.now
80
+
81
+ puts "Tree built in #{loop_td - start_td} TDMined in #{finish_td - loop_td}"
82
+
83
+ puts "Found #{patterns_td.size} rather than #{patterns.size} with a DeltaTime of #{finish_td - start_td - (finish - start)} it's a #{-(finish_td - start_td - (finish - start)) / (finish - start) * 100}% speedup"
48
84
 
49
85
 
50
86
  assert_not_equal(0, patterns.size)
@@ -53,20 +89,51 @@ class TestEnelOpenData < Test::Unit::TestCase
53
89
 
54
90
  def test_canada_produzione_impianti_termoelecttrici
55
91
 
92
+
93
+ total_item = 0
94
+ min = @transactions_produzione_impianti_termoelecttrici[0].size
95
+ max = 0
96
+ @transactions_produzione_impianti_termoelecttrici.each { |transaction|
97
+ total_item += transaction.size
98
+ min = transaction.size if transaction.size < min
99
+ max = transaction.size if transaction.size > max
100
+ }
101
+ average = total_item / @transactions_produzione_impianti_termoelecttrici.size
102
+
103
+ puts "Test Canada Produzione Impianti Termoelecttrici"
104
+ puts "Extracted #{@transactions_produzione_impianti_termoelecttrici.size} transactions"
105
+ puts "With a total of #{total_item} items"
106
+ puts "min:#{min} avg:#{average} max:#{max} items/sets"
107
+ transactions_produzione_impianti_termoelecttrici1 = @transactions_produzione_impianti_termoelecttrici.clone
108
+ transactions_produzione_impianti_termoelecttrici2 = @transactions_produzione_impianti_termoelecttrici.clone
109
+
56
110
  start = Time.now
57
- fp_tree = FpGrowth::FpTree.build(@transactions_produzione_impianti_termoelecttrici, 1)
111
+ fp_tree = FpGrowth::FpTree.build(transactions_produzione_impianti_termoelecttrici1, 1)
58
112
  loop = Time.now
59
- puts "Tree built in #{loop - start}"
113
+ puts "Tree built of size #{fp_tree.size} in #{loop - start}"
60
114
  patterns = FpGrowth::Miner.fp_growth(fp_tree)
61
115
  finish = Time.now
62
- puts "Tree Mined in #{finish - start}"
116
+ puts "Tree Mined in #{finish - loop}"
117
+
118
+ assert_not_equal(0, patterns.size)
119
+
120
+ start_td = Time.now
121
+ fp_tree_td = FpGrowth::FpTree.build(transactions_produzione_impianti_termoelecttrici2, 1)
122
+
123
+ loop_td = Time.now
124
+ puts "Tree built of size #{fp_tree_td.size} in #{loop_td - start_td}"
125
+
126
+ patterns_td = FpGrowth::Miner.td_fp_growth(fp_tree_td)
127
+
128
+ finish_td = Time.now
129
+
130
+ puts "Tree built in #{loop_td - start_td} TDMined in #{finish_td - loop_td}"
131
+
132
+ puts "Found #{patterns_td.size} rather than #{patterns.size} with a DeltaTime of #{finish_td - start_td - (finish - start)} it's a #{-(finish_td - start_td - (finish - start)) / (finish - start) * 100}% speedup"
63
133
 
64
- patterns.sort! { |a, b| a.support <=> b.support }
65
- patterns.sort! { |a, b| a.content.length <=> b.content.length }
66
134
 
67
135
  assert_not_equal(0, patterns.size)
68
136
 
69
- #patterns.each { |pattern| puts "<#{pattern.content}:#{pattern.support}>" if pattern.support > 1}
70
137
 
71
138
  end
72
139