• בלוג
  • היום למדתי: המחלקה StringScanner ב Ruby

היום למדתי: המחלקה StringScanner ב Ruby

14/09/2025

מה קורה כשאתם צריכים לסרוק מחרוזת כדי לחפש תבנית שחוזרת על עצמה? בדיוק בשביל זה נולדה המחלקה StringScanner. היא מאפשרת חיפוש איטרטיבי במחרוזת עם קוד טיפול לכל מופע. בואו נראה איך זה עובד דרך תרגיל רובי.

1. האתגר - חיפוש מילים מסומנות

נתון טקסט:

text = 'I can [see] a [mountain] in the [distance]'

בואו נמצא את האינדקס של המילים המסומנות, כלומר נרצה לחזיר את המספרים 2, 4 ו 7 (אינדקסים מתחילים מאפס). בשביל הפשטות אני מטפל כאן בטקסט באנגלית בלבד ולא שובר את הראש עם מילים כמו don't.

2. סריקת כל הסוגריים המרובעים עם StringScanner

נתחיל עם תוכנית ראשונה כדי להבין איך סריקה עם StringScanner עובדת:

require 'strscan'

text = 'I can [see] a [mountain] in the [distance]'
words_without_brackets = text.delete('[]')

scanner = StringScanner.new(text)
while scanner.scan_until(/\[/)
  char_position = scanner.charpos
  puts char_position
end

התוצאה:

7
15
33

בדיוק שלושת האינדקסים של הסוגריים המרובעים הפותחים בטקסט.

3. הפיכת האינדקסים לאינדקסים של מילים

מאיפה נקבל אינדקס התחלה של מילים? נוכל להשתמש שוב ב StringScanner. הפעם אני קורא לו מתוך Enumerator כדי לקבל מערך של כל האינדקסים ומחפש רווחים כי זה מה שמפריד אצלנו בין המילים. בדוגמאות הפשוטות שלנו התו שאחרי רווח מתחיל מילה חדשה:

require 'strscan'

text = 'I can [see] a [mountain] in the [distance]'
words_without_brackets = text.delete('[]')

puts text
puts words_without_brackets

scanner = StringScanner.new(text)
while scanner.scan_until(/\[/)
  char_position = scanner.charpos
  puts char_position
end

word_scanner = StringScanner.new(words_without_brackets)
word_indexes = Enumerator.new do |y|
  y << word_scanner.charpos
  y << word_scanner.charpos while word_scanner.scan_until
end.to_a

pp word_indexes

והפעם אנחנו מקבלים:

I can [see] a [mountain] in the [distance]
I can see a mountain in the distance
7
15
33
[0, 2, 6, 10, 12, 21, 24, 28]

וזה כבר ממש קרוב! עכשיו בשביל למצוא אינדקסים של מילים מסומנות צריך רק לסדר את האינדקסים ולהוריד את מספר הסוגריים המרובעים פותחים וסוגרים לפני כל אחד. נעדכן את הלולאה השניה לכתיב ה Enumerator ונקבל:

require 'strscan'

text = 'I can [see] a [mountain] in the [distance]'
words_without_brackets = text.delete('[]')

puts text
puts words_without_brackets

word_scanner = StringScanner.new(words_without_brackets)
word_indexes = Enumerator.new do |y|
  y << word_scanner.charpos
  y << word_scanner.charpos while word_scanner.scan_until(/ /)
end.to_a

bracket_scanner = StringScanner.new(text)
bracket_indexes = Enumerator.new do |y|
  y << bracket_scanner.charpos while bracket_scanner.scan_until(/\[/)
end

bracket_indexes = bracket_indexes.each_with_index.map do |char_index, count|
  char_index - 2 * count - 1
end

pp word_indexes
pp bracket_indexes

והתוצאה:

I can [see] a [mountain] in the [distance]
I can see a mountain in the distance
[0, 2, 6, 10, 12, 21, 24, 28]
[6, 12, 28]

וזה כבר נראה ממש טוב כי קל להתאים את האינדקסים ולגלות איזה מילים מסומנות. התוכנית הזאת כבר מדפיסה 2,4 ו 7:

require 'strscan'

text = 'I can [see] a [mountain] in the [distance]'
words_without_brackets = text.delete('[]')

puts text
puts words_without_brackets

word_scanner = StringScanner.new(words_without_brackets)
word_indexes = Enumerator.new do |y|
  y << word_scanner.charpos
  y << word_scanner.charpos while word_scanner.scan_until(/ /)
end.to_a

bracket_scanner = StringScanner.new(text)
bracket_indexes = Enumerator.new do |y|
  y << bracket_scanner.charpos while bracket_scanner.scan_until(/\[/)
end

bracket_indexes = bracket_indexes.each_with_index.map do |char_index, count|
  char_index - 2 * count - 1
end

bracket_indexes.each do |i|
  word_index = word_indexes.find_index { |j| j == i }
  puts word_index
end