วิธีแก้ปัญหาที่ใช้งานได้จริงสำหรับปัญหานี้จะสะอาดเหมือนความจำเป็นหรือไม่?


10

ฉันมีแบบฝึกหัดใน Python ดังนี้:

  • พหุนามได้รับเป็น tuple ของค่าสัมประสิทธิ์ดังกล่าวว่าอำนาจจะถูกกำหนดโดยดัชนีเช่น: (9,7,5) หมายถึง 9 + 7 * x + 5 * x ^ 2

  • เขียนฟังก์ชันเพื่อคำนวณค่าสำหรับ x ที่กำหนด

ตั้งแต่ฉันเข้าสู่การเขียนโปรแกรมการทำงานเมื่อเร็ว ๆ นี้ฉันเขียน

def evaluate1(poly, x):
  coeff = 0
  power = 1
  return reduce(lambda accu,pair : accu + pair[coeff] * x**pair[power],
                map(lambda x,y:(x,y), poly, range(len(poly))),
                0)

ซึ่งฉันคิดว่าอ่านไม่ได้ดังนั้นฉันจึงเขียน

def evaluate2(poly, x):
  power = 0
  result = 1
  return reduce(lambda accu,coeff : (accu[power]+1, accu[result] + coeff * x**accu[power]),
                poly,
                (0,0)
               )[result]

ซึ่งอย่างน้อยอ่านไม่ได้ดังนั้นฉันเขียน

def evaluate3(poly, x):
  return poly[0]+x*evaluate(poly[1:],x) if len(poly)>0 else 0

ซึ่งอาจมีประสิทธิภาพน้อยกว่า (แก้ไข: ฉันผิด!) เนื่องจากใช้การคูณหลายอย่างแทนการยกกำลังโดยหลักการฉันไม่สนใจการวัดที่นี่ (แก้ไข: ความโง่ของฉัน! การวัดจะชี้ให้เห็นความเข้าใจผิดของฉัน!) และ ยังไม่สามารถอ่านได้ (arguably) เป็นวิธีแก้ซ้ำ:

def evaluate4(poly, x):
  result = 0
  for i in range(0,len(poly)):
      result += poly[i] * x**i
  return result

มีวิธีแก้ปัญหาฟังก์ชั่นบริสุทธิ์ที่สามารถอ่านได้ตามความจำเป็นและใกล้เคียงกับประสิทธิภาพหรือไม่?

เป็นที่ยอมรับการเปลี่ยนแปลงการเป็นตัวแทนจะช่วย แต่สิ่งนี้ได้รับจากการฝึก

สามารถเป็น Haskell หรือ Lisp ได้เช่นกันไม่ใช่แค่ Python


7
จากประสบการณ์ของฉันรหัสการทำงานอย่างหมดจดในแง่ของการไม่ใช้ตัวแปรที่ไม่แน่นอน (ซึ่งหมายถึงการไม่ใช้forลูปเป็นต้น) เป็นเป้าหมายที่ไม่ดีที่จะตั้งเป้าหมายไว้ใน Python ตัวแปรการโยงอีกครั้งอย่างรอบคอบและไม่กลายพันธุ์วัตถุให้ประโยชน์เกือบทั้งหมดแก่คุณและทำให้โค้ดอ่านได้อย่างไม่มีที่สิ้นสุดมากขึ้น เนื่องจากอ็อบเจกต์ number นั้นไม่เปลี่ยนรูปแบบและมันจะ rebinds สองชื่อโลคอลโซลูชัน "จำเป็น" ของคุณจึงทำให้การเขียนโปรแกรมทำงานได้ดีกว่ารหัส Python ที่ "บริสุทธิ์อย่างเคร่งครัด"

2
BTW วิธีการคูณเป็นวิธีของ Horner และมีประสิทธิภาพมากกว่าการยกกำลังในแต่ละขั้นตอนเนื่องจากการยกกำลังนั้นต้องการการคูณที่เหมือนกัน

1
Python ค่อนข้างน่าเกลียดอย่างมากเมื่อคุณใช้lambdaงานเมื่อเทียบกับภาษาที่มีฟังก์ชั่นไวยากรณ์ที่ไม่ระบุชื่อจางลง ส่วนหนึ่งที่อาจก่อให้เกิดลักษณะ "สกปรก"
KChaloux

@Kalhaoux นั่นคือสิ่งที่ฉันจะพูด การสนับสนุนการเขียนโปรแกรมเชิงฟังก์ชั่นนั้นค่อนข้างจะเป็นความคิดใน Python ในหลาย ๆ แง่มุมและเป็นการแสดง ถึงอย่างนั้นฉันก็ไม่คิดว่าแม้แต่รุ่นแรกก็อ่านไม่ออกอย่างน่ากลัวจนคุณไม่สามารถเข้าใจได้ว่าเกิดอะไรขึ้น
Evicatos

ฉันสับสนด้วยรหัสของคุณจริง ๆ ในขณะที่ขอบเขตปัญหามีสมการทางคณิตศาสตร์ที่ชัดเจนมากทำไมคุณไม่ใช้สมการทางคณิตศาสตร์คำต่อคำนั้น มันกลายเป็นฟังก์ชั่นที่ให้ภาษาใด ๆ ได้อย่างง่ายดาย ... ไม่แน่ใจว่าคุณต้องการแมปหรือลดหรือย้ำอะไรเมื่อคำถามถามหาฟังก์ชั่นที่ประเมินสมการเดียวและให้สมการนั้น - มันไม่ถาม ทำซ้ำเลย ...
จิมมี่ฮอฟฟา

คำตอบ:


13

วิธีของ Horner น่าจะมีประสิทธิภาพในการคำนวณมากกว่า @delnan ชี้ให้เห็น แต่ฉันจะเรียกสิ่งนี้ว่า Python สำหรับการแก้ปัญหาการยกกำลัง:

def eval_poly(poly, x):
    return sum( [a * x**i for i,a in enumerate(poly)] )

17
วางเครื่องหมายวงเล็บเหลี่ยมและให้ชื่อที่สื่อความหมายกับตัวแปรมากขึ้นและดียิ่งขึ้น: sum(coeff * X**power for power, coeff in enumerate(poly))
Izkata

1
มันทำให้ฉันเศร้าใจว่าคำตอบที่โพสต์อื่นนั้นซับซ้อนมาก ใช้ภาษาเพื่อประโยชน์ของคุณ!
Izkata

ความเข้าใจเป็นเหมือน for-loop "
smuggled

7
@ user1358 ไม่มีก็น้ำตาลประโยคสำหรับองค์ประกอบของและmap filterเราสามารถคิดได้ว่ามันเป็นห่วงของรูปร่างที่เฉพาะเจาะจง แต่ห่วงของรูปร่างนั้นเทียบเท่ากับ combinator funcitonal ดังกล่าวข้างต้น

7

ภาษาที่ใช้งานได้จำนวนมากมีการใช้งาน mapi ที่ช่วยให้คุณมีดัชนีที่ทอผ่านแผนที่ รวมกับผลรวมและคุณมีดังต่อไปนี้ใน F #:

let compute coefficients x = 
    coefficients 
        |> Seq.mapi (fun i c -> c * Math.Pow(x, (float)i))
        |> Seq.sum

2
และถึงแม้ว่าพวกเขาจะทำไม่ได้ตราบใดที่คุณเข้าใจวิธีการmapทำงานมันควรจะค่อนข้างง่ายที่จะเขียนหนึ่งของคุณเอง
KChaloux

4

ฉันไม่เข้าใจว่าโค้ดของคุณเกี่ยวข้องกับขอบเขตปัญหาที่คุณกำหนดไว้อย่างไรดังนั้นฉันจะให้เวอร์ชันของฉันว่าโค้ดของคุณไม่สนใจขอบเขตปัญหา (ตามโค้ดที่จำเป็นที่คุณเขียน)

haskell สามารถอ่านได้สวย (วิธีนี้สามารถแปลเป็นภาษา FP ใด ๆ ที่มีรายการ destructuring และออกมาอย่างบริสุทธิ์และอ่านง่าย):

eval acc exp val [] = acc
eval acc exp val (x:xs) = eval (acc + execPoly) (exp+1) xs
  where execPoly = x * (val^exp)

บางครั้งวิธีการที่เรียบง่ายไร้เดียงสาในฮาเซลเช่นนี้สะอาดกว่าวิธีรัดกุมมากกว่าสำหรับคนที่ไม่คุ้นเคยกับ FP

แนวทางที่ชัดเจนยิ่งขึ้นซึ่งยังคงบริสุทธิ์อยู่คือ:

steval val poly = runST $ do
  accAndExp <- newSTRef (0,1)
  forM_ poly $ \x -> do
    modifySTRef accAndExp (updateAccAndExp x)
  readSTRef accAndExp
  where updateAccAndExp x (acc, exp) = (acc + x*(val^exp), exp + 1)

โบนัสสำหรับแนวทางที่สองกำลังอยู่ใน ST monad มันจะทำงานได้ดีมาก

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


4

หากคุณเพียงแค่มี (คงที่) tuple ทำไมไม่ทำเช่นนี้ (ใน Haskell):

evalPolyTuple (c, b, a) x = c + b*x + a*x^2

หากคุณมีรายการค่าสัมประสิทธิ์คุณสามารถใช้:

evalPolyList coefs x = sum $ zipWith (\c p -> c*x^p) coefs [0..]

หรือลดตามที่คุณมี:

evalPolyList' coefs x = foldl' (\sum (c, p) -> sum + c*x^p) 0 $ zip coefs [0..]

1
มันไม่ใช่การบ้าน! ไม่ต้องพูดถึงว่าฉันได้ทำไปแล้ว 3 ทาง
user1358

ครึ่งหนึ่งของเวลาใน Python (รวมถึงในกรณีนี้) "tuple" หมายถึง "รายการที่ไม่เปลี่ยนรูป" และด้วยเหตุนี้จึงมีความยาวตามอำเภอใจ

เห็นได้ชัด
ว่าความ

1
ไม่ใช่เพราะไพ ธ อน แต่เนื่องจากพหุนามบอกถึงความยาวตามอำเภอใจและขนาดคงที่จะไม่เป็นการออกกำลังกายขนาดใหญ่
user1358

1
@delnan นั่นน่าสนใจ ฉันมักtupleจะหมายถึงชุดค่าคงที่ประเภทที่อาจแตกต่างกันซึ่งไม่สามารถเพิ่มหรือลบออกได้ ฉันไม่เคยเข้าใจจริงๆว่าทำไมภาษาไดนามิกที่มีรายการซึ่งยอมรับอินพุตที่ต่างกันจะต้องการพวกเขา
KChaloux

3

มีชุดของขั้นตอนทั่วไปที่คุณสามารถใช้เพื่อปรับปรุงความสามารถในการอ่านของอัลกอริทึมการทำงาน:

  • ใส่ชื่อในผลลัพธ์ระดับกลางของคุณแทนที่จะพยายามยัดทุกอย่างในบรรทัดเดียว
  • ใช้ฟังก์ชั่นที่มีชื่อแทน lambdas โดยเฉพาะอย่างยิ่งในภาษาที่มีไวยากรณ์ lambda verbose การอ่านevaluateTermแลมบ์ดานั้นง่ายกว่ามาก เพียงเพราะคุณสามารถใช้แลมบ์ดาไม่ได้หมายความว่าคุณควร
  • หากหนึ่งในฟังก์ชั่นที่มีชื่อของคุณดูเหมือนสิ่งที่จะเกิดขึ้นค่อนข้างบ่อยโอกาสที่มันจะอยู่ในไลบรารีมาตรฐานแล้ว มองไปรอบ ๆ. งูหลามของฉันเป็นสนิมเล็ก ๆ น้อย ๆ แต่มันดูเหมือนว่าคุณอัตชีวประวัติพื้นหรือenumeratezipWith
  • บ่อยครั้งที่การเห็นฟังก์ชั่นและผลลัพธ์ระดับกลางทำให้ง่ายขึ้นในการคิดว่าเกิดอะไรขึ้นและทำให้มันง่ายขึ้น ณ จุดนี้มันอาจเหมาะสมที่จะนำแลมบ์ดากลับมาหรือรวมบางบรรทัดเข้าด้วยกัน
  • หากความจำเป็นในการวนซ้ำดูได้ง่ายขึ้นโอกาสในการเข้าใจก็จะใช้ได้ดี
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.