หัดเขียนโปรแกรมแบบบ้านๆ ตอนที่ 10 Iterator

 บทความก่อนหน้า


Iterator

คือฟังก์ชันที่ส่งค่าชุดหนึ่งออกมาที่ละส่วนเมื่อมีการเรียกซึ่งสามารถเอาไปใช้ในการวนรอบการทำงานของโปรแกรมได้ แบ่งออกเป็น stateless และ stateful แบบแรกนั้นคือฟังก์ชันที่ไม่มีตัวเก็บสถานะการวนรอบไว้เช่นฟังก์ชัน next(), ipairs(), pairs() ที่เคยกล่าวถึงในตอนก่อนๆ

Stateless Iterator

ตัวอย่างฟังก์ชัน next()
  
  local a = {5,6,7,8}
  
  print(next(a))                        -- 1     5
  print(next(a,nil))                    -- 1     5
  print(next(a,3))                      -- 4     8
  print(next(a,1))                      -- 2     6
  print(next(a))                        -- 1     5
  print(next(a,4)                       -- nil
  
  -- เรียกใช้ในลูป
  for i,v in next,a,nil do
    print(i,v)
  end
  
  -- ผลลัพธ์
  --[[
  1     5
  2     6
  3     7
  4     8
  ]]
  
เราสามารถจำลองการทำงานของฟังก์ชัน ipairs() ได้ดังนี้

  function itor(tbl,control_index)
    local index = control_index+1
    local value = tbl[index]
    if value then
      return index, value
    end
    return nil
  end
  
  function ipairs_less(tbl)
    return itor, tbl, 0
  end
  
  for i,v in ipairs_less(a) do
    print(i,v)
  end
  
  -- ผลลัพธ์
  --[[
  1     5
  2     6
  3     7
  4     8
  ]]
  
  -- ได้ผลเหมือนกับ 
  for i,v in itor,a,0 do
    print(i,v)
  end
  
  -- ผลลัพธ์
  --[[
  1     5
  2     6
  3     7
  4     8
  ]]
  
  -- เรียกใช้ฟังก์ชันโดยตรง
  print(itor(a,0))                      -- 1     5
  print(itor(a,2))                      -- 3     7
  print(itor(a,0))                      -- 1     5
  
เห็นได้ว่าไม่ว่าเราจะเรียก next() หรือ itor() กี่ครั้งด้วยอาร์กิวเมนท์เดิมก็จะได้ค่าเดิมออกมาค่าจะเปลี่ยนตามตัวแปรควบคุมที่ส่งเข้าไปไม่ได้มีการเก็บสถานะไว้ในฟังก์ชัน

Stateful Iterator

จะมีการเก็บสถานะลำดับข้อมูลไว้เราสามารถสร้าง iterator แบบ stateful ได้ด้วยวิธี closure และ coroutine ดังนี้
อย่างที่เรารู้กันในบทก่อนๆ ว่า closure จะเก็บค่าตัวแปร local ในบล๊อกนอกฟังก์ชันเป็น upvalue ซึ่งฟังก์ชันสามารถเข้าถึงได้เราจึงเก็บสถานะของ iterator ไว้ใน upvalue เมื่อมีการเรียกใช้ฟังก์ชันแต่ละครั้งค่าสถานะก็จะเปลี่ยนไปเรื่อยๆ โดยเราไม่ต้องส่งค่าตัวควบคุมไปให้ฟังก์ชัน

  -- แบบ Closure
  function ipairs_ful1(tbl)
    local index = 0                       -- เป็น upvalue ของฟังก์ชันที่บรรทัดถัดไป
    return function()
      index = index + 1
      local value = tbl[index]
      if value then
        return index, value
      end
      return nil
    end
  end
  
  for i,v in ipairs_ful1(a) do
      print(i,v)
  end
  
  -- ผลลัพธ์
  --[[
  1     5
  2     6
  3     7
  4     8
  ]]
  
  -- เรียกใช้ฟังก์ชันโดยตรง
  local itor_cl = ipairs_ful1(a)
  print(itor_cl())                      -- 1     5
  print(itor_cl())                      -- 2     6
  print(itor_cl())                      -- 3     7
  
  -- หรือสามารถเรียกใช้งานแบบนี้ก็ได้ผลเหมือนกัน
  itor_cl = ipairs_ful1(a)
  for i,v in itor_cl do
    print(i,v)
  end
  
  -- ผลลัพธ์
  --[[
  1     5
  2     6
  3     7
  4     8
  ]]
  
ในตอนที่แล้วเรารู้จักกับ coroutine ซึ่งสามารถหยุดการทำงานชั่วคราวและเริ่มทำงานต่อจากที่จุดที่หยุดได้ซึ่งเราสามารถมาประยุกต์ใช้กับ iterator ได้โดยฟังก์ชันจะเก็บสถานะตำแหน่งที่หยุดการทำงานไว้และเริ่มทำงานต่อเมื่อเรียกใช้ฟังก์ชันครั้งต่อไปโดยไม่ต้องอาศัยตัวควบคุมการวนรอบจากภายนอก

  --แบบ Coroutine
  function ipairs_ful2(tbl)
    return coroutine.wrap(function()
      for index=1,#tbl do
        coroutine.yield(index, tbl[index])
      end
    end)
  end
  
  for i,v in ipairs_ful2(a) do
    print(i,v)
  end
  
  -- ผลลัพธ์
  --[[
  1     5
  2     6
  3     7
  4     8
  ]]
  
  -- เรียกใช้ฟังก์ชันโดยตรง
  local itor_co = ipairs_ful2(a)
  print(itor_co())                      -- 1     5
  print(itor_co())                      -- 2     6
  print(itor_co())                      -- 3     7
  
  -- หรือสามารถเรียกใช้งานแบบนี้ก็ได้ผลเหมือนกัน
  itor_co = ipairs_ful2(a)
  for i,v in itor_co do
    print(i,v)
  end
  
  -- ผลลัพธ์
  --[[
  1     5
  2     6
  3     7
  4     8
  ]]
  

การใช้งาน Iterator

  --ตัวอย่าง Iterator แบบต่างๆ
  --หาตัวอักษรจากข้อความ
  function char_iterator(str)
    local i = 0
    return function()
      i = i + 1
      if i <= #str then
        return str:sub(i,i)
      end
    end
  end
  
  for char in char_iterator "Hello" do
    print(char)
  end
  
  -- ผลลัพธ์
  --[[
  H
  e
  l
  l
  o
  ]]
  
  --หาเลขจำนวนเต็มในช่วงที่กำหนด
  function range(start_num,stop_num)
    local start = stop_num and start_num or 1
    local stop = stop_num or start_num
    if start>stop then
      start,stop = stop,start
    end
    return function()
      if start<=stop then
        local num = start
        start = start + 1
        return num
      end
    end
  end
  
  for i in range(5,3) do
    for j in range(2,4) do
      print(i,j)
    end
  end
  
  -- ผลลัพธ์
  --[[
  3     2
  3     3
  3     4
  4     2
  4     3
  4     4
  5     2
  5     3
  5     4
  ]]
  
  for i in range(6) do
    print(i)
  end
  
  -- ผลลัพธ์
  --[[
  1
  2
  3
  4
  5
  6
  ]]
  
  --หารายการในลิสต์
  function fruits()
    local t = {"apple","banana", "orange"}
    local i = 0
    return function()
      i = i + 1
      return t[i]
    end
  end
  
  for fruit in fruits() do
    print(fruit)
  end
  
  -- ผลลัพธ์
  --[[
  apple
  banana
  orange
  ]]
  
ตอนต่อไปมารู้จัก OOP ในแบบ Lua กันครับ

บทความถัดไป

ความคิดเห็น