Mengapa x == (x = y) tidak sama dengan (x = y) == x?

207

Perhatikan contoh berikut:

class Quirky {
    public static void main(String[] args) {
        int x = 1;
        int y = 3;

        System.out.println(x == (x = y)); // false
        x = 1; // reset
        System.out.println((x = y) == x); // true
     }
}

Saya tidak yakin apakah ada item dalam Spesifikasi Bahasa Jawa yang menentukan pemuatan nilai variabel sebelumnya untuk perbandingan dengan sisi kanan ( x = y) yang, berdasarkan urutan yang ditunjukkan oleh tanda kurung, harus dihitung terlebih dahulu.

Mengapa ungkapan pertama dievaluasi false, tetapi yang kedua mengevaluasi true? Saya akan diharapkan (x = y)untuk dievaluasi terlebih dahulu, dan kemudian akan membandingkan xdengan dirinya sendiri ( 3) dan kembali true.


Pertanyaan ini berbeda dari urutan evaluasi subekspresi dalam ekspresi Java yang xjelas bukan 'subekspresi' di sini. Perlu dimuat untuk perbandingan daripada harus 'dievaluasi'. Pertanyaannya adalah khusus-Jawa dan ekspresinya x == (x = y), tidak seperti konstruksi tidak praktis yang dibuat-buat yang biasanya dibuat untuk pertanyaan wawancara yang rumit, datang dari proyek nyata. Itu seharusnya menjadi pengganti satu baris untuk idiom bandingkan-dan-ganti

int oldX = x;
x = y;
return oldX == y;

yang, bahkan lebih sederhana dari instruksi x86 CMPXCHG, layak mendapatkan ekspresi yang lebih pendek di Java.

John McClane
sumber
62
Sisi kiri selalu dievaluasi sebelum sisi kanan. Kurung tidak membuat perbedaan untuk itu.
Louis Wasserman
11
Mengevaluasi ekspresi x = ytentu relevan, dan menyebabkan efek samping yang xdiatur ke nilai y.
Louis Wasserman
50
Bantulah diri Anda sendiri dan rekan setim Anda dan jangan campur mutasi negara ke jalur yang sama dengan ujian negara. Melakukannya secara drastis mengurangi pembacaan kode Anda. (Ada beberapa kasus di mana itu benar-benar diperlukan karena persyaratan atomisitas, tetapi fungsi untuk yang sudah ada dan tujuan mereka akan langsung dikenali.)
jpmc26
50
Pertanyaan sebenarnya adalah mengapa Anda ingin menulis kode seperti ini.
klutt
26
Kunci dari pertanyaan Anda adalah keyakinan salah Anda bahwa tanda kurung menyiratkan urutan evaluasi. Itu adalah kepercayaan umum karena bagaimana kita diajarkan matematika di sekolah dasar dan karena beberapa buku pemrograman pemula masih salah , tetapi itu adalah keyakinan yang salah. Ini pertanyaan yang cukup sering. Anda mungkin mendapat manfaat dari membaca artikel saya tentang masalah ini; mereka adalah tentang C # tetapi mereka berlaku untuk Jawa: ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order ericlippert.com/2009/08/10/precedence-vs-order-redux
Eric Lippert

Jawaban:

97

yang, atas perintah yang ditunjukkan oleh tanda kurung, harus dihitung terlebih dahulu

Tidak. Ini adalah kesalahpahaman umum bahwa tanda kurung memiliki efek (umum) pada perhitungan atau urutan evaluasi. Mereka hanya memaksa bagian dari ekspresi Anda ke pohon tertentu, mengikat operan yang tepat ke operasi yang tepat untuk pekerjaan itu.

(Dan, jika Anda tidak menggunakannya, informasi ini berasal dari "presedensi" dan asosiasi operator, sesuatu yang merupakan hasil dari bagaimana pohon sintaks bahasa didefinisikan. Bahkan, ini masih persis bagaimana cara kerjanya ketika Anda gunakan tanda kurung, tetapi kami menyederhanakan dan mengatakan bahwa kami tidak bergantung pada aturan prioritas sebelumnya.)

Setelah selesai (yaitu setelah kode Anda diurai menjadi sebuah program) operan tersebut masih perlu dievaluasi, dan ada aturan terpisah tentang bagaimana hal itu dilakukan: kata aturan (seperti yang ditunjukkan Andrew kepada kami) menyatakan bahwa LHS dari setiap operasi dievaluasi pertama kali di Jawa.

Perhatikan bahwa ini tidak terjadi di semua bahasa; misalnya, dalam C ++, kecuali jika Anda menggunakan operator hubungan pendek seperti &&atau ||, urutan evaluasi operan umumnya tidak ditentukan dan Anda tidak boleh mengandalkan itu baik.

Para guru perlu berhenti menjelaskan prioritas operator menggunakan frasa menyesatkan seperti "ini membuat penambahan terjadi terlebih dahulu". Diberikan ekspresi x * y + zpenjelasan yang tepat akan menjadi "operator diutamakan membuat penambahan terjadi antara x * ydan z, bukan antara ydan z", tanpa menyebutkan "pesanan".

Lightness Races di Orbit
sumber
6
Saya berharap guru-guru saya telah membuat beberapa pemisahan antara matematika yang mendasari dan sintaks yang mereka gunakan untuk mewakilinya, seperti jika kita menghabiskan satu hari dengan angka Romawi atau notasi Polandia atau apa pun dan melihat bahwa penambahan memiliki sifat yang sama. Kami belajar asosiatif dan semua properti itu di sekolah menengah, jadi ada banyak waktu.
John P
1
Senang Anda menyebutkan bahwa aturan ini tidak berlaku untuk semua bahasa. Juga, jika salah satu pihak memiliki efek samping lain, seperti menulis ke file, atau membaca waktu saat ini, itu (bahkan di Jawa) tidak ditentukan dalam urutan apa yang terjadi. Namun, hasil perbandingannya akan seolah dievaluasi dari kiri ke kanan (di Jawa). Di samping yang lain: beberapa bahasa hanya menolak penugasan campuran dan membandingkannya dengan aturan sintaksis, dan masalah tidak akan muncul.
Abel
5
@ JohnP: Itu semakin buruk. Apakah 5 * 4 berarti 5 + 5 + 5 + 5 atau 4 + 4 + 4 + 4 + 4? Beberapa guru bersikeras bahwa hanya satu dari pilihan itu yang benar.
Brian
3
@ Brian Tapi ... tapi ... perkalian bilangan real adalah komutatif!
Lightness Races in Orbit
2
Dalam dunia pemikiran saya, sepasang tanda kurung mewakili "diperlukan untuk". Menghitung ´a * (b + c) ´, tanda kurung akan menyatakan bahwa hasil penambahan diperlukan untuk perkalian. Preferensi operator implisit apa pun dapat dinyatakan oleh paren, kecuali aturan LHS-first atau RHS-first. (Apakah itu benar?) @Brian Dalam matematika ada beberapa kasus yang jarang di mana perkalian dapat diganti dengan penambahan berulang tetapi sejauh ini tidak selalu benar (dimulai dengan bilangan kompleks tetapi tidak terbatas pada). Jadi pendidik Anda harus benar - benar memperhatikan apa yang dikatakan orang ....
syck
164

==adalah operator kesetaraan biner .

Operan tangan kiri dari operator biner tampaknya sepenuhnya dievaluasi sebelum setiap bagian dari operan kanan dievaluasi.

Java 11 Spesifikasi> Urutan Evaluasi> Evaluasi Operand Tangan Kiri Pertama

Andrew Tobilko
sumber
42
Kata-kata "tampaknya" tidak terdengar seperti mereka yakin, tbh.
Mr Lister
86
"tampaknya" berarti spesifikasi tidak mengharuskan operasi benar-benar dilakukan dalam urutan itu secara kronologis, tetapi mengharuskan Anda mendapatkan hasil yang sama dengan yang akan Anda dapatkan jika itu terjadi.
Robyn
24
@MrLister "tampaknya" tampaknya merupakan pilihan kata yang buruk di pihak mereka. Yang dimaksud dengan "muncul" adalah "bermanifestasi sebagai fenomena bagi pengembang". "secara efektif" mungkin ungkapan yang lebih baik.
Kelvin
18
di komunitas C ++ ini setara dengan aturan "as-if" ... operan diharuskan untuk berperilaku "seolah-olah" itu diterapkan sesuai aturan berikut, bahkan jika secara teknis tidak.
Michael Edenfield
2
@ Selvin Saya setuju, saya akan memilih kata itu juga, daripada "sepertinya".
MC Emperor
149

Seperti yang dikatakan LouisWasserman, ekspresi dievaluasi dari kiri ke kanan. Dan java tidak peduli apa yang "dievaluasi" sebenarnya, ia hanya peduli tentang menghasilkan nilai (non volatile, final) untuk bekerja dengannya.

//the example values
x = 1;
y = 3;

Jadi untuk menghitung output pertama System.out.println(), berikut ini dilakukan:

x == (x = y)
1 == (x = y)
1 == (x = 3) //assign 3 to x, returns 3
1 == 3
false

dan untuk menghitung yang kedua:

(x = y) == x
(x = 3) == x //assign 3 to x, returns 3
3 == x
3 == 3
true

Perhatikan bahwa nilai kedua akan selalu bernilai true, terlepas dari nilai awal xdan y, karena Anda secara efektif membandingkan penetapan nilai dengan variabel yang ditugaskan padanya, dan a = bdan bakan, dievaluasi dalam urutan itu, selalu sama Menurut definisi.

Poohl
sumber
Ngomong-ngomong, "Kiri ke kanan" juga berlaku dalam matematika, tepat ketika Anda sampai pada tanda kurung atau prioritas, Anda mengulanginya di dalamnya dan mengevaluasi semua yang ada di dalam kiri ke kanan sebelum melangkah lebih jauh pada tingkat utama. Tetapi matematika tidak akan pernah melakukan ini; perbedaan hanya penting karena ini bukan persamaan tetapi operasi kombo, melakukan tugas dan persamaan dalam satu gerakan . Saya tidak akan pernah melakukan ini karena keterbacaan buruk, kecuali saya melakukan golf kode atau sedang mencari cara untuk mengoptimalkan kinerja, dan kemudian, akan ada komentar.
Harper - Reinstate Monica
25

Saya tidak yakin apakah ada item dalam Spesifikasi Bahasa Jawa yang menentukan pemuatan nilai variabel sebelumnya ...

Ada. Lain kali Anda tidak jelas apa spesifikasi mengatakan, silakan baca spesifikasi dan kemudian ajukan pertanyaan jika tidak jelas.

... sisi kanan (x = y)yang, berdasarkan urutan yang ditunjukkan oleh tanda kurung, harus dihitung terlebih dahulu.

Pernyataan itu salah. Tanda kurung tidak menyiratkan urutan evaluasi . Di Jawa, urutan evaluasi dibiarkan ke kanan, terlepas dari tanda kurung. Tanda kurung menentukan di mana batas subekspresi berada, bukan urutan evaluasi.

Mengapa ekspresi pertama bernilai false, tetapi yang kedua bernilai true?

Aturan untuk ==operator adalah: mengevaluasi sisi kiri untuk menghasilkan nilai, mengevaluasi sisi kanan untuk menghasilkan nilai, membandingkan nilai-nilai, perbandingannya adalah nilai ekspresi.

Dengan kata lain, arti expr1 == expr2selalu sama seolah-olah Anda telah menulis temp1 = expr1; temp2 = expr2;dan kemudian mengevaluasi temp1 == temp2.

Aturan untuk =operator dengan variabel lokal di sisi kiri adalah: mengevaluasi sisi kiri untuk menghasilkan variabel, mengevaluasi sisi kanan untuk menghasilkan nilai, melakukan penugasan, hasilnya adalah nilai yang ditugaskan.

Jadi kumpulkan:

x == (x = y)

Kami memiliki operator pembanding. Mengevaluasi sisi kiri untuk menghasilkan nilai - kami mendapatkan nilai saat ini x. Mengevaluasi sisi kanan: itu adalah tugas jadi kami mengevaluasi sisi kiri untuk menghasilkan variabel - variabel x- kami mengevaluasi sisi kanan - nilai saat ini y- menetapkannya x, dan hasilnya adalah nilai yang diberikan. Kami kemudian membandingkan nilai asli xdengan nilai yang ditugaskan.

Anda bisa melakukannya (x = y) == xsebagai latihan. Sekali lagi, ingat, semua aturan untuk mengevaluasi sisi kiri terjadi sebelum semua aturan mengevaluasi sisi kanan .

Saya berharap (x = y) dievaluasi terlebih dahulu, dan kemudian akan membandingkan x dengan dirinya sendiri (3) dan mengembalikan true.

Harapan Anda didasarkan pada seperangkat keyakinan yang salah tentang aturan Jawa. Semoga Anda sekarang memiliki keyakinan yang benar dan di masa depan akan mengharapkan hal-hal yang benar.

Pertanyaan ini berbeda dari "urutan evaluasi subekspresi dalam ekspresi Java"

Pernyataan ini salah. Pertanyaan itu benar-benar erat.

x jelas bukan 'subekspresi' di sini.

Pernyataan ini juga salah. Ini adalah subekspresi dua kali dalam setiap contoh.

Perlu dimuat untuk perbandingan daripada harus 'dievaluasi'.

Saya tidak tahu apa artinya ini.

Tampaknya Anda masih memiliki banyak kepercayaan salah. Saran saya adalah Anda membaca spesifikasi sampai keyakinan salah Anda digantikan oleh keyakinan sejati.

Pertanyaannya adalah khusus-Jawa dan ekspresi x == (x = y), tidak seperti konstruksi tidak praktis yang dibuat-buat yang biasanya dibuat untuk pertanyaan wawancara yang rumit, berasal dari proyek nyata.

Asal usul ungkapan tidak relevan dengan pertanyaan. Aturan untuk ekspresi semacam itu dijelaskan dengan jelas dalam spesifikasi; membacanya!

Itu seharusnya menjadi pengganti satu baris untuk idiom bandingkan-dan-ganti

Karena penggantian satu baris menyebabkan banyak kebingungan pada Anda, pembaca kode, saya akan menyarankan bahwa itu adalah pilihan yang buruk. Membuat kode lebih ringkas tetapi lebih sulit untuk dipahami bukanlah suatu kemenangan. Tidak mungkin membuat kode lebih cepat.

Kebetulan, C # telah membandingkan dan mengganti sebagai metode perpustakaan, yang dapat dimasukkan ke instruksi mesin. Saya percaya Java tidak memiliki metode seperti itu, karena tidak dapat diwakili dalam sistem tipe Java.

Eric Lippert
sumber
8
Jika ada yang bisa melalui seluruh JLS, maka tidak ada alasan untuk menerbitkan buku-buku Java dan setidaknya setengah dari situs ini juga tidak berguna.
John McClane
8
@JohnMcClane: Saya yakinkan Anda, tidak ada kesulitan apa pun dalam menelusuri seluruh spesifikasi, tetapi juga, Anda tidak harus melakukannya. Spesifikasi Java dimulai dengan "daftar isi" bermanfaat yang akan membantu Anda dengan cepat mencapai bagian yang paling Anda minati. Ini juga online dan kata kunci yang dapat dicari. Yang mengatakan, Anda benar: ada banyak sumber daya yang bagus yang akan membantu Anda mempelajari cara kerja Java; saran saya kepada Anda adalah Anda memanfaatkannya!
Eric Lippert
8
Jawaban ini tidak perlu merendahkan dan kasar. Ingat: baiklah .
walen
7
@LuisG .: Tidak ada penghinaan yang dimaksudkan atau tersirat; kita semua di sini untuk belajar dari satu sama lain, dan saya tidak merekomendasikan apa pun yang belum saya lakukan sendiri ketika saya masih pemula. Juga tidak kasar. Mengidentifikasi keyakinan palsu mereka secara jelas dan jelas merupakan kebaikan bagi poster aslinya . Bersembunyi di balik "kesopanan" dan membiarkan orang untuk terus memiliki kepercayaan yang salah adalah tidak membantu , dan memperkuat kebiasaan berpikir yang buruk .
Eric Lippert
5
@LuisG .: Saya dulu menulis blog tentang desain JavaScript, dan komentar paling membantu yang pernah saya dapatkan adalah dari Brendan menunjukkan dengan jelas dan jelas di mana saya salah. Itu hebat dan saya menghargai dia meluangkan waktu, karena saya kemudian hidup 20 tahun berikutnya dalam hidup saya tidak mengulangi kesalahan itu dalam pekerjaan saya sendiri, atau lebih buruk lagi, mengajarkannya kepada orang lain. Itu juga memberi saya kesempatan untuk mengoreksi kepercayaan salah yang sama pada orang lain dengan menggunakan diri saya sebagai contoh bagaimana orang-orang percaya hal-hal yang salah.
Eric Lippert
16

Ini terkait dengan prioritas operator dan bagaimana operator dievaluasi.

Tanda kurung '()' memiliki prioritas lebih tinggi dan memiliki asosiativitas dari kiri ke kanan. Kesetaraan '==' datang berikutnya dalam pertanyaan ini dan memiliki asosiatif dari kiri ke kanan. Tugas '=' datang terakhir dan memiliki hak asosiatif ke kiri.

Sistem menggunakan tumpukan untuk mengevaluasi ekspresi. Ekspresi dievaluasi dari kiri ke kanan.

Sekarang sampai pada pertanyaan asli:

int x = 1;
int y = 3;
System.out.println(x == (x = y)); // false

Pertama x (1) akan didorong ke stack. maka inner (x = y) akan dievaluasi dan didorong ke stack dengan nilai x (3). Sekarang x (1) akan dibandingkan dengan x (3) sehingga hasilnya salah.

x = 1; // reset
System.out.println((x = y) == x); // true

Di sini, (x = y) akan dievaluasi, sekarang nilai x menjadi 3 dan x (3) akan didorong ke tumpukan. Sekarang x (3) dengan nilai yang diubah setelah kesetaraan akan didorong ke tumpukan. Sekarang ekspresi akan dievaluasi dan keduanya akan sama sehingga hasilnya benar.

Amit
sumber
12

Itu tidak sama. Sisi kiri akan selalu dievaluasi sebelum sisi kanan, dan kurung tidak menentukan urutan eksekusi, tetapi pengelompokan perintah.

Dengan:

      x == (x = y)

Anda pada dasarnya melakukan hal yang sama seperti:

      x == y

Dan x akan memiliki nilai y setelah perbandingan.

Sementara dengan:

      (x = y) == x

Anda pada dasarnya melakukan hal yang sama seperti:

      x == x

Setelah x mengambil nilai y . Dan itu akan selalu kembali benar .

Or10n
sumber
9

Pada tes pertama yang Anda periksa apakah 1 == 3.

Pada tes kedua pemeriksaan Anda tidak 3 == 3.

(x = y) memberikan nilai dan nilai itu diuji. Dalam contoh sebelumnya x = 1 pertama maka x ditugaskan 3. Apakah 1 == 3?

Dalam yang terakhir, x ditugaskan 3, dan jelas itu masih 3. Apakah 3 == 3?

Michael Puckett II
sumber
8

Pertimbangkan contoh lain yang mungkin lebih sederhana ini:

int x = 1;
System.out.println(x == ++x); // false
x = 1; // reset
System.out.println(++x == x); // true

Di sini, operator pra-kenaikan ++xharus diterapkan sebelum perbandingan dibuat - seperti (x = y)pada contoh Anda harus dihitung sebelumnya perbandingan.

Namun, evaluasi ekspresi masih terjadi kiri → ke → kanan , jadi perbandingan pertama sebenarnya 1 == 2sedangkan yang kedua adalah 2 == 2.
Hal yang sama terjadi pada contoh Anda.

walen
sumber
8

Ekspresi dievaluasi dari kiri ke kanan. Pada kasus ini:

int x = 1;
int y = 3;

x == (x = y)) // false
x ==    t

- left x = 1
- let t = (x = y) => x = 3
- x == (x = y)
  x == t
  1 == 3 //false

(x = y) == x); // true
   t    == x

- left (x = y) => x = 3
           t    =      3 
-  (x = y) == x
-     t    == x
-     3    == 3 //true
Derviş Kayımbaşıoğlu
sumber
5

Pada dasarnya pernyataan pertama x memiliki nilai 1 Jadi Java membandingkan 1 == ke variabel x baru yang tidak akan sama

Di yang kedua Anda mengatakan x = y yang berarti nilai x berubah dan jadi ketika Anda menyebutnya lagi itu akan menjadi nilai yang sama maka mengapa itu benar dan x == x

Ahmad Bedirxan
sumber
4

== adalah operator kesetaraan perbandingan dan berfungsi dari kiri ke kanan.

x == (x = y);

di sini nilai x yang diberikan yang lama dibandingkan dengan nilai assign x yang baru, (1 == 3) // false

(x = y) == x;

Sedangkan, di sini nilai penetapan x yang baru dibandingkan dengan nilai holding yang baru dari x yang diberikan tepat sebelum perbandingan, (3 == 3) // benar

Sekarang pertimbangkan ini

    System.out.println((8 + (5 * 6)) * 9);
    System.out.println(8 + (5 * 6) * 9);
    System.out.println((8 + 5) * 6 * 9);
    System.out.println((8 + (5) * 6) * 9);
    System.out.println(8 + 5 * 6 * 9);

Keluaran:

342

278

702

342

278

Dengan demikian, kurung memainkan peran utama dalam ekspresi aritmatika tidak hanya dalam ekspresi perbandingan.

Nisrin Dhoondia
sumber
1
Kesimpulannya salah. Perilaku ini tidak berbeda antara operator aritmatika dan pembanding. x + (x = y)dan (x = y) + xakan menunjukkan perilaku yang sama seperti aslinya dengan operator pembanding.
JJJ
1
@JJJ Dalam x + (x = y) dan (x = y) + x tidak ada perbandingan yang terlibat, itu hanya menetapkan nilai y ke x dan menambahkannya ke x.
Nisrin Dhoondia
1
... ya, itu intinya. "Tanda kurung memainkan peran utama dalam ekspresi aritmatika, bukan hanya dalam ekspresi perbandingan" adalah salah karena tidak ada perbedaan antara ekspresi aritmatika dan perbandingan.
JJJ
2

Masalahnya di sini adalah operator aritmatik / operator prioritas urutan keluar dari dua operator =vs ==yang dominan adalah ==(Operator Relasional mendominasi) karena mendahului =operator penugasan. Meskipun didahulukan, urutan evaluasi adalah prioritas LTR (LEFT TO RIGHT) muncul setelah urutan evaluasi. Jadi, terlepas dari evaluasi kendala apa pun adalah LTR.

Himanshu Ahuja
sumber
1
Jawabannya salah. Prioritas operator tidak mempengaruhi urutan evaluasi. Baca beberapa jawaban terpilih untuk penjelasan, terutama yang ini .
JJJ
1
Benar, itu sebenarnya cara kita diajari ilusi tentang pembatasan pada hak datang pada semua hal itu tetapi dengan tepat menunjukkan itu tidak berdampak karena urutan evaluasi tetap ke kanan
Himanshu Ahuja
-1

Sangat mudah dalam perbandingan kedua di sebelah kiri adalah penugasan setelah menugaskan y ke x (di sebelah kiri) Anda kemudian membandingkan 3 == 3. Pada contoh pertama Anda membandingkan x = 1 dengan penugasan baru x = 3. Tampaknya bahwa selalu ada pernyataan pembacaan keadaan sekarang dari kiri ke kanan x.

Michał Ziobro
sumber
-2

Jenis pertanyaan yang Anda ajukan adalah pertanyaan yang sangat bagus jika Anda ingin menulis kompiler Java, atau menguji program untuk memverifikasi bahwa kompiler Java berfungsi dengan benar. Di Jawa, kedua ekspresi ini harus menghasilkan hasil yang Anda lihat. Dalam C ++, misalnya, mereka tidak harus - jadi jika seseorang menggunakan kembali bagian dari kompiler C ++ di kompiler Java mereka, Anda mungkin secara teoritis menemukan bahwa kompiler tidak berperilaku sebagaimana mestinya.

Sebagai pengembang perangkat lunak, menulis kode yang dapat dibaca, dimengerti dan dipelihara, kedua versi kode Anda akan dianggap mengerikan. Untuk memahami apa yang dikerjakan kode, kita harus tahu persis bagaimana bahasa Jawa didefinisikan. Seseorang yang menulis kode Java dan C ++ akan ngeri melihat kode. Jika Anda harus bertanya mengapa satu baris kode melakukan apa yang dilakukannya, maka Anda harus menghindari kode itu. (Saya kira dan berharap bahwa orang-orang yang menjawab pertanyaan "mengapa" Anda dengan benar akan sendiri menghindari kode itu juga).

gnasher729
sumber
"Untuk memahami apa yang dikerjakan kode, kita harus tahu persis bagaimana bahasa Jawa didefinisikan." Tetapi bagaimana jika setiap rekan kerja menganggapnya sebagai akal sehat?
BinaryTreeee