วิธีที่มีประสิทธิภาพในการสลับวัตถุ


20

ฉันกำลังเขียนโปรแกรมสำหรับซอฟต์แวร์ตอบคำถาม ฉันมีชั้นคำถามที่มี ArrayLists สำหรับคำถามคำตอบตัวเลือกเครื่องหมายและเครื่องหมายลบ บางสิ่งเช่นนี้

class question
{
    private ArrayList<Integer> index_list;
    private ArrayList<String> question_list;        
    private ArrayList<String> answer_list;      
    private ArrayList<String> opt1_list;        
    private ArrayList<String> opt2_list;    
}

ฉันต้องการสลับคำถามทั้งหมด แต่สำหรับคำถามที่จะสับได้วัตถุทั้งหมดต้องถูกสับ ฉันจะเข้าหาปัญหานี้ด้วยวิธีนี้:

ก่อนอื่นฉันจะไม่ใช้การออกแบบนี้และใช้ String ที่ไม่ArrayList<String>พิมพ์เป็นตัวแปรอินสแตนซ์และจากนั้นจะใช้Collections.shuffleวิธีการสลับวัตถุ แต่ทีมของฉันยืนยันในการออกแบบนี้

ตอนนี้คลาสคำถามจะมี ArrayLists ที่เพิ่มขึ้นเมื่อมีการป้อนคำถาม จะสลับคำถามได้อย่างไร


30
ฉันเกลียดที่จะพูดคุยด้วยความสมบูรณ์ แต่ถ้าทีมของคุณยืนยันในแบบนี้แสดงว่าพวกเขาผิด บอกพวกเขา! บอกพวกเขาว่าฉันพูดอย่างนั้น (และฉันเขียนไว้บนอินเทอร์เน็ตดังนั้นฉันต้องพูดถูก)
Joachim Sauer

10
ใช่บอกพวกเขาว่ามีคนมากมายที่นี่บอกคุณว่าการออกแบบประเภทนี้เป็นความผิดพลาดเริ่มต้นโดยทั่วไป
Doc Brown

6
ด้วยความอยากรู้: ทีมของคุณเห็นประโยชน์อะไรในการออกแบบนี้
Joachim Sauer

9
ระเบียบการตั้งชื่อ Java คือ CamelCase สำหรับชื่อคลาสและ camelCase สำหรับชื่อตัวแปร
Tulains Córdova

ฉันคิดว่าคุณต้องเผชิญหน้ากับทีมของคุณเกี่ยวกับการตัดสินใจออกแบบที่ยอดเยี่ยมนี้ หากพวกเขายังคงยืนยันหาสาเหตุ ถ้ามันเป็นเพียงความดื้อรั้นบางทีเริ่มคิดเกี่ยวกับการหาทีมใหม่ในอนาคตอันใกล้ หากพวกเขามีเหตุผลสำหรับโครงสร้างนี้ให้พิจารณาเหตุผลเหล่านั้นในข้อดีของพวกเขา
Ben Lee

คำตอบ:


95

ทีมงานของคุณทนทุกข์ทรมานจากปัญหาที่พบบ่อย: ปฏิเสธวัตถุ

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

นั่นเป็นวิธีที่ผิดที่จะไปเกี่ยวกับมันและมันซับซ้อนสิ่งที่คุณพยายามทำมาก ! การเรียงลำดับ (และการสับ) อาร์เรย์ขนาน (หรือรายการ) เป็นธุรกิจที่น่ารังเกียจและไม่มี API ทั่วไปสำหรับมันเพียงเพราะคุณมักจะต้องการหลีกเลี่ยงเลย

ฉันขอแนะนำให้คุณปรับโครงสร้างรหัสของคุณเช่นนี้:

class Question
{
    private Integer index;
    private String question;        
    private String answer;      
    private String opt1;        
    private String opt2;    
}

// somewhere else
List<Question> questionList = new ArrayList<Question>();

ด้วยวิธีนี้การสลับคำถามของคุณกลายเป็นเรื่องเล็กน้อย (โดยใช้Collections.shuffle()):

Collections.shuffle(questionList);

39
มันไม่ได้แม้แต่การปฏิเสธวัตถุมันเป็นโครงสร้างข้อมูลปฏิเสธ
jk

22

คุณทำไม่ได้ คุณสร้างรายการ / คิวของดัชนีและสลับแบบนั้น จากนั้นคุณทำซ้ำดัชนีซึ่งจะทำให้ลำดับ "สับ" ของคอลเลกชันอื่น ๆ ของคุณ

แม้จะอยู่นอกสถานการณ์ของคุณด้วยสิ่งที่แยกออกจากกันคอลเลกชันการสั่งซื้อแยกต่างหากก็ให้ประโยชน์มากมาย


10
ฉันไม่เต็มใจที่จะออกเสียงลงคะแนนนี้ขึ้น: มันต่อไปที่ดีที่สุดวิธีการแก้IFFออกแบบนี้ได้รับการแก้ไขอย่างแน่นอน แต่ยืนยันในการออกแบบนี้คือเพื่อให้ผิดว่าฉันจะไม่ต้องการที่จะให้คำแนะนำใด ๆ เกี่ยวกับเรื่องนี้ (meh upvoted อยู่แล้ว ;-))
โจอาคิมซาวเออร์

3
@joachimSauer - ในขณะที่ฉันเห็นด้วยมีสถานการณ์อื่น ๆ อีกมากมาย (น่ารังเกียจน้อยกว่า) ที่คอลเลกชันดั้งเดิมจะต้องคงที่ในขณะที่เส้นทางผ่านพวกเขาต้องการที่จะแตกต่างกันไป
Telastyn

4
ใช่ฉันรู้. การสับเปลี่ยนดัชนีเป็นวิธีที่ถูกต้องสำหรับสถานการณ์เหล่านั้น ความกลัวเพียงอย่างเดียวของฉันคือทีม OPs จะใช้สิ่งนี้และพูดว่า "ดีพอ" โดยไม่ต้องดูการออกแบบของพวกเขาอีกครั้ง
Joachim Sauer

1
คำตอบนี้มีค่าโดยเฉพาะอย่างยิ่งสำหรับกรณีที่ไม่มีอิสระในการแก้ไข / recode คลาสหรือโครงสร้างของคอลเลกชันพื้นฐานเช่นหนึ่งต้องทำกับ API กับคอลเลกชันที่ได้รับการดูแลระบบปฏิบัติการ การสับดัชนีเป็นข้อมูลเชิงลึกที่ยอดเยี่ยมและยืนหยัดด้วยตนเองแม้ว่ามันจะไม่กระตือรือร้นเท่าที่จะเข้าใจได้เท่ากับการออกแบบต้นแบบใหม่
hardmath

@ โจอาคิมซาวเออร์: การสับดัชนีจริง ๆ ไม่จำเป็นต้องเป็นทางออกที่ดีที่สุดสำหรับปัญหาดังกล่าว ดูคำตอบของฉันสำหรับทางเลือก
Michael Borgwardt

16

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

อย่างไรก็ตามเป็นเรื่องง่ายมากที่จะสลับหลายรายการในลักษณะเดียวกัน:

Random rnd = new Random();
long seed = rnd.nextLong();

rnd.setSeed(seed);
Collections.shuffle(index_list, rnd);
rnd.setSeed(seed);
Collections.shuffle(question_list, rnd);
rnd.setSeed(seed);
Collections.shuffle(answer_list, rnd);
...

นั่นเป็น ... วิธีที่เป็นระเบียบที่จะทำ! ทีนี้สำหรับกรณี "การเรียงลำดับ" เราแค่ต้องการค้นหาเมล็ดพันธุ์ที่สร้างรายการเรียงลำดับเมื่อใช้วิธีนี้แล้วเราจะสลับรายการทั้งหมดด้วยเมล็ดพันธุ์นี้!
Joachim Sauer

1
@JoachimSauer: ดีการเรียงลำดับไม่ได้เป็นส่วนหนึ่งของปัญหา แม้ว่ามันจะเป็นคำถามที่น่าสนใจว่ามีวิธีที่เป็นระบบในการค้นหาเมล็ดพันธุ์ดังกล่าวสำหรับ RNG ที่กำหนดหรือไม่
Michael Borgwardt

2
@MichaelBorgwardt เมื่อคุณได้รับมากกว่า 17 คำถามคุณก็ไม่สามารถแสดงจำนวน shuffles ที่เป็นไปได้ใน 48 บิตที่จาวาสุ่มใช้ (log_2 (17!) = 48.33)
วงล้อประหลาด

@ ratchetfreak: ไม่ดูเหมือนปัญหาจริงสำหรับฉัน และมันก็ไม่สำคัญที่จะใช้ SecureRandom แทนหากคุณต้องการ
Michael Borgwardt

4
@Telastyn: รายการของดัชนีคือ IMO เลเยอร์ทางอ้อมที่ทำให้วิธีการแก้ปัญหาของคุณมีความซับซ้อนมากขึ้นและไม่ว่ามันจะมีประสิทธิภาพมากขึ้นหรือน้อยลงขึ้นอยู่กับความถี่ในการเข้าถึงรายการหลังจากสับเปลี่ยน แต่ความแตกต่างของประสิทธิภาพจะไม่มีนัยสำคัญเนื่องจากขนาดที่เป็นจริงสำหรับการตอบคำถามที่มนุษย์ต้องตอบ
Michael Borgwardt

3

สร้างคลาสQuestion2:

class Question2
{
    public Integer index_list;
    public String question_list;        
    public String answer_list;      
    public String opt1_list;        
    public String opt2_list;    
}

แล้วสร้างแผนที่ฟังก์ชั่นquestionการArrayList<Question2>ใช้งานCollection.Shuffleเพื่อให้ได้ผลลัพธ์ที่และสร้างฟังก์ชั่นที่สองสำหรับการทำแผนที่กลับไปArrayList<Question2>question

หลังจากนั้นไปที่ทีมของคุณและพยายามโน้มน้าวพวกเขาว่าการใช้รหัสArrayList<Question2>แทนที่จะquestionปรับปรุงรหัสของพวกเขาเป็นจำนวนมากเนื่องจากจะช่วยให้พวกเขาได้รับการแปลงที่ไม่จำเป็นจำนวนมาก


1
นี่เป็นความคิดที่ดี แต่หลังจากความพยายามในการเปลี่ยนการออกแบบล้มเหลว
เซบาสเตียนเรดล

@SebastianRedl: บางครั้งการโน้มน้าวใจคนที่มีการออกแบบที่ดีขึ้นง่ายขึ้นเมื่อแสดงวิธีการแก้ปัญหาในรหัส
Doc Brown

1

คำตอบเดิม ๆ ที่ไร้เดียงสาและผิดของฉัน:

เพียงสร้างnตัวเลขสุ่ม(อย่างน้อย) และรายการแลกเปลี่ยนn กับรายการiในลูปสำหรับแต่ละรายการที่คุณมี

ในรหัสเทียม:

for (in i = 0; i < question_list.length(); i++) {
  int random = randomNumber(0, questions_list.length()-1);
  question_list.switchItems(i, random);
  answer_list.switchItems(i, random);
  opt1_list.switchItems(i, random);
  opt2_list.switchItems(i, random);

}

ปรับปรุง:

ขอบคุณสำหรับ the_lotus ที่ชี้ให้เห็นถึงบทความสยองขวัญที่เข้ารหัส ฉันรู้สึกชาญฉลาดขึ้นมากในขณะนี้ :-) อย่างไรก็ตาม Jeff Atwood ยังแสดงให้เห็นว่าทำอย่างไรให้ถูกต้องโดยใช้อัลกอริทึม Fisher-Yates :

for (int i = question_list.Length - 1; i > 0; i--){
  int n = rand.Next(i + 1); //assuming rand.Next(x) returns values between 0 and x-1
  question_list.switchItems(i, n);
  answer_list.switchItems(i, n);
  opt1_list.switchItems(i, n);
  opt2_list.switchItems(i, n);
}

ความแตกต่างที่สำคัญที่นี่คือแต่ละองค์ประกอบมีการสลับเพียงครั้งเดียว

และในขณะที่คำตอบอื่น ๆ อย่างถูกต้องอธิบายว่าแบบจำลองวัตถุของคุณมีข้อบกพร่องคุณอาจไม่อยู่ในตำแหน่งที่จะเปลี่ยน ดังนั้นอัลกอริทึม Fisher-Yates จะแก้ปัญหาของคุณโดยไม่ต้องเปลี่ยนรูปแบบข้อมูลของคุณ


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