สำนวนที่เป็นไปได้ของผู้ประกอบการที่ประกอบไปด้วยไตรภาคของ C คืออะไร?


297

ใน C / C ++ (และหลายภาษาของตระกูลนั้น) สำนวนทั่วไปในการประกาศและเริ่มต้นตัวแปรขึ้นอยู่กับเงื่อนไขใช้ตัวดำเนินการเงื่อนไขแบบไตรภาค

int index = val > 0 ? val : -val

Go ไม่มีตัวดำเนินการตามเงื่อนไข เป็นวิธีสำนวนที่สุดในการใช้ชิ้นส่วนของรหัสเดียวกันข้างต้นคืออะไร? ฉันมาถึงวิธีแก้ปัญหาต่อไปนี้ แต่ดูเหมือนว่าค่อนข้างละเอียด

var index int

if val > 0 {
    index = val
} else {
    index = -val
}

มีอะไรที่ดีกว่านี้ไหม?


คุณสามารถเริ่มต้นค่ากับส่วนอื่นและมีเพียงการตรวจสอบสภาพของคุณมีการเปลี่ยนแปลงไม่แน่ใจว่ามันจะดีกว่าครับแม้ว่า
x29a

ถ้า / ควรจะถูกกำจัดไปมาก เราเคยทำสิ่งนี้ตลอดเวลานับจากวันที่ฉันเขียนโปรแกรม BASIC ครั้งแรกเมื่อ 35 ปีที่แล้ว ตัวอย่างของคุณอาจเป็น: int index = -val + 2 * val * (val > 0);
hyc

9
@hyc ตัวอย่างของคุณไม่สามารถอ่านได้ง่ายเหมือนรหัสสำนวนหรือแม้แต่รุ่น C โดยใช้โอเปอร์เรเตอร์ ternary อย่างไรก็ตาม AFAIK ไม่สามารถใช้โซลูชันนี้ใน Go เนื่องจากบูลีนไม่สามารถใช้เป็นค่าตัวเลขได้
Fabien

สงสัยว่าทำไมไม่ไปให้ผู้ประกอบการดังกล่าว?
Eric Wang

@EricWang สองเหตุผล AFAIK: 1- คุณไม่ต้องการและพวกเขาต้องการให้ภาษามีขนาดเล็กที่สุดเท่าที่จะเป็นไปได้ 2- มันมีแนวโน้มที่จะถูกทารุณกรรมเช่นใช้ในการแสดงออกหลายบรรทัดที่ซับซ้อนและนักออกแบบภาษาไม่ชอบ
Fabien

คำตอบ:


245

ตามที่ระบุไว้ (และหวังว่าจะแปลกใจ) การใช้if+elseนั้นเป็นวิธีที่ใช้สำนวนในการทำเงื่อนไขใน Go

นอกเหนือจากการvar+if+elseบล็อกรหัสเต็มเป่าการสะกดนี้ยังใช้บ่อย:

index := val
if val <= 0 {
    index = -val
}

และหากคุณมีบล็อกของรหัสที่ซ้ำซากพอเช่นเทียบเท่าint value = a <= b ? a : bคุณสามารถสร้างฟังก์ชั่นที่จะเก็บมัน:

func min(a, b int) int {
    if a <= b {
        return a
    }
    return b
}

...

value := min(a, b)

คอมไพเลอร์จะอินไลน์ฟังก์ชั่นที่เรียบง่ายเช่นนั้นมันเร็วขึ้นชัดเจนขึ้นและสั้นลง


184
เฮ้พวกดูสิ! ฉันแค่นำผู้ประกอบการternarityไปยัง golangs! play.golang.org/p/ZgLwC_DHm0 ดังนั้นที่มีประสิทธิภาพ!
thwd

28
@tomwilde โซลูชันของคุณดูน่าสนใจ แต่ก็ขาดคุณสมบัติหลักอย่างใดอย่างหนึ่งของผู้ประกอบการที่ประกอบไปด้วย - การประเมินตามเงื่อนไข
Vladimir Matveev

12
@VladimirMatveev ห่อค่าในการปิด;)
nemo

55
c := (map[bool]int{true: a, false: a - 1})[a > b]เป็นตัวอย่างของการทำให้งง IMHO แม้ว่ามันจะทำงาน
Rick-777

34
ถ้าif/elseเป็นวิธีสำนวนแล้วบางที golang อาจจะพิจารณาการให้คำสั่งคืนค่า:if/else x = if a {1} else {0}ไปไม่ได้เป็นเพียงภาษาเดียวที่ใช้วิธีนี้ ตัวอย่างสำคัญคือสกาล่า ดู: alvinalexander.com/scala/scala-ternary-operator-syntax
Max Murphy

81

ไม่ต้องไปไม่มีโอเปอเรเตอร์ประกอบการใช้ if / else ไวยากรณ์เป็นวิธีการใช้สำนวน

ทำไม Go จึงไม่มีโอเปอเรเตอร์?:

ไม่มีการดำเนินการทดสอบแบบไตรภาคใน Go คุณอาจใช้สิ่งต่อไปนี้เพื่อให้ได้ผลลัพธ์เดียวกัน:

if expr {
    n = trueVal
} else {
    n = falseVal
}

เหตุผลที่?:ไม่ได้มาจาก Go คือนักออกแบบของภาษาได้เห็นการดำเนินการที่ใช้บ่อยเกินไปในการสร้างการแสดงออกที่ซับซ้อนอย่างไม่อาจปฏิเสธได้ if-elseรูปแบบแม้จะนานเด็ดชัดเจน ภาษาต้องการเพียงหนึ่งโฟลว์การควบคุมตามเงื่อนไขสร้าง

- คำถามที่พบบ่อย (FAQ) - ภาษาโปรแกรม Go


1
ดังนั้นเพราะสิ่งที่นักออกแบบภาษาได้เห็นพวกเขาละเว้นการซับหนึ่งif-elseบล็อกทั้งหมด? และใครบอกif-elseว่าไม่ได้ถูกทำร้ายในลักษณะเดียวกัน? ฉันไม่ได้โจมตีคุณฉันแค่รู้สึกว่าข้ออ้างของนักออกแบบนั้นไม่ถูกต้องเพียงพอ
Alf Moh

58

สมมติว่าคุณมีนิพจน์ประกอบไปด้วย (ใน C):

int a = test ? 1 : 2;

วิธีการใช้สำนวนใน Go เป็นเพียงแค่ใช้ifบล็อก:

var a int

if test {
  a = 1
} else {
  a = 2
}

อย่างไรก็ตามนั่นอาจไม่ตรงกับความต้องการของคุณ ในกรณีของฉันฉันต้องการการแสดงออกแบบอินไลน์สำหรับแม่แบบการสร้างรหัส

ฉันใช้ฟังก์ชั่นนิรนามที่ประเมินโดยทันที:

a := func() int { if test { return 1 } else { return 2 } }()

สิ่งนี้ทำให้มั่นใจได้ว่าทั้งสองสาขาจะไม่ได้รับการประเมินเช่นกัน


เป็นการดีที่จะรู้ว่ามีเพียงหนึ่งสาขาของฟังก์ชัน inon an ไลน์เท่านั้นที่ได้รับการประเมิน แต่ทราบว่ากรณีเช่นนี้อยู่นอกเหนือขอบเขตของผู้ประกอบการที่สามของ C
Wolf

1
นิพจน์เงื่อนไข C (หรือที่รู้จักกันทั่วไปว่าเป็นผู้ประกอบการที่ประกอบไปด้วยสาม) มีตัวถูกดำเนินการสามตัว: expr1 ? expr2 : expr3. หากexpr1ประเมินtrue, expr2การประเมินและเป็นผลมาจากการแสดงออก มิฉะนั้นexpr3จะได้รับการประเมินและจัดให้เป็นผลลัพธ์ นี่คือจากส่วนภาษาการเขียนโปรแกรม ANSI C 2.11 โดย K&R โซลูชัน My Go รักษาความหมายเฉพาะเหล่านี้ไว้ @ หมาป่าคุณช่วยอธิบายสิ่งที่คุณแนะนำได้ไหม?
Peter Boyer

ฉันไม่แน่ใจว่าสิ่งที่ฉันมีอยู่ในใจบางทีฟังก์ชั่นอานนท์มีขอบเขต (เนมสเปซในพื้นที่) ซึ่งไม่ใช่ตัวดำเนินการ ternary ใน C / C ++ ดูตัวอย่างการใช้ขอบเขตนี้
Wolf

39

ประกอบไปด้วยแผนที่ง่ายต่อการอ่านโดยไม่ต้องใส่วงเล็บ:

c := map[bool]int{true: 1, false: 0} [5 > 4]

ไม่แน่ใจว่าทำไมมันถึงได้ -2 ... ใช่มันเป็นวิธีแก้ปัญหา แต่มันใช้งานได้และปลอดภัยต่อประเภท
Alessandro Santini

30
ใช่มันใช้งานได้มีความปลอดภัยสูงและมีความคิดสร้างสรรค์ อย่างไรก็ตามมีตัวชี้วัดอื่น ๆ Ternary ops เป็น runtime เทียบเท่ากับ if / else (ดูเช่นโพสต์ S / O นี้ ) การตอบสนองนี้ไม่ได้เป็นเพราะ 1) ทั้งสองสาขาจะดำเนินการ 2) สร้างแผนที่ 3) เรียกแฮ ทั้งหมดเหล่านี้เป็น "เร็ว" แต่ไม่เร็วเท่า if / else นอกจากนี้ฉันจะยืนยันว่ามันไม่สามารถอ่านได้มากกว่า var r T หากเงื่อนไข {r = foo ()} else {r = bar ()}
อัศวิน

ในภาษาอื่นฉันใช้วิธีนี้เมื่อฉันมีตัวแปรหลายตัวและมีการปิดหรือฟังก์ชันพอยน์เตอร์หรือการกระโดด การเขียน ifs ที่ซ้อนกันจะเกิดข้อผิดพลาดได้ง่ายเนื่องจากจำนวนของตัวแปรเพิ่มขึ้นขณะที่ {(0,0,0) => {code1}, (0,0,1) => {code2} ... } [(x> 1 , y> 1, z> 1)] (pseudocode) น่าสนใจยิ่งขึ้นเมื่อจำนวนตัวแปรเพิ่มขึ้น การปิดทำให้โมเดลนี้เร็วขึ้น ฉันคาดหวังว่าการแลกเปลี่ยนที่คล้ายกันจะมีผลในระหว่างเดินทาง
Max Murphy

ฉันคิดว่าคุณจะใช้สวิตช์สำหรับรุ่นนั้น ฉันชอบวิธีที่สวิตช์หยุดทำงานโดยอัตโนมัติแม้ว่ามันจะไม่สะดวกก็ตาม
Max Murphy

8
ดังที่Cassy Foesch ชี้ให้เห็น: simple and clear code is better than creative code.
Wolf

11
func Ternary(statement bool, a, b interface{}) interface{} {
    if statement {
        return a
    }
    return b
}

func Abs(n int) int {
    return Ternary(n >= 0, n, -n).(int)
}

สิ่งนี้จะไม่ดีกว่าหาก / และต้องใช้การส่ง แต่ใช้งานได้ FYI:

BenchmarkAbsTernary-8 100000000 18.8 ns / op

BenchmarkAbsIfElse-8 2000000000 0.27 ns / op


นี่คือทางออกที่ดีที่สุดขอแสดงความยินดี! หนึ่งบรรทัดที่จัดการกรณีที่เป็นไปได้ทั้งหมด
Alexandro de Oliveira

2
ฉันไม่คิดว่าสิ่งนี้จัดการกับการประเมินตามเงื่อนไขหรือไม่ ด้วยสาขาที่ไม่มีผลข้างเคียงสิ่งนี้ไม่สำคัญ (เช่นในตัวอย่างของคุณ) แต่ถ้าเป็นสิ่งที่มีผลข้างเคียงคุณจะพบปัญหา
Ashton Wiersdorf

7

หากทุกสาขาของคุณให้ผลข้างเคียงหรือคอมพิวเตอร์ที่มีราคาแพงต่อไปนี้จะความหมายรักษา refactoring:

index := func() int {
    if val > 0 {
        return printPositiveAndReturn(val)
    } else {
        return slowlyReturn(-val)  // or slowlyNegate(val)
    }
}();  # exactly one branch will be evaluated

โดยปกติแล้วจะไม่มีค่าใช้จ่าย (inlined) และที่สำคัญที่สุดคือโดยไม่ต้องยุ่งกับ namespace ของคุณด้วยฟังก์ชั่นตัวช่วยที่ใช้เพียงครั้งเดียวเท่านั้น ตัวอย่างสด

หมายเหตุหากคุณต้องใช้แนวทางของกุสตาโวอย่างไร้เดียงสา:

    index := printPositiveAndReturn(val);
    if val <= 0 {
        index = slowlyReturn(-val);  // or slowlyNegate(val)
    }

คุณจะได้รับโปรแกรมที่มีลักษณะการทำงานที่แตกต่างกัน ; ในกรณีที่val <= 0โปรแกรมจะพิมพ์ค่าที่ไม่เป็นบวกในขณะที่มันไม่ควร! (เหมือนกันถ้าคุณกลับสาขาคุณจะแนะนำค่าใช้จ่ายโดยการเรียกฟังก์ชั่นช้าโดยไม่จำเป็น)


1
อ่านแล้วน่าสนใจ แต่ฉันไม่เข้าใจประเด็นที่คุณวิจารณ์ในแนวทางของกุสตาโว ฉันเห็น (ชนิดของ) absฟังก์ชั่นในรหัสเดิม (ดีผมเปลี่ยน<=ไป<) ในตัวอย่างของคุณฉันเห็นการเริ่มต้นที่ซ้ำซ้อนในบางกรณีและอาจขยายตัว คุณช่วยอธิบายได้ไหม: อธิบายความคิดของคุณอีกเล็กน้อย
Wolf

ข้อแตกต่างที่สำคัญคือการเรียกฟังก์ชั่นที่อยู่นอกสาขาใดสาขาหนึ่งจะทำให้เกิดผลข้างเคียงแม้ว่าจะไม่ได้รับสาขานั้นก็ตาม ในกรณีของฉันจะพิมพ์เฉพาะจำนวนบวกเท่านั้นเนื่องจากฟังก์ชันprintPositiveAndReturnเรียกเฉพาะตัวเลขที่เป็นบวกเท่านั้น ตรงกันข้ามเสมอรันสาขาหนึ่งแล้ว "แก้ไข" ค่าที่มีการดำเนินการเป็นสาขาที่แตกต่างกันจะมีผลข้างเคียงไม่ยกเลิกสาขาแรกของ
eold

ฉันเห็น แต่ประสบการณ์โปรแกรมเมอร์มักตระหนักถึงผลข้างเคียง ในกรณีนี้ฉันต้องการโซลูชันที่ชัดเจนของ Cassy Foeschกับฟังก์ชั่นการฝังตัวแม้ว่าโค้ดที่คอมไพล์อาจเหมือนกัน: มันสั้นกว่าและดูเหมือนชัดเจนกับโปรแกรมเมอร์ส่วนใหญ่ อย่าเข้าใจฉันผิด: ฉันชอบการปิดประตูของ Go จริงๆ)
Wolf

1
" ประสบการณ์โปรแกรมเมอร์มักจะตระหนักถึงผลข้างเคียง " - ไม่การหลีกเลี่ยงการประเมินเงื่อนไขเป็นหนึ่งในคุณสมบัติหลักของผู้ประกอบการที่ประกอบไปด้วยคน
Jonathan Hartley

6

คำนำ:หากปราศจากการโต้เถียงนั่นif elseคือหนทางที่จะไปเรายังคงสามารถเล่นและสนุกไปกับโครงสร้างที่ใช้ภาษาได้

โครงสร้างต่อไปนี้Ifมีอยู่ในgithub.com/icza/goxห้องสมุดของฉันพร้อมด้วยวิธีการอื่นbuiltinx.Ifมากมาย


ไปช่วยให้การแนบวิธีการใด ๆที่ผู้ใช้กำหนดชนิดboolรวมทั้งรูปแบบดั้งเดิมเช่น เราสามารถสร้างประเภทที่กำหนดเองที่มีประเภทพื้นฐานboolของมันแล้วด้วยการแปลงประเภทที่เรียบง่ายกับเงื่อนไขเราสามารถเข้าถึงวิธีการของมัน วิธีการที่รับและเลือกจากตัวถูกดำเนินการ

บางสิ่งเช่นนี้

type If bool

func (c If) Int(a, b int) int {
    if c {
        return a
    }
    return b
}

เราจะใช้มันอย่างไร?

i := If(condition).Int(val1, val2)  // Short variable declaration, i is of type int
     |-----------|  \
   type conversion   \---method call

ตัวอย่างประกอบไปด้วยmax():

i := If(a > b).Int(a, b)

ประกอบไปด้วยabs():

i := If(a >= 0).Int(a, -a)

สิ่งนี้ดูเท่ห์เรียบง่ายสง่างามและมีประสิทธิภาพ (นอกจากนี้ยังมีสิทธิ์ได้รับการอินไลน์ )

ข้อเสียหนึ่งเมื่อเทียบกับผู้ประกอบการที่ "จริง": มันจะประเมินตัวถูกดำเนินการทั้งหมด

เพื่อให้บรรลุถึงการประเมินผลที่เลื่อนออกไปและจำเป็นเพียงอย่างเดียวตัวเลือกเดียวคือการใช้ฟังก์ชั่น ( ฟังก์ชั่นที่ประกาศไว้หรือวิธีการหรือตัวอักษรฟังก์ชั่น ) ซึ่งจะเรียกว่าเมื่อ / หากต้องการ:

func (c If) Fint(fa, fb func() int) int {
    if c {
        return fa()
    }
    return fb()
}

ใช้มัน: สมมติว่าเรามีฟังก์ชั่นเหล่านี้ในการคำนวณaและb:

func calca() int { return 3 }
func calcb() int { return 4 }

แล้ว:

i := If(someCondition).Fint(calca, calcb)

ตัวอย่างเช่นเงื่อนไขเป็นปีปัจจุบัน> 2020:

i := If(time.Now().Year() > 2020).Fint(calca, calcb)

หากเราต้องการใช้ตัวอักษรฟังก์ชั่น:

i := If(time.Now().Year() > 2020).Fint(
    func() int { return 3 },
    func() int { return 4 },
)

หมายเหตุสุดท้าย: หากคุณมีฟังก์ชั่นที่มีลายเซ็นที่แตกต่างกันคุณไม่สามารถใช้งานได้ที่นี่ ในกรณีนี้คุณอาจใช้ฟังก์ชันตามตัวอักษรพร้อมลายเซ็นการจับคู่เพื่อให้ยังคงใช้ได้

ตัวอย่างเช่นหากcalca()และcalcb()จะมีพารามิเตอร์ด้วย (นอกเหนือจากค่าส่งคืน):

func calca2(x int) int { return 3 }
func calcb2(x int) int { return 4 }

นี่คือวิธีที่คุณสามารถใช้:

i := If(time.Now().Year() > 2020).Fint(
    func() int { return calca2(0) },
    func() int { return calcb2(0) },
)

ลองตัวอย่างเหล่านี้บนไปสนามเด็กเล่น


4

คำตอบของ eold นั้นน่าสนใจและสร้างสรรค์บางทีก็ฉลาด

อย่างไรก็ตามขอแนะนำให้ทำแทน:

var index int
if val > 0 {
    index = printPositiveAndReturn(val)
} else {
    index = slowlyReturn(-val)  // or slowlyNegate(val)
}

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

โดยพื้นฐานแล้วโค้ดที่ง่ายและชัดเจนดีกว่าโค้ดโฆษณา

นอกจากนี้รหัสใด ๆ ที่ใช้ตัวอักษรของแผนที่ไม่ใช่ความคิดที่ดีเพราะแผนที่นั้นไม่เบาเลยใน Go ตั้งแต่ Go 1.3 รับประกันการสุ่มซ้ำสำหรับแผนที่ขนาดเล็กและเพื่อบังคับใช้สิ่งนี้จึงได้รับหน่วยความจำที่มีประสิทธิภาพน้อยกว่าสำหรับแผนที่ขนาดเล็ก

ด้วยเหตุนี้การสร้างและลบแผนที่ขนาดเล็กจำนวนมากจึงใช้พื้นที่และใช้เวลานาน ฉันมีชิ้นส่วนของรหัสที่ใช้แผนที่ขนาดเล็ก (สองหรือสามปุ่มมีแนวโน้ม แต่กรณีการใช้งานทั่วไปเป็นเพียงรายการเดียว) แต่รหัสนั้นช้ามาก เรากำลังพูดถึงคำสั่งอย่างน้อย 3 คำสั่งที่ช้ากว่ารหัสเดิมที่เขียนขึ้นใหม่เพื่อใช้คีย์สไลซ์คู่ [ดัชนี] => ข้อมูล [ดัชนี] แผนที่ และมีโอกาสมากขึ้น เนื่องจากการดำเนินการบางอย่างที่ก่อนหน้านี้ใช้เวลาสองสามนาทีในการทำงานจึงเริ่มทำให้เสร็จในเสี้ยววินาที


1
simple and clear code is better than creative code- นี่ฉันชอบมาก แต่ฉันก็สับสนนิดหน่อยในส่วนสุดท้ายหลังจากdog slowนี้อาจทำให้คนอื่นสับสนบ้างไหม?
Wolf

1
ดังนั้นโดยทั่วไป ... ฉันมีรหัสบางอย่างที่สร้างแผนที่ขนาดเล็กที่มีหนึ่งสองหรือสามรายการ แต่รหัสทำงานช้ามาก ดังนั้นมากm := map[string]interface{} { a: 42, b: "stuff" }และในฟังก์ชั่นอื่นวนซ้ำมัน: for key, val := range m { code here } หลังจากเปลี่ยนเป็นระบบสองชิ้น: keys = []string{ "a", "b" }, data = []interface{}{ 42, "stuff" }แล้ววนซ้ำผ่านfor i, key := range keys { val := data[i] ; code here }สิ่งต่าง ๆ เร่ง 1,000 เท่า
Cassy Foesch

ฉันเห็นขอบคุณสำหรับการชี้แจง (บางทีคำตอบอาจจะได้รับการปรับปรุงในจุดนี้)
Wolf

1
-.- ... touché, ตรรกะ ... touché ... ฉันจะไปที่นั้นในที่สุด ... ;)
Cassy Foesch

3

หนึ่งตอร์ปิโดแม้รังเกียจโดยผู้สร้างมีสถานที่ของพวกเขา

อันนี้แก้ปัญหาการประเมินผลที่ขี้เกียจโดยให้คุณผ่านการประเมินฟังก์ชั่นถ้าจำเป็น:

func FullTernary(e bool, a, b interface{}) interface{} {
    if e {
        if reflect.TypeOf(a).Kind() == reflect.Func {
            return a.(func() interface{})()
        }
        return a
    }
    if reflect.TypeOf(b).Kind() == reflect.Func {
        return b.(func() interface{})()
    }
    return b
}

func demo() {
    a := "hello"
    b := func() interface{} { return a + " world" }
    c := func() interface{} { return func() string { return "bye" } }
    fmt.Println(FullTernary(true, a, b).(string)) // cast shown, but not required
    fmt.Println(FullTernary(false, a, b))
    fmt.Println(FullTernary(true, b, a))
    fmt.Println(FullTernary(false, b, a))
    fmt.Println(FullTernary(true, c, nil).(func() string)())
}

เอาท์พุต

hello
hello world
hello world
hello
bye
  • ฟังก์ชั่นที่ส่งผ่านจะต้องส่งคืนinterface{}เพื่อตอบสนองการดำเนินการร่ายภายใน
  • ขึ้นอยู่กับบริบทคุณอาจเลือกที่จะแปลงผลลัพธ์เป็นชนิดเฉพาะ
  • cถ้าคุณอยากจะกลับฟังก์ชั่นจากนี้คุณจะต้องห่อมันเป็นแสดงด้วย

โซลูชันแบบสแตนด์อโลนที่นี่ก็ดี แต่อาจมีความชัดเจนน้อยลงสำหรับการใช้งานบางอย่าง


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