multi_string_replace 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 212107102a63c46a717026fbc8c853da01204b73cfda68fa7ff58909d72cade7
4
+ data.tar.gz: 59cafa3a2e9c934e4824a61f87372524b765ba52c4ddadda1283b42f52d4860a
5
+ SHA512:
6
+ metadata.gz: '073753252667508ac0006fe6b6b9d88d5890aaea4a102a94622dcc1e8d12dee9f3760fa1c98ca8ee343b289b474e6e0a89e8cd81379aa618b11d9096154d50e5'
7
+ data.tar.gz: c18f648b5a010e926f8d79b219fc67ccd96dd76fe213003988e265c16279e24d0117678f12526be5c54021065da175101546d5fccc88e3f84a3a2dbeee6f7d15
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.bundle
10
+ *.o
11
+ *.log
12
+ *.so
13
+
14
+ extconf.h
15
+ Makefile
16
+
17
+ # rspec failure tracking
18
+ .rspec_status
19
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ before_install: gem install bundler -v 1.16.2
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at joseph.dayo@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in multi_string_replace.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ multi_string_replace (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ byebug (10.0.2)
10
+ coderay (1.1.2)
11
+ diff-lcs (1.3)
12
+ method_source (0.9.0)
13
+ pry (0.11.3)
14
+ coderay (~> 1.1.0)
15
+ method_source (~> 0.9.0)
16
+ pry-byebug (3.6.0)
17
+ byebug (~> 10.0)
18
+ pry (~> 0.10)
19
+ rake (10.5.0)
20
+ rake-compiler (1.0.5)
21
+ rake
22
+ rspec (3.8.0)
23
+ rspec-core (~> 3.8.0)
24
+ rspec-expectations (~> 3.8.0)
25
+ rspec-mocks (~> 3.8.0)
26
+ rspec-core (3.8.0)
27
+ rspec-support (~> 3.8.0)
28
+ rspec-expectations (3.8.1)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.8.0)
31
+ rspec-mocks (3.8.0)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.8.0)
34
+ rspec-support (3.8.0)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ bundler (~> 1.16)
41
+ multi_string_replace!
42
+ pry-byebug
43
+ rake (~> 10.0)
44
+ rake-compiler
45
+ rspec (~> 3.0)
46
+
47
+ BUNDLED WITH
48
+ 1.16.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Joseph Dayo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # MultiStringReplace
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/multi_string_replace`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'multi_string_replace'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install multi_string_replace
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/multi_string_replace. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the MultiStringReplace project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/multi_string_replace/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rake/extensiontask"
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ Rake::ExtensionTask.new "multi_string_replace" do |ext|
8
+ ext.lib_dir = "lib/multi_string_replace"
9
+ end
10
+
11
+ task :default => :spec
data/bin/benchmark.rb ADDED
@@ -0,0 +1,38 @@
1
+ require "bundler/setup"
2
+ require 'multi_string_replace'
3
+ require 'benchmark'
4
+ require 'pry-byebug'
5
+
6
+ class String
7
+ def mgsub(key_value_pairs=[].freeze)
8
+ regexp_fragments = key_value_pairs.collect { |k,v| k }
9
+ gsub(
10
+ Regexp.union(*regexp_fragments)) do |match|
11
+ key_value_pairs.detect{|k,v| k =~ match}[1]
12
+ end
13
+ end
14
+ end
15
+
16
+ body = File.read(File.join('spec', 'fixtures', 'test.txt'))
17
+
18
+ replace = {
19
+ 'Lorem' => 'XXXX',
20
+ 'ipsum' => 'yyyyy',
21
+ 'sapien' => 'zzzzzz',
22
+ 'sed' => 'pppppppp',
23
+ 'Fusce' => 'wwwwwwww',
24
+ 'non' => 'NON',
25
+ 'sit' => 'SIT',
26
+ 'laoreet' => 'lllll',
27
+ 'Cras' => 'uuuuuuuu',
28
+ 'nunc' => 'eeeeeee',
29
+ 'cursus' => '乧乨乩乪乫乬乭乮乯买乱乲乳乴乵乶乷乸乹乺乻乼乽乾乿',
30
+ }
31
+
32
+ File.write('replaced.txt', body.gsub(/(#{replace.keys.join('|')})/, replace))
33
+ File.write('replaced2.txt', MultiStringReplace.replace(body, replace))
34
+
35
+ Benchmark.bmbm do |x|
36
+ x.report "multi gsub" do body.mgsub(replace.map { |k, v| [/#{k}/, v] } ) end
37
+ x.report "MultiStringReplace" do MultiStringReplace.replace(body, replace) end
38
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "multi_string_replace"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,78 @@
1
+ #include "aho_queue.h"
2
+ #include <string.h>
3
+ #include <stdlib.h>
4
+
5
+ void aho_queue_init(struct aho_queue * restrict que)
6
+ {
7
+ memset(que, 0x00, sizeof(struct aho_queue));
8
+ }
9
+
10
+ void aho_queue_destroy(struct aho_queue * restrict que)
11
+ {
12
+ struct aho_queue_node* que_node = NULL;
13
+ while ((que_node = aho_queue_dequeue(que)) != NULL)
14
+ {
15
+ free(que_node);
16
+ }
17
+ }
18
+
19
+ bool aho_queue_enqueue(struct aho_queue * restrict que, struct aho_trie_node* node)
20
+ {
21
+ struct aho_queue_node* que_node;
22
+ que_node = (struct aho_queue_node*) malloc(sizeof(struct aho_queue_node));
23
+
24
+ if (!que_node)
25
+ {
26
+ /* free memory error!! */
27
+ return false;
28
+ }
29
+
30
+ memset(que_node, 0x00, sizeof(struct aho_queue_node));
31
+ que_node->data = node;
32
+
33
+ if (que->count == 0)
34
+ {
35
+ que->rear = que_node;
36
+ que->front = que_node;
37
+ que->count++;
38
+ return true;
39
+ }
40
+
41
+ que_node->prev = que->rear;
42
+ que->rear->next = que_node;
43
+ que->rear = que_node;
44
+ que->count++;
45
+
46
+ return true;
47
+ }
48
+
49
+ inline bool aho_queue_empty(struct aho_queue * restrict que)
50
+ {
51
+ return (que->count == 0);
52
+ }
53
+
54
+ struct aho_queue_node* aho_queue_dequeue(struct aho_queue * restrict que)
55
+ {
56
+ struct aho_queue_node* deque_node;
57
+ struct aho_queue_node* after_last_node;
58
+ if (aho_queue_empty(que) == true)
59
+ {
60
+ return NULL;
61
+ }
62
+
63
+ if (que->count == 1)
64
+ {
65
+ deque_node = que->rear;
66
+ que->front = que->rear = NULL;
67
+ que->count--;
68
+ return deque_node;
69
+ }
70
+
71
+ deque_node = que->rear;
72
+
73
+ after_last_node = que->rear->prev;
74
+ after_last_node->next = NULL;
75
+ que->rear = after_last_node;
76
+ que->count--;
77
+ return deque_node;
78
+ }
@@ -0,0 +1,26 @@
1
+ #pragma once
2
+ #include <stdbool.h>
3
+ #include "aho_trie.h"
4
+
5
+ struct aho_queue_node
6
+ {
7
+ struct aho_queue_node *next, *prev;
8
+ struct aho_trie_node *data;
9
+ };
10
+
11
+ struct aho_queue
12
+ {
13
+ struct aho_queue_node *front;
14
+ struct aho_queue_node *rear;
15
+ unsigned int count;
16
+ };
17
+
18
+
19
+ void aho_queue_init(struct aho_queue * restrict que);
20
+ void aho_queue_destroy(struct aho_queue * restrict que);
21
+
22
+ bool aho_queue_enqueue(struct aho_queue * restrict que, struct aho_trie_node *node);
23
+ struct aho_queue_node* aho_queue_dequeue(struct aho_queue * restrict que);
24
+
25
+ /* inline */
26
+ bool aho_queue_empty(struct aho_queue * restrict que);
@@ -0,0 +1,9 @@
1
+ #pragma once
2
+
3
+ struct aho_text_t
4
+ {
5
+ int id;
6
+ char* text;
7
+ int len;
8
+ struct aho_text_t *prev, *next;
9
+ };
@@ -0,0 +1,312 @@
1
+ #include <string.h>
2
+ #include <stdio.h>
3
+ #include <stdlib.h>
4
+ #include "aho_trie.h"
5
+ #include "aho_text.h"
6
+ #include "aho_queue.h"
7
+
8
+ void __aho_trie_node_init(struct aho_trie_node * restrict node)
9
+ {
10
+ memset(node, 0x00, sizeof(struct aho_trie_node));
11
+ node->text_end = false;
12
+ node->ref_count = 1;
13
+ }
14
+
15
+ void aho_init_trie(struct aho_trie * restrict t)
16
+ {
17
+ memset(t, 0x00, sizeof(struct aho_trie));
18
+ __aho_trie_node_init(&(t->root));
19
+ }
20
+
21
+ void aho_destroy_trie(struct aho_trie * restrict t)
22
+ {
23
+ aho_clean_trie_node(t);
24
+ }
25
+
26
+ bool aho_add_trie_node(struct aho_trie * restrict t, struct aho_text_t * restrict text)
27
+ {
28
+ struct aho_trie_node* travasal_node = &(t->root);
29
+
30
+ for (int text_idx = 0; text_idx < text->len; text_idx++)
31
+ {
32
+ unsigned char node_text = text->text[text_idx];
33
+ bool find_node = false;
34
+ int child_idx = 0;
35
+
36
+ if (travasal_node->child_count == 0)
37
+ {
38
+ /* insert first node to child_list */
39
+ travasal_node->child_list[0] =
40
+ (struct aho_trie_node*) malloc(sizeof(struct aho_trie_node));
41
+ travasal_node->child_count++;
42
+
43
+ __aho_trie_node_init(travasal_node->child_list[0]);
44
+ travasal_node->child_list[0]->text = node_text;
45
+ travasal_node->child_list[0]->parent = travasal_node;
46
+
47
+ travasal_node = travasal_node->child_list[0];
48
+ continue;
49
+ }
50
+
51
+ if (travasal_node->child_count == MAX_AHO_CHILD_NODE)
52
+ {
53
+ return false;
54
+ }
55
+
56
+ for (child_idx=0; child_idx < travasal_node->child_count; child_idx++)
57
+ {
58
+ if (travasal_node->child_list[child_idx]->text == node_text )
59
+ {
60
+ find_node = true;
61
+ break;
62
+ }
63
+ }
64
+
65
+ if (find_node == true)
66
+ {
67
+ travasal_node->child_list[child_idx]->ref_count++;
68
+ travasal_node = travasal_node->child_list[child_idx];
69
+ }
70
+ else
71
+ {
72
+ /* push_back to child_list */
73
+ struct aho_trie_node* child_node = NULL;
74
+
75
+ travasal_node->child_list[travasal_node->child_count] =
76
+ (struct aho_trie_node*) malloc(sizeof(struct aho_trie_node));
77
+
78
+ child_node = travasal_node->child_list[travasal_node->child_count];
79
+ travasal_node->child_count++;
80
+
81
+ __aho_trie_node_init(child_node);
82
+ child_node->text = node_text;
83
+ child_node->parent = travasal_node;
84
+
85
+ travasal_node = child_node;
86
+ }
87
+ }
88
+
89
+ // connect output link
90
+ if (travasal_node)
91
+ {
92
+ travasal_node->text_end = true;
93
+ travasal_node->output_text = text;
94
+ }
95
+ return true;
96
+ }
97
+
98
+ bool __aho_connect_link(struct aho_trie_node* p, struct aho_trie_node* q)
99
+ {
100
+ struct aho_trie_node *pf = NULL;
101
+ int i = 0;
102
+
103
+ /* is root node */
104
+ if (p->parent == NULL)
105
+ {
106
+ q->failure_link = p;
107
+ return true;
108
+ }
109
+
110
+ pf = p->failure_link;
111
+ for (i=0; i < pf->child_count; i++)
112
+ {
113
+ /* check child node of failure link(p) */
114
+ if (pf->child_list[i]->text == q->text )
115
+ {
116
+ /* connect failure link */
117
+ q->failure_link = pf->child_list[i];
118
+
119
+ /* connect output link */
120
+ if (pf->child_list[i]->text_end)
121
+ {
122
+ q->output_link = pf->child_list[i];
123
+ }
124
+ else
125
+ {
126
+ q->output_link = pf->child_list[i]->output_link;
127
+ }
128
+ return true;
129
+ }
130
+ }
131
+ return false;
132
+ }
133
+
134
+ void aho_connect_link(struct aho_trie * restrict t)
135
+ {
136
+ struct aho_queue queue;
137
+ aho_queue_init(&queue);
138
+ aho_queue_enqueue(&queue, &(t->root));
139
+
140
+ /* BFS access
141
+ * connect failure link and output link
142
+ */
143
+ while (true)
144
+ {
145
+ /* p :parent, q : child node */
146
+ struct aho_queue_node *queue_node = NULL;
147
+ struct aho_trie_node *p = NULL;
148
+ struct aho_trie_node *q = NULL;
149
+ int i = 0;
150
+
151
+ queue_node = aho_queue_dequeue(&queue);
152
+ if (queue_node == NULL)
153
+ {
154
+ break;
155
+ }
156
+
157
+ p = queue_node->data;
158
+ free(queue_node);
159
+
160
+ /* get child node list of p */
161
+ for (i=0; i < p->child_count; i++)
162
+ {
163
+ struct aho_trie_node *pf = p;
164
+
165
+ aho_queue_enqueue(&queue, p->child_list[i]);
166
+ q = p->child_list[i];
167
+
168
+ while (__aho_connect_link(pf, q) == false)
169
+ {
170
+ pf = pf->failure_link;
171
+ }
172
+ }
173
+ }
174
+
175
+ aho_queue_destroy(&queue);
176
+ }
177
+
178
+ void aho_clean_trie_node(struct aho_trie * restrict t)
179
+ {
180
+ struct aho_queue queue;
181
+ aho_queue_init(&queue);
182
+ aho_queue_enqueue(&queue, &(t->root));
183
+
184
+ /* BFS */
185
+ while (true)
186
+ {
187
+ struct aho_queue_node *queue_node = NULL;
188
+ struct aho_trie_node *remove_node = NULL;
189
+ int i = 0;
190
+
191
+ queue_node = aho_queue_dequeue(&queue);
192
+ if (queue_node == NULL)
193
+ {
194
+ break;
195
+ }
196
+
197
+ remove_node = queue_node->data;
198
+ free(queue_node);
199
+
200
+ for (i=0; i < remove_node->child_count; i++)
201
+ {
202
+ aho_queue_enqueue(&queue, remove_node->child_list[i]);
203
+ }
204
+
205
+ /* is root node */
206
+ if (remove_node->parent == NULL)
207
+ {
208
+ continue;
209
+ }
210
+
211
+ free(remove_node);
212
+ }
213
+ }
214
+
215
+ bool __aho_find_trie_node(struct aho_trie_node** restrict start, const unsigned char text)
216
+ {
217
+ struct aho_trie_node* search_node = NULL;
218
+ int i = 0;
219
+
220
+ search_node = *start;
221
+ for (i = 0; i < search_node->child_count; i++)
222
+ {
223
+ if (search_node->child_list[i]->text == text)
224
+ {
225
+ /* find it! move to find child node! */
226
+ *start = search_node->child_list[i];
227
+ return true;
228
+ }
229
+ }
230
+
231
+ /* not found */
232
+ return false;
233
+ }
234
+
235
+ struct aho_text_t* aho_find_trie_node(struct aho_trie_node** restrict start, const unsigned char text)
236
+ {
237
+ while (__aho_find_trie_node(start, text) == false)
238
+ {
239
+ /* not found!
240
+ * when root node stop
241
+ */
242
+ if( (*start)->parent == NULL)
243
+ {
244
+ return NULL;
245
+ }
246
+
247
+ /* retry find. move failure link. */
248
+ *start = (*start)->failure_link;
249
+ }
250
+
251
+ /* found node... */
252
+ /* match case1: find text end! */
253
+ if ((*start)->text_end)
254
+ {
255
+ return (*start)->output_text;
256
+ }
257
+
258
+ /* match case2: exist output_link */
259
+ if ((*start)->output_link)
260
+ {
261
+ return (*start)->output_link->output_text;
262
+ }
263
+
264
+ /* keep going */
265
+ return NULL;
266
+ }
267
+
268
+ void aho_print_trie(struct aho_trie * restrict t)
269
+ {
270
+ struct aho_queue queue;
271
+ aho_queue_init(&queue);
272
+ aho_queue_enqueue(&queue, &(t->root));
273
+
274
+ /* BFS */
275
+ while (true)
276
+ {
277
+ struct aho_queue_node *queue_node = NULL;
278
+ struct aho_trie_node *travasal_node = NULL;
279
+ int i = 0;
280
+
281
+ queue_node = aho_queue_dequeue(&queue);
282
+ if (queue_node == NULL)
283
+ {
284
+ break;
285
+ }
286
+
287
+ travasal_node = queue_node->data;
288
+ free(queue_node);
289
+
290
+ for (i=0; i < travasal_node->child_count; i++)
291
+ {
292
+ aho_queue_enqueue(&queue, travasal_node->child_list[i]);
293
+ }
294
+
295
+ /* is root node */
296
+ if(travasal_node->parent == NULL)
297
+ {
298
+ printf("root node %p\n", travasal_node);
299
+ continue;
300
+ }
301
+
302
+ printf("%c (textend:%d) node %p ref %u (parent %p) failure_link(%p) output_link(%p)",
303
+ travasal_node->text, travasal_node->text_end,
304
+ travasal_node, travasal_node->ref_count,
305
+ travasal_node->parent, travasal_node->failure_link,
306
+ travasal_node->output_link);
307
+
308
+ printf("\n");
309
+ }
310
+
311
+ aho_queue_destroy(&queue);
312
+ }
@@ -0,0 +1,41 @@
1
+ #pragma once
2
+ #include <stdbool.h>
3
+ #include <stdbool.h>
4
+
5
+ #define MAX_AHO_CHILD_NODE 256 /* Character 1 byte => 256 */
6
+
7
+ struct aho_trie_node
8
+ {
9
+ unsigned char text;
10
+ unsigned int ref_count;
11
+
12
+ struct aho_trie_node* parent;
13
+ struct aho_trie_node* child_list[MAX_AHO_CHILD_NODE];
14
+ unsigned int child_count;
15
+
16
+ bool text_end;
17
+ struct aho_text_t* output_text; /* when text_end is true */
18
+
19
+ struct aho_trie_node* failure_link;
20
+ struct aho_trie_node* output_link;
21
+ };
22
+
23
+ struct aho_trie
24
+ {
25
+ struct aho_trie_node root;
26
+ };
27
+
28
+ void aho_init_trie(struct aho_trie * restrict t);
29
+ void aho_destroy_trie(struct aho_trie * restrict t);
30
+
31
+ bool aho_add_trie_node(struct aho_trie * restrict t, struct aho_text_t * restrict text);
32
+ void aho_connect_link(struct aho_trie * restrict t);
33
+ void aho_clean_trie_node(struct aho_trie * restrict t);
34
+
35
+ struct aho_text_t* aho_find_trie_node(struct aho_trie_node** restrict start, const unsigned char text);
36
+
37
+ void aho_print_trie(struct aho_trie * restrict t);
38
+
39
+ /* TODO:
40
+ * bool aho_del_trie_node(struct aho_trie* t, struct aho_text_t* text);
41
+ */
@@ -0,0 +1,229 @@
1
+ #include <limits.h>
2
+ #include <string.h>
3
+ #include <stdlib.h>
4
+ #include <stdio.h>
5
+
6
+ #include "ahocorasick.h"
7
+ #include "aho_trie.h"
8
+
9
+ extern void aho_init(struct ahocorasick * restrict aho)
10
+ {
11
+ memset(aho, 0x00, sizeof(struct ahocorasick));
12
+ }
13
+
14
+ void aho_destroy(struct ahocorasick * restrict aho)
15
+ {
16
+ aho_clear_match_text(aho);
17
+ aho_clear_trie(aho);
18
+ }
19
+
20
+ int aho_add_match_text(struct ahocorasick * restrict aho, const char* text, unsigned int len)
21
+ {
22
+ struct aho_text_t* a_text = NULL;
23
+ if (aho->accumulate_text_id == AHO_MAX_TEXT_ID)
24
+ {
25
+ return -1;
26
+ }
27
+
28
+ a_text = (struct aho_text_t*) malloc(sizeof(struct aho_text_t));
29
+ if (!a_text)
30
+ goto lack_free_mem;
31
+
32
+ a_text->text = (char*) malloc(sizeof(char)*len);
33
+ if (!a_text->text)
34
+ goto lack_free_mem;
35
+
36
+ a_text->id = aho->accumulate_text_id++;
37
+ memcpy(a_text->text, text, len);
38
+ a_text->len = len;
39
+ a_text->prev = NULL;
40
+ a_text->next = NULL;
41
+
42
+ if (aho->text_list_head == NULL)
43
+ {
44
+ aho->text_list_head = a_text;
45
+ aho->text_list_tail = a_text;
46
+ aho->text_list_len++;
47
+ return a_text->id;
48
+ }
49
+
50
+ aho->text_list_tail->next = a_text;
51
+ a_text->prev = aho->text_list_tail;
52
+ aho->text_list_tail = a_text;
53
+ aho->text_list_len++;
54
+ return a_text->id;
55
+
56
+ lack_free_mem:
57
+ return -1;
58
+ }
59
+
60
+ bool aho_del_match_text(struct ahocorasick * restrict aho, const int id)
61
+ {
62
+ struct aho_text_t* iter = NULL;
63
+ for (iter = aho->text_list_head; iter != NULL; iter = iter->next)
64
+ {
65
+ /*if (iter->id > id)
66
+ {
67
+ return false;
68
+ }
69
+ */
70
+
71
+ if (iter->id == id)
72
+ {
73
+ if (iter == aho->text_list_head)
74
+ {
75
+ aho->text_list_head = iter->next;
76
+ }
77
+ else if (iter == aho->text_list_tail)
78
+ {
79
+ aho->text_list_tail = iter->prev;
80
+ }
81
+ else
82
+ {
83
+ iter->prev->next = iter->next;
84
+ iter->next->prev = iter->prev;
85
+ }
86
+ free(iter);
87
+ aho->text_list_len--;
88
+ return true;
89
+ }
90
+ }
91
+ return false;
92
+ }
93
+
94
+ void aho_clear_match_text(struct ahocorasick * restrict aho)
95
+ {
96
+ for(int i = 0; i < aho->accumulate_text_id; i++)
97
+ {
98
+ aho_del_match_text(aho, i);
99
+ }
100
+
101
+ // reset id
102
+ aho->accumulate_text_id = 0;
103
+ }
104
+
105
+
106
+ void aho_create_trie(struct ahocorasick * restrict aho)
107
+ {
108
+ struct aho_text_t* iter = NULL;
109
+ aho_init_trie(&(aho->trie));
110
+
111
+ for (iter = aho->text_list_head; iter != NULL; iter = iter->next)
112
+ {
113
+ aho_add_trie_node(&(aho->trie), iter);
114
+ }
115
+
116
+ aho_connect_link(&(aho->trie));
117
+
118
+ /* debugging */
119
+ //aho_print_trie(&(aho->trie));
120
+ }
121
+
122
+ void aho_clear_trie(struct ahocorasick * restrict aho)
123
+ {
124
+ aho_destroy_trie(&aho->trie);
125
+ }
126
+
127
+ unsigned int aho_findtext(struct ahocorasick * restrict aho, const char* data, unsigned long long data_len)
128
+ {
129
+ int i = 0;
130
+ int match_count = 0;
131
+ struct aho_trie_node* travasal_node = NULL;
132
+
133
+ travasal_node = &(aho->trie.root);
134
+
135
+ for (i = 0; i < data_len; i++)
136
+ {
137
+ struct aho_match_t match;
138
+ struct aho_text_t* result;
139
+
140
+ result = aho_find_trie_node(&travasal_node, data[i]);
141
+ if (result == NULL)
142
+ {
143
+ continue;
144
+ }
145
+
146
+ match.id = result->id;
147
+ match.len = result->len;
148
+
149
+ match.pos = i - result->len + 1;
150
+ if (result->len == 1)
151
+ {
152
+ match.pos = i;
153
+ }
154
+
155
+ match_count++;
156
+ if (aho->callback_match)
157
+ {
158
+ aho->callback_match(aho->rb_result_container, aho->callback_arg, &match);
159
+ }
160
+ }
161
+
162
+ return match_count;
163
+ }
164
+
165
+ VALUE aho_replace_text(struct ahocorasick * restrict aho, const char* data, unsigned long long data_len, char *values[], VALUE ruby_values[])
166
+ {
167
+ int i = 0;
168
+ int match_count = 0;
169
+ struct aho_trie_node* travasal_node = NULL;
170
+
171
+ travasal_node = &(aho->trie.root);
172
+ VALUE main_result = rb_str_new("", 0);
173
+
174
+ long last_concat_pos = 0;
175
+
176
+ for (i = 0; i < data_len; i++)
177
+ {
178
+ struct aho_match_t match;
179
+ struct aho_text_t* result;
180
+
181
+ result = aho_find_trie_node(&travasal_node, data[i]);
182
+ if (result == NULL)
183
+ {
184
+ continue;
185
+ }
186
+
187
+ long pos = i - result->len + 1;
188
+ if (result->len == 1)
189
+ {
190
+ pos = i;
191
+ }
192
+
193
+ // concatenate from last_concat_pos
194
+ rb_str_cat(main_result, &data[last_concat_pos], pos - last_concat_pos);
195
+ // concatenate replace
196
+ if (values[result->id] == NULL) {
197
+ VALUE proc_result = rb_funcall(ruby_values[result->id], rb_intern("call"), 0);
198
+ values[result->id] = StringValueCStr(proc_result);
199
+ }
200
+
201
+ rb_str_cat2(main_result, values[result->id]);
202
+ last_concat_pos = i + 1;
203
+ }
204
+
205
+ if (last_concat_pos < data_len - 1) {
206
+ rb_str_cat(main_result, &data[last_concat_pos], data_len - last_concat_pos);
207
+ }
208
+
209
+ return main_result;
210
+ }
211
+
212
+ inline void aho_register_match_callback(VALUE rb_result_container, struct ahocorasick * restrict aho,
213
+ void (*callback_match)(VALUE rb_result_container, void* arg, struct aho_match_t*),
214
+ void *arg)
215
+ {
216
+ aho->callback_arg = arg;
217
+ aho->callback_match = callback_match;
218
+ aho->rb_result_container = rb_result_container;
219
+ }
220
+
221
+ void aho_print_match_text(struct ahocorasick * restrict aho)
222
+ {
223
+ struct aho_text_t* iter = NULL;
224
+ for (iter = aho->text_list_head; iter != NULL; iter = iter->next)
225
+ {
226
+ printf("id:%d text:%s len:%d this:%p prev:%p next:%p\n",
227
+ iter->id, iter->text, iter->len, iter, iter->prev, iter->next);
228
+ }
229
+ }
@@ -0,0 +1,48 @@
1
+ #pragma once
2
+
3
+ #include <stdbool.h>
4
+ #include "aho_trie.h"
5
+ #include "aho_text.h"
6
+ #include "ruby.h"
7
+
8
+ struct aho_match_t
9
+ {
10
+ int id;
11
+ unsigned long long pos;
12
+ int len;
13
+ };
14
+
15
+ struct ahocorasick
16
+ {
17
+ #define AHO_MAX_TEXT_ID INT_MAX
18
+ int accumulate_text_id;
19
+ struct aho_text_t* text_list_head;
20
+ struct aho_text_t* text_list_tail;
21
+ int text_list_len;
22
+
23
+ struct aho_trie trie;
24
+
25
+ void (*callback_match)(VALUE rb_result_container, void* arg, struct aho_match_t*);
26
+ void* callback_arg;
27
+ VALUE rb_result_container;
28
+ };
29
+
30
+ void aho_init(struct ahocorasick * restrict aho);
31
+ void aho_destroy(struct ahocorasick * restrict aho);
32
+
33
+ int aho_add_match_text(struct ahocorasick * restrict aho, const char* text, unsigned int len);
34
+ bool aho_del_match_text(struct ahocorasick * restrict aho, const int id);
35
+ void aho_clear_match_text(struct ahocorasick * restrict aho);
36
+
37
+ void aho_create_trie(struct ahocorasick * restrict aho);
38
+ void aho_clear_trie(struct ahocorasick * restrict aho);
39
+
40
+ unsigned int aho_findtext(struct ahocorasick * restrict aho, const char* data, unsigned long long data_len);
41
+ VALUE aho_replace_text(struct ahocorasick * restrict aho, const char* data, unsigned long long data_len, char *values[], VALUE ruby_values[]);
42
+
43
+ void aho_register_match_callback(VALUE rb_result_container, struct ahocorasick * restrict aho,
44
+ void (*callback_match)(VALUE rb_result_container, void* arg, struct aho_match_t*),
45
+ void *arg);
46
+
47
+ /* for debug */
48
+ void aho_print_match_text(struct ahocorasick * restrict aho);
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+ create_header
3
+ create_makefile 'multi_string_replace/multi_string_replace'
@@ -0,0 +1,94 @@
1
+ #include "ruby.h"
2
+ #include "extconf.h"
3
+ #include <stdio.h>
4
+ #include <string.h>
5
+ #include <pthread.h>
6
+ #include "ahocorasick.h"
7
+
8
+ void callback_match_pos(VALUE rb_result_container, void *arg, struct aho_match_t* m)
9
+ {
10
+
11
+ unsigned int i = m->pos, idx = 0;
12
+
13
+ VALUE key = LONG2NUM(m->id);
14
+ VALUE hash_value = rb_hash_aref(rb_result_container, key);
15
+
16
+ if (NIL_P(hash_value)) {
17
+ hash_value = rb_ary_new();
18
+ rb_hash_aset(rb_result_container, key, hash_value);
19
+ }
20
+ rb_ary_push(hash_value, LONG2NUM(m->pos)) ;
21
+ }
22
+
23
+ VALUE multi_string_match(VALUE self, VALUE body, VALUE keys)
24
+ {
25
+ Check_Type(keys, T_ARRAY);
26
+ int state;
27
+ VALUE result = rb_hash_new();
28
+ struct ahocorasick aho;
29
+ aho_init(&aho);
30
+ char *target = StringValueCStr(body);
31
+ long size = RARRAY_LEN(keys);
32
+
33
+ for(long idx = 0; idx < size; idx++) {
34
+ VALUE entry = rb_ary_entry(keys, idx);
35
+ char *key_text = StringValueCStr(entry);
36
+ aho_add_match_text(&aho, key_text, strlen(key_text));
37
+ }
38
+ aho_create_trie(&aho);
39
+ aho_register_match_callback(result, &aho, callback_match_pos, (void*)target);
40
+ long count = aho_findtext(&aho, target, strlen(target));
41
+ aho_destroy(&aho);
42
+ return result;
43
+ }
44
+
45
+ VALUE multi_string_replace(VALUE self, VALUE body, VALUE replace)
46
+ {
47
+ Check_Type(replace, T_HASH);
48
+ int state;
49
+
50
+ struct ahocorasick aho;
51
+ aho_init(&aho);
52
+
53
+ char *target = StringValuePtr(body);
54
+ VALUE keys = rb_funcall(replace, rb_intern("keys"), 0);
55
+ VALUE replace_values = rb_funcall(replace, rb_intern("values"), 0);
56
+ long size = RARRAY_LEN(keys);
57
+ char *values[size];
58
+ VALUE ruby_val[size];
59
+
60
+ for(long idx = 0; idx < size; idx++) {
61
+ VALUE entry = rb_ary_entry(keys, idx);
62
+ VALUE value = rb_ary_entry(replace_values, idx);
63
+ if (RB_TYPE_P(value, T_STRING)) {
64
+ values[idx] = StringValueCStr(value);
65
+ } else {
66
+ values[idx] = NULL;
67
+ ruby_val[idx] = value;
68
+ }
69
+
70
+ aho_add_match_text(&aho, StringValuePtr(entry), RSTRING_LEN(entry));
71
+ }
72
+
73
+ aho_create_trie(&aho);
74
+
75
+ VALUE result = aho_replace_text(&aho, target, RSTRING_LEN(body), values, ruby_val);
76
+ aho_destroy(&aho);
77
+ return result;
78
+ }
79
+
80
+ void Init_multi_string_replace()
81
+ {
82
+ int state;
83
+ VALUE mod = rb_eval_string_protect("MultiStringReplace", &state);
84
+
85
+ if (state)
86
+ {
87
+ /* handle exception */
88
+ } else {
89
+ rb_define_singleton_method(mod, "match", multi_string_match, 2);
90
+ rb_define_singleton_method(mod, "replace", multi_string_replace, 2);
91
+ }
92
+
93
+ }
94
+
@@ -0,0 +1,3 @@
1
+ module MultiStringReplace
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "multi_string_replace/version"
2
+ require 'pry-byebug'
3
+ require "multi_string_replace/multi_string_replace"
4
+
5
+ module MultiStringReplace
6
+ end
@@ -0,0 +1,41 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "multi_string_replace/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "multi_string_replace"
8
+ spec.version = MultiStringReplace::VERSION
9
+ spec.authors = ["Joseph Dayo"]
10
+ spec.email = ["joseph.dayo@gmail.com"]
11
+
12
+ spec.summary = %q{Efficient O(N) multiword string replace for ruby}
13
+ spec.description = %q{Efficient O(N) multiword string replace for ruby}
14
+ spec.homepage = "https://github.com/jedld"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ # Specify which files should be added to the gem when it is released.
27
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
29
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+ spec.extensions = ["ext/multi_string_replace/extconf.rb"]
35
+
36
+ spec.add_development_dependency "bundler", "~> 1.16"
37
+ spec.add_development_dependency "pry-byebug"
38
+ spec.add_development_dependency "rake", "~> 10.0"
39
+ spec.add_development_dependency "rspec", "~> 3.0"
40
+ spec.add_development_dependency "rake-compiler"
41
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multi_string_replace
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joseph Dayo
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-09-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake-compiler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Efficient O(N) multiword string replace for ruby
84
+ email:
85
+ - joseph.dayo@gmail.com
86
+ executables: []
87
+ extensions:
88
+ - ext/multi_string_replace/extconf.rb
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".travis.yml"
94
+ - CODE_OF_CONDUCT.md
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - bin/benchmark.rb
101
+ - bin/console
102
+ - bin/setup
103
+ - ext/multi_string_replace/aho_queue.c
104
+ - ext/multi_string_replace/aho_queue.h
105
+ - ext/multi_string_replace/aho_text.h
106
+ - ext/multi_string_replace/aho_trie.c
107
+ - ext/multi_string_replace/aho_trie.h
108
+ - ext/multi_string_replace/ahocorasick.c
109
+ - ext/multi_string_replace/ahocorasick.h
110
+ - ext/multi_string_replace/extconf.rb
111
+ - ext/multi_string_replace/multi_string_replace.c
112
+ - lib/multi_string_replace.rb
113
+ - lib/multi_string_replace/version.rb
114
+ - multi_string_replace.gemspec
115
+ homepage: https://github.com/jedld
116
+ licenses:
117
+ - MIT
118
+ metadata:
119
+ allowed_push_host: https://rubygems.org
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.7.7
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Efficient O(N) multiword string replace for ruby
140
+ test_files: []