เริ่มต้นโครงสร้างโครงสร้างต้นไม้


16

หลังจากทำงานกับต้นไม้ 2-3 นิ้วมาสักพักฉันก็รู้สึกประทับใจกับความเร็วในการทำงานส่วนใหญ่ อย่างไรก็ตามปัญหาเดียวที่ฉันพบคือค่าใช้จ่ายขนาดใหญ่ที่เกี่ยวข้องกับการสร้างต้นนิ้วขนาดใหญ่ เนื่องจากการสร้างถูกกำหนดเป็นลำดับของการดำเนินการต่อกันคุณจะต้องสร้างโครงสร้างต้นไม้นิ้วจำนวนมากที่ไม่จำเป็น

ด้วยลักษณะที่ซับซ้อนของต้นไม้ 2-3 นิ้วฉันจึงไม่เห็นวิธีการที่ใช้งานง่ายในการบู๊ตพวกเขาและการค้นหาทั้งหมดของฉันว่างเปล่า ดังนั้นคำถามคือคุณจะไปเกี่ยวกับการบูทต้นไม้ขนาด 2-3 นิ้วโดยมีค่าใช้จ่ายน้อยที่สุดได้อย่างไร

หากต้องการชัดเจน: กำหนดลำดับทราบความยาวnสร้างการแสดงต้นไม้นิ้วของSด้วยการดำเนินการน้อยที่สุดSnS

วิธีที่ไร้เดียงสาที่จะประสบความสำเร็จเป็นสายต่อเนื่องในการดำเนินงานข้อเสีย (ในวรรณคดีของ 'ผู้ประกอบการ) แต่นี้จะสร้างnที่แตกต่างกันโครงสร้างต้นไม้นิ้วที่เป็นตัวแทนของชิ้นทั้งหมดของSสำหรับ[ 1 .. ฉัน ]nS[1 ..ผม]



@Dave ฉันนำไปใช้จริงจากกระดาษของพวกเขาและพวกเขาไม่ได้อยู่ที่การสร้างที่มีประสิทธิภาพ
jbondeson

ฉันคิดได้มาก
Dave Clarke

คุณจะเจาะจงมากขึ้นในสิ่งที่คุณหมายถึงโดย "สร้าง" ในกรณีนี้หรือไม่? นี่เป็นคลี่คลายหรือไม่
jbapple

@jbapple - ฉันแก้ไขให้ชัดเจนยิ่งขึ้นขออภัยในความสับสน
jbondeson

คำตอบ:


16

Data.SequencereplicateO(LGn)

O(1)

O(1)เวลาและสถานที่ สำหรับการสร้างต้นนิ้วคุณมีพื้นที่เพียงพอสำหรับคุณหรือไม่?

หมายเหตุ: การใช้คำว่า "bootstrapping" ของพวกเขาหมายถึงบางสิ่งที่แตกต่างจากการใช้งานของคุณด้านบน พวกเขาหมายถึงการจัดเก็บเป็นส่วนหนึ่งของโครงสร้างข้อมูลโดยใช้รุ่นที่เรียบง่ายกว่าของโครงสร้างเดียวกัน


ความคิดที่น่าสนใจมาก ฉันจะต้องตรวจสอบเรื่องนี้และดูว่าการแลกเปลี่ยนอะไรกับโครงสร้างข้อมูลโดยรวม
jbondeson

ฉันต้องการให้มีสองแนวคิดในคำตอบนี้: (1) ความคิดที่ซ้ำกัน (2) เชื่อมต่อที่เร็วกว่าสำหรับต้นไม้ขนาดเกือบเท่ากัน ฉันคิดว่าแนวคิดการทำซ้ำสามารถสร้างต้นไม้นิ้วในพื้นที่พิเศษน้อยมากถ้าอินพุตเป็นอาร์เรย์
jbapple

ใช่ฉันเห็นทั้งคู่ ขออภัยฉันไม่ได้แสดงความคิดเห็นกับทั้งคู่ ฉันกำลังดูรหัสการทำซ้ำก่อน - แม้ว่าฉันจะขยายความรู้ Haskell ของฉันแน่นอนเท่าที่จะไป ตอนแรกบลัชออนดูเหมือนจะสามารถแก้ไขปัญหาส่วนใหญ่ที่ฉันมีให้ได้หากคุณเข้าถึงแบบสุ่มได้อย่างรวดเร็ว ข้อตกลงที่เร็วอาจเป็นคำตอบทั่วไปได้เล็กน้อยในกรณีที่ไม่มีการเข้าถึงแบบสุ่ม
jbondeson

10

การใช้คำตอบที่ดีของ jbapple เกี่ยวกับการreplicateใช้riffing แทนreplicateA(ซึ่งreplicateสร้างขึ้นบน) แทนที่จะทำตามขั้นตอนต่อไปนี้:

--Unlike fromList, one needs the length explicitly. 
myFromList :: Int -> [b] -> Seq b
myFromList l xs = flip evalState xs $ Seq.replicateA l go
    where go = do
           (y:ys) <- get
            put ys
            return y

myFromList(ในรุ่นเล็กน้อยมีประสิทธิภาพมากขึ้น) เป็นที่เรียบร้อยแล้วกำหนดและใช้ภายในในData.Sequenceการสร้างต้นไม้นิ้วที่มีผลการทุกประเภท

โดยทั่วไปแล้วสัญชาตญาณของreplicateAเรื่องง่าย replicateAถูกสร้างขึ้นที่ด้านบนของฟังก์ชั่นapplicativeTree applicativeTreeรับต้นไม้ขนาดหนึ่งชิ้นmและสร้างต้นไม้ที่มีความสมดุลซึ่งบรรจุnสำเนาของสิ่งนี้ เคสสำหรับnมากถึง 8 ( Deepนิ้วเดียว) นั้นเขียนโค้ดแบบยาก อะไรก็ตามที่อยู่เหนือสิ่งนี้และมันจะเรียกตัวเองซ้ำ ๆ องค์ประกอบ "การใช้งาน" เป็นเพียงการแทรกการสร้างต้นไม้ที่มีเอฟเฟกต์เกลียวผ่านเช่นในกรณีของรหัสข้างต้นรัฐ

goฟังก์ชั่นซึ่งถูกจำลองเป็นเพียงการกระทำที่ได้รับสถานะปัจจุบันปรากฏองค์ประกอบปิดด้านบนไปและแทนที่ส่วนที่เหลือ ในการร้องขอแต่ละครั้งจะมีขั้นตอนเพิ่มเติมในรายการที่ให้ไว้เป็นอินพุต

หมายเหตุที่เป็นรูปธรรมมากขึ้น

main = print (length (show (Seq.fromList [1..10000000::Int])))

ในการทดสอบแบบง่าย ๆ สิ่งนี้ให้ผลการปฏิบัติที่น่าสนใจ หน้าที่หลักข้างต้นวิ่งเกือบ 1/3 ที่ต่ำกว่าด้วย myFromList fromListกว่าด้วย ในทางกลับกันmyFromListใช้ฮีปคงที่ 2MB ในขณะที่มาตรฐานfromListใช้สูงสุด 926MB 926MB นั้นเกิดขึ้นจากต้องเก็บรายชื่อทั้งหมดไว้ในหน่วยความจำพร้อมกัน ในขณะเดียวกันการแก้ปัญหาด้วยmyFromListสามารถที่จะใช้โครงสร้างในแบบสตรีมมิ่งขี้เกียจ ปัญหาเกี่ยวกับความเร็วเป็นผลมาจากความจริงที่ว่าmyFromListจะต้องดำเนินการจัดสรรเป็นสองเท่าโดยประมาณfromList. เราสามารถกำจัดการจัดสรรเหล่านั้นได้ด้วยการย้ายไปที่ Monad ที่เปลี่ยนรูปแบบ CPS แต่ผลลัพธ์นั้นจะทำให้หน่วยความจำมีมากขึ้นในเวลาใดก็ตามเนื่องจากการสูญเสียความขี้เกียจนั้นต้องผ่านรายการในลักษณะที่ไม่ได้สตรีม

ในทางตรงกันข้ามถ้าแทนที่จะบังคับให้ลำดับทั้งหมดด้วยการแสดงฉันจะย้ายไปที่การแยกส่วนหัวหรือองค์ประกอบสุดท้ายmyFromListนำเสนอการชนะที่ยิ่งใหญ่ขึ้นทันที - การแยกองค์ประกอบส่วนหัวนั้นเกือบจะในทันทีและการแยกองค์ประกอบสุดท้ายคือ 0.8s . ในขณะเดียวกันด้วยมาตรฐานการfromListแยกส่วนหัวหรือองค์ประกอบสุดท้ายใช้เวลาประมาณ 2.3 วินาที

นี่คือรายละเอียดทั้งหมดและเป็นผลมาจากความบริสุทธิ์และความเกียจคร้าน ในสถานการณ์ที่มีการกลายพันธุ์และการเข้าถึงแบบสุ่มฉันคิดว่าreplicateวิธีแก้ปัญหานั้นจะดีกว่าอย่างเคร่งครัด

อย่างไรก็ตามมันทำให้เกิดคำถามว่ามีวิธีการเขียนapplicativeTreeเช่นmyFromListนี้ว่ามีประสิทธิภาพมากขึ้นอย่างเคร่งครัด ปัญหาคือฉันคิดว่าการดำเนินการสมัครจะดำเนินการในลำดับที่แตกต่างจากต้นไม้ที่ถูกสำรวจโดยธรรมชาติ แต่ฉันไม่ได้ทำงานอย่างเต็มที่ผ่านวิธีการทำงานนี้หรือหากมีวิธีการแก้ปัญหานี้


4
(1) น่าสนใจ นี้ดูเหมือนวิธีที่ถูกต้องในการทำงานนี้ ฉันประหลาดใจที่ได้ยินว่านี่ช้ากว่าเมื่อบังคับทั้งหมด (2) บางทีคำตอบนี้อาจเป็นรหัสที่หนักเกินไปและขึ้นอยู่กับภาษาสำหรับ cstheory.stackexchange.com มันจะดีถ้าคุณสามารถเพิ่มคำอธิบายวิธีการทำงานในลักษณะที่ไม่ขึ้นกับภาษา fromListreplicateA
Tsuyoshi Ito

9

ในขณะที่คุณไขปลายนิ้วกลางจำนวนมากพวกเขาแบ่งปันโครงสร้างส่วนใหญ่กับอีกฝ่ายหนึ่ง ในท้ายที่สุดคุณจัดสรรหน่วยความจำได้มากถึงสองเท่าของเคสที่เหมาะที่สุดและส่วนที่เหลือจะได้รับการรวบรวมเป็นชุดแรก asymptotics ของสิ่งนี้ดีเท่าที่พวกเขาจะได้รับเนื่องจากคุณต้องใช้ปลายนิ้วที่เต็มไปด้วยค่า n ในท้ายที่สุด

คุณสามารถสร้างปลายนิ้วโดยใช้Data.FingerTree.replicateและพวกเขาใช้FingerTree.fmapWithPosเพื่อค้นหาค่าของคุณในอาร์เรย์ที่มีบทบาทของลำดับที่ จำกัด ของคุณหรือใช้traverseWithPosเพื่อแยกพวกเขาออกจากรายการหรือภาชนะขนาดอื่น ๆ ที่รู้จักกัน

สิ่งนี้จะจัดสรร O(เข้าสู่ระบบn) โหนดสำหรับโครงกระดูกจำลองแบบเริ่มต้นแล้วแทนที่ด้วย O(n) โหนดที่จำเป็นในการเติมโครงกระดูก 'wasting' ที่มากที่สุด O(เข้าสู่ระบบn) หน่วยความจำจนกระทั่งการเก็บขยะทำความสะอาดสิ่งต่าง ๆ ดังนั้นแทนที่จะเป็นโหนที่ดีที่สุด ~ 1,000 โหนดคุณต้องจ่ายสำหรับ ~ 1010 แทนที่จะเป็น 2000 จากการสร้าง cons

ในที่สุดคุณสามารถหลีกเลี่ยงการใช้การทำซ้ำที่และสร้างช่วงเวลานั้น O(เข้าสู่ระบบn)ต้นไม้จำลองหน่วยความจำโดยใช้replicateAแต่ตามที่ @sclv สังเกตว่าสิ่งอันดับสำหรับการจัดการสถานะที่แท้จริงmapAccumLหรือการข้ามกับรัฐ monad ตัวเองจริง ๆ แล้วจะแนะนำค่าใช้จ่ายที่คล้ายกันตามสัดส่วนเพื่อจ่ายสำหรับเซลล์ผลิตภัณฑ์พิเศษทั้งหมด

TL; DR ถ้าฉันต้องทำสิ่งนี้ฉันอาจจะใช้:

rep :: (Int -> a) -> Int -> Seq a 
rep f n = mapWithIndex (const . f) $ replicate n () 

และดัชนีลงในอาร์เรย์ขนาดคงที่ฉันต้องการเพียงแค่จัดหา(arr !)สำหรับfข้างต้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.