ตัวแจงนับเป็นหัวใจสำคัญของสิ่งที่ทำให้ Ruby เป็นภาษาที่ทรงพลังและมีพลัง และตัวแจงนับขี้เกียจก็ก้าวไปอีกขั้นโดยช่วยให้คุณทำงานกับคอลเล็กชันขนาดใหญ่มากได้อย่างมีประสิทธิภาพ
ไฟล์ - ปรากฎ - เป็นเพียงชุดของบรรทัดหรืออักขระจำนวนมาก ตัวแจงนับที่เกียจคร้านทำให้สามารถทำสิ่งที่น่าสนใจและทรงพลังกับพวกเขาได้
Enumerator คืออะไร
ทุกครั้งที่คุณใช้วิธีการเช่น each
คุณสร้างตัวแจงนับ นี่คือเหตุผลที่คุณสามารถเชื่อมโยงวิธีการต่างๆ เช่น [1,2,3].map { ... }.reduce { ... }
. คุณสามารถดูสิ่งที่ฉันหมายถึงในตัวอย่างด้านล่าง โทร each
ส่งคืนตัวแจงนับ ซึ่งฉันสามารถใช้เพื่อดำเนินการวนซ้ำอื่น ๆ ได้
# I swiped this code from Ruby's documentation https://ruby-doc.org/core-2.2.0/Enumerator.html
enumerator = %w(one two three).each
puts enumerator.class # => Enumerator
enumerator.each_with_object("foo") do |item, obj|
puts "#{obj}: #{item}"
end
# foo: one
# foo: two
# foo: three
Lazy-enumerators-are-for-large-collections">Lazy-enumerators สำหรับคอลเลกชันขนาดใหญ่
ตัวแจงนับปกติมีปัญหากับคอลเลกชันขนาดใหญ่ เหตุผลก็คือแต่ละวิธีที่คุณเรียกใช้ต้องการทำซ้ำในคอลเล็กชันทั้งหมด คุณสามารถดูสิ่งนี้ได้ด้วยตัวเองโดยเรียกใช้รหัสต่อไปนี้:
# This code will "hang" and you'll have to ctrl-c to exit
(1..Float::INFINITY).reject { |i| i.odd? }.map { |i| i*i }.first(5)
reject
เมธอดจะทำงานตลอดไป เพราะมันไม่สามารถวนซ้ำในคอลเล็กชันที่ไม่มีที่สิ้นสุดได้
แต่ด้วยการเพิ่มเล็กน้อยโค้ดจึงทำงานได้อย่างสมบูรณ์ ถ้าฉันเรียกแค่ว่า lazy
วิธี Ruby ทำสิ่งที่ฉลาดและทำซ้ำได้มากเท่าที่จำเป็นสำหรับการคำนวณ ในกรณีนี้มีเพียง 10 แถว ซึ่งเล็กกว่าอินฟินิตี้อย่างมาก
(1..Float::INFINITY).lazy.reject { |i| i.odd? }.map { |i| i*i }.first(5)
#=> [4, 16, 36, 64, 100]
Moby Dick จำนวนหกพันชุด
หากต้องการทดสอบกลเม็ดไฟล์เหล่านี้ เราจำเป็นต้องมีไฟล์ขนาดใหญ่ เรื่องใหญ่ที่ "ความล้มเหลวในการขี้เกียจ" ใด ๆ จะปรากฏชัด
ฉันดาวน์โหลด Moby Dick จาก Project Gutenberg แล้วสร้างไฟล์ข้อความที่มี 100 สำเนา ที่ไม่ใหญ่พอแม้ว่า ผมเลยเพิ่มเป็น 6,000. นั่นหมายความว่าตอนนี้ฉันอาจเป็นคนเดียวในโลกที่มีไฟล์ข้อความที่มี Moby Dick 6,000 ชุด เป็นการถ่อมตน แต่ฉันพูดเพ้อเจ้อ
ฉันดาวน์โหลด moby dick และทำซ้ำหลายพันครั้งเพื่อให้ได้ไฟล์ขนาดใหญ่ที่จะเล่นด้วย ไวยากรณ์ไม่ได้ทุบตี มันคือเปลือกปลา ฉันคิดว่าเหลือฉันคนเดียวที่ใช้มัน
วิธีรับตัวแจงนับสำหรับไฟล์
นี่คือเคล็ดลับเด็ด ๆ ของ Ruby ที่คุณอาจเคยใช้ แม้ว่าคุณจะไม่รู้ว่าคุณกำลังใช้มันอยู่ก็ตาม เกือบทุกวิธีใน Ruby ที่วนซ้ำในคอลเลกชันจะส่งคืนวัตถุ Enumerator ให้กับคุณ หากคุณเรียกใช้โดยไม่ผ่านบล็อก หมายความว่าอย่างไร
ลองพิจารณาตัวอย่างนี้ ฉันสามารถเปิดไฟล์และใช้แต่ละบรรทัดเพื่อพิมพ์แต่ละบรรทัด แต่ถ้าฉันเรียกมันโดยไม่มีตัวกั้น ฉันจะได้ตัวแจงนับ วิธีการที่น่าสนใจคือ each_line
, each_char
และ each_codepoint
.
File.open("moby.txt") do |f|
# Print out each line in the file
f.each_line do |l|
puts l
end
# Also prints out each line in the file. But it does it
# by calling `each` on the Enumerator returned by `each_line`
f.each_line.each do |l|
puts l
end
end
ตัวอย่างทั้งสองนี้เกือบจะเหมือนกัน แต่ตัวอย่างที่สองถือกุญแจเพื่อปลดล็อก พลังที่น่าอัศจรรย์ .
การใช้ตัวแจงนับของไฟล์
เมื่อคุณมีตัวแจงนับที่ "มี" ทุกบรรทัดในไฟล์แล้ว คุณสามารถแบ่งและแบ่งบรรทัดเหล่านั้นได้เหมือนกับที่คุณทำกับอาร์เรย์ทับทิม นี่เป็นเพียงตัวอย่างบางส่วนเท่านั้น
file.each_line.each_with_index.map { |line, i| "Line #{ i }: #{ line }" }[3, 10]
file.each_line.select { |line| line.size == 9 }.first(10)
file.each_line.reject { |line| line.match /whale/i }
นี่เจ๋งจริงๆ แต่ตัวอย่างเหล่านี้ล้วนมีปัญหาใหญ่เพียงปัญหาเดียว พวกเขาทั้งหมดโหลดไฟล์ทั้งหมดลงในหน่วยความจำก่อนที่จะวนซ้ำ สำหรับไฟล์ที่มี Moby Dick 6,000 สำเนา จะสังเกตเห็นความล่าช้า
ขี้เกียจโหลดบรรทัดของไฟล์
หากเรากำลังสแกนไฟล์ข้อความขนาดใหญ่สำหรับ 10 อินสแตนซ์แรกของคำว่า "วาฬ" ก็ไม่จำเป็นต้องคอยดูแลการเกิดขึ้นครั้งที่ 10 ต่อไป โชคดีที่บอกผู้แจงนับของ Ruby ให้ทำเช่นนี้ได้ง่าย คุณเพียงแค่ใช้คำหลัก "ขี้เกียจ"
ในตัวอย่างด้านล่าง เราใช้ประโยชน์จากการโหลดแบบ Lazy Loading เพื่อทำสิ่งที่ค่อนข้างซับซ้อน
File.open("moby.txt") do |f|
# Get the first 3 lines with the word "whale"
f.each_line.lazy.select { |line| line.match(/whale/i) }.first(3)
# Go back to the beginning of the file.
f.rewind
# Prepend the line number to the first three lines
f.each_line.lazy.each_with_index.map do |line, i|
"LINE #{ i }: #{ line }"
end.first(3)
f.rewind
# Get the first three lines containing "whale" along with their line numbers
f.each_line.lazy.each_with_index.map { |line, i| "LINE #{ i }: #{ line }" }.select { |line| line.match(/whale/i) }.first(3)
end
ไม่ได้มีไว้สำหรับไฟล์เท่านั้น
ซ็อกเก็ต ไปป์ พอร์ตอนุกรม - แสดงใน Ruby โดยใช้คลาส IO นั่นหมายความว่าพวกเขาทั้งหมดมี each_line
, each_char
และ each_codepoint
วิธีการ ดังนั้นคุณสามารถใช้เคล็ดลับนี้กับพวกเขาทั้งหมดได้ สวยเนี๊ยบ!
ไม่ใช่เวทมนตร์
ขออภัย ตัวแจงนับขี้เกียจจะเร่งความเร็วขึ้นหากงานที่คุณพยายามทำให้สำเร็จไม่ต้องการให้อ่านไฟล์ทั้งหมด หากคุณกำลังค้นหาคำที่เกิดขึ้นเฉพาะในหน้าสุดท้ายของหนังสือ คุณต้องอ่านหนังสือทั้งเล่มจึงจะพบ แต่ในกรณีนี้ แนวทางนี้ไม่ควรช้ากว่าแนวทางที่ไม่ระบุจำนวน