לרובי אין בעיה להפריד מחרוזת למילים עם אותה פקודת split שאנחנו מכירים גם משפות אחרות:
3.3.5 :027 > "hello world".split
=> ["hello", "world"]
אבל כשיוצאים מעולם הדוגמאות הקטנות הקוד הזה מתחיל לאכזב. הנה עוד ניסיון עם משפט יותר מאתגר:
3.3.5 :029 > "Don't. go. there".split
=> ["Don't.", "go.", "there"]
שהיה עובד הרבה יותר טוב בלי הנקודות שצמודות למילים. דרך אחת לסדר את זה היא לחפש "התאמות" במקום "הפרדות" ובשביל זה יש לנו את scan:
3.3.5 :032 > "Don't. go. there".scan(/\p{L}+(?:'\p{L}+)*/u)
=> ["Don't", "go", "there"]
ל scan אני נותן ביטוי רגולארי, במקרה שלנו ביטוי רגולארי שמחפש אותיות, אחרי זה אולי גרש (בגלל מילים כמו Don't) ואז עוד אותיות. זה עוזר כדי לקבל רק את המילה, אבל עכשיו מה אם אנחנו רוצים גם את האינדקס של כל מופע? אי אפשר פשוט לחפש את האינדקס אחרי שכבר מצאנו את ההתאמה כי לפעמים יש מילים כפולות:
3.3.5 :036 > text = "Don't means Don't - Don't press the button"
=> "Don't means Don't - Don't press the button"
3.3.5 :037 > third_word = text.scan(/\p{L}+(?:'\p{L}+)*/u)[2]
=> "Don't"
3.3.5 :038 > index_of_third_word = text.index(third_word)
=> 0
זה החזיר 0 כי המילה השלישית זהה למילה הראשונה.
דרך אחת שעבדה לי כדי לפתור את זה ברובי היא ליצור Enumerator של ההתאמות לביטוי הרגולארי. מכל התאמה נוכל לקבל את הטקסט וגם את אינדקס ההתחלה שלה, ובגלל שאנחנו ברובי אפשר להוסיף את זה למחלקה String כדי שאפשר יהיה לקרוא לקוד על כל מחרוזת. הנה הקוד:
3.3.5 :039 > class String
3.3.5 :040 > def tokenize
3.3.5 :041 > regex = Regexp.new(/\p{L}+(?:'\p{L}+)*/u)
3.3.5 :042 >
3.3.5 :043 > Enumerator.new do |y|
3.3.5 :044 > pos = 0
3.3.5 :045 > while m = regex.match(self, pos)
3.3.5 :046 > y << m
3.3.5 :047 > pos = m.end(0)
3.3.5 :048 > end
3.3.5 :049 > end
3.3.5 :050 > end
3.3.5 :051 > end
3.3.5 :056 > text.tokenize.to_a
=>
[#<MatchData "Don't">,
#<MatchData "means">,
#<MatchData "Don't">,
#<MatchData "Don't">,
#<MatchData "press">,
#<MatchData "the">,
#<MatchData "button">]
עכשיו בשביל לקבל את כל המילים ואינדקס ההתחלה של כל מילה אני כותב:
3.3.5 :057 > text.tokenize.map { |m| [m.to_s, m.begin(0)] }
=> [["Don't", 0], ["means", 6], ["Don't", 12], ["Don't", 20], ["press", 26], ["the", 32], ["button", 36]]
ובשביל לקבל את אינדקס ההתחלה של המילה השלישית אני כותב:
3.3.5 :060 > text.tokenize.drop(2).first.begin(0)
=> 12