Thursday, February 09, 2006

วันนี้อ่าน Permutation Utilities in Lisp
utility นี้ไว้ช่วยจัดการกับ combination ของ variables
(ถ้าในทาง database ก็เรียกว่า cartesian join)

ยกตัวอย่าง
กรณีเราต้องการ print combination ของ
สี, ขนาด
กำหนดให้สี => red, blue
ขนาด => s, m, l, xl
combination ของกรณีนี้ ก็คือ

red, s
red, m
red, l
red, xl
blue, s
blue, m
blue, l
blue, xl


อ่านเฉยๆ ไม่ลองเขียนก็ไม่ซาบซื้ง
ก็เลยลอง implement ด้วย ruby ดู

Note: ใน Permutation Utilities in Lisp
เขา implement ไว้ 2 แบบ แต่ผมจะลองเขียนแค่แบบเดียว คือแบบ maperm

ขั้นแรกลองแปลงแบบตรงๆก่อน
def maperm (func, *lists)  
case
when lists.empty? :
return nil
when lists.length == 1 :
lists[0].each do |elm|
func.call elm
end
else
lists[0].each do |elm|
wrapper = lambda {|*args|
func.call elm, *args
}

maperm(wrapper, *lists[1..lists.length])
end
end
nil
end

เวลาเรียกใช้ ก็เรียกแบบนี้
maperm(lambda {|a,b| puts "#{a},#{b}"},['red','blue'],['s','m','l','xl'])

หลักๆ ก็คือมันจะ iterate array ตัวแรก
โดยแต่ละ step ก็จะมีการ recursive call เพื่อที่จะ iterate array ตัวถัดไป
หัวใจหลัก ก็คือการ ห่อ (wrap) fีืunc ก่อนที่จะ recursive ไปยัง array ตัวถัดไป

wrapper = lambda {|*args|
func.call elm, *args
}

maperm(wrapper, *lists[1..lists.length])

พอ recursive มาจนถึง array ตัวสุดท้าย
ก็จะเกิดการเรียกใช้ function ที่ถูกห่อมา

when lists.length == 1 :
lists[0].each do |elm|
func.call elm
end


หลังจากลอง run ผ่านแล้ว
ก็เลยแปลง code ให้เป็น ruby มากขึ้น
โดยเปลี่ยนจาก lambda syntax ไปเป็น block syntax แทน

def maperm3(*lists, &blk)
case
when lists.empty?
return nil
when lists.length == 1
lists[0].each { |elm|
yield elm
}
else
lists[0].each { |elm|
maperm3(*lists[1..lists.length]) { |*args|
blk.call elm, *args
}
}
end
nil
end


เวลาเรียกใช้ ก็
maperm3(['red','blue'],['s','m','l','xl']) {|a,b| puts "#{a}, #{b}"}

Related link from Roti

No comments: