เลือก N องค์ประกอบสุ่มจากรายการ <T> ใน C #


158

ฉันต้องการอัลกอริทึมด่วนเพื่อเลือก 5 องค์ประกอบแบบสุ่มจากรายการทั่วไป ตัวอย่างเช่นผมต้องการที่จะได้รับ 5 List<string>องค์ประกอบสุ่มจาก


11
โดยการสุ่มคุณหมายถึงรวมหรือไม่รวม IOW สามารถเลือกองค์ประกอบเดียวกันมากกว่าหนึ่งครั้งได้หรือไม่ (สุ่มอย่างแท้จริง) หรือเมื่อองค์ประกอบถูกเลือกแล้วจะไม่สามารถเลือกได้จากกลุ่มที่มีอยู่อีกต่อไปหรือไม่
Pretzel

คำตอบ:


127

วนซ้ำไปมาและสำหรับแต่ละองค์ประกอบทำให้ความน่าจะเป็นของการเลือก = (จำนวนที่ต้องการ) / (จำนวนที่เหลือ)

ดังนั้นหากคุณมี 40 รายการสิ่งแรกจะมีโอกาสเลือก 5/40 ถ้าเป็นเช่นนั้นถัดไปจะมีโอกาส 4/39 มิฉะนั้นจะมีโอกาส 5/39 เมื่อถึงจุดสิ้นสุดคุณจะมี 5 รายการและบ่อยครั้งที่คุณจะมีทั้งหมดก่อนหน้านั้น


33
ฉันรู้สึกว่ามันผิดอย่างละเอียด ดูเหมือนว่าส่วนหลังของรายการจะถูกเลือกบ่อยกว่าส่วนหน้าเนื่องจากส่วนหลังจะเห็นความน่าจะเป็นที่ใหญ่กว่ามาก ตัวอย่างเช่นหากไม่ได้รับ 35 หมายเลขแรกจะต้องเลือก 5 หมายเลขสุดท้าย หมายเลขแรกจะเห็นโอกาส 5/40 เท่านั้น แต่ตัวเลขสุดท้ายจะเห็น 1/1 บ่อยกว่า 5/40 ครั้ง คุณจะต้องสุ่มรายการก่อนที่คุณจะใช้อัลกอริทึมนี้
Ankur Goel

23
ตกลงฉันใช้อัลกอริทึมนี้ 10 ล้านครั้งในรายการ 40 องค์ประกอบแต่ละรายการมี 5/40 (.125) ช็อตที่ได้รับเลือกจากนั้นเรียกใช้การจำลองนั้นหลายครั้ง ปรากฎว่าสิ่งนี้ไม่ได้กระจายอย่างเท่าเทียมกัน องค์ประกอบที่ 16 ถึง 22 ได้รับการเลือกต่ำกว่า (16 = .123, 17 = .124) ในขณะที่องค์ประกอบที่ 34 ได้รับการเลือกเกิน (34 = .129) องค์ประกอบที่ 39 และ 40 ยังได้รับการเลือกน้อย แต่ไม่มาก (39 = .1247, 40 = .1246)
Ankur Goel

21
@ Ankur: ฉันไม่เชื่อว่ามันมีนัยสำคัญทางสถิติ ฉันเชื่อว่ามีหลักฐานอุปนัยว่าสิ่งนี้จะให้การกระจายที่สม่ำเสมอ
เรียกซ้ำ

9
ฉันได้ทำการทดลองซ้ำกัน 100 ล้านครั้งและในการทดลองของฉันรายการที่ถูกเลือกน้อยที่สุดได้รับการเลือกน้อยกว่า 0.106% น้อยกว่ารายการที่เลือกบ่อยที่สุด
เรียกซ้ำ

5
@ recursive: การพิสูจน์เป็นเรื่องเล็กน้อย เรารู้วิธีเลือก K รายการจาก K สำหรับ K ใด ๆ และวิธีเลือก 0 รายการจาก N สำหรับ N. ใด ๆ สมมติว่าเรารู้วิธีการเลือก K หรือ K-1 อย่างสม่ำเสมอจาก N-1> = K; จากนั้นเราสามารถเลือกรายการ K จาก N โดยเลือกรายการแรกที่มีความน่าจะเป็น K / N จากนั้นใช้วิธีการที่รู้จักเพื่อเลือกรายการ K หรือ K-1 ที่จำเป็นยังคงอยู่จาก N-1 ที่เหลือ
Ilmari Karonen

216

ใช้ linq:

YourList.OrderBy(x => rnd.Next()).Take(5)

2
+1 แต่ถ้าองค์ประกอบสองรายการได้รับหมายเลขเดียวกันจาก rnd.Next () หรือคล้ายกันดังนั้นองค์ประกอบแรกจะถูกเลือกและองค์ประกอบที่สองอาจไม่ได้ (ถ้าไม่จำเป็นต้องมีองค์ประกอบเพิ่มเติม) มันถูกสุ่มพอขึ้นอยู่กับการใช้งาน
Lasse Espeholt

7
ฉันคิดว่าการเรียงลำดับโดยคือ O (n log (n)) ดังนั้นฉันจะเลือกวิธีนี้หากความเรียบง่ายของรหัสเป็นสิ่งที่กังวลหลัก (เช่นกับรายการขนาดเล็ก)
Guido

2
แต่นี่ไม่แจกแจงและเรียงลำดับรายการทั้งหมดหรือไม่ เว้นแต่ด้วยคำว่า "รวดเร็ว" OP หมายถึง "ง่าย" ไม่ใช่ "นักแสดง" ...
drzaus

2
สิ่งนี้จะใช้งานได้ก็ต่อเมื่อ OrderBy () เรียกตัวเลือกหลักเพียงครั้งเดียวสำหรับแต่ละองค์ประกอบ หากมันเรียกมันว่าเมื่อใดก็ตามที่มันต้องการทำการเปรียบเทียบระหว่างสององค์ประกอบมันจะได้รับค่าที่แตกต่างกันไปในแต่ละครั้งซึ่งจะทำให้เกิดการเรียงลำดับ [เอกสารประกอบ] ( msdn.microsoft.com/en-us/library/vstudio/ ...... ) ไม่ได้บอกว่าจะใช้ไฟล์ไหน
Oliver Bock

2
ระวังถ้าYourListมีรายการมากมาย แต่คุณต้องการเลือกเพียงไม่กี่อย่าง ในกรณีนี้มันไม่ได้เป็นวิธีที่มีประสิทธิภาพในการทำมัน
Callum Watkins

39
public static List<T> GetRandomElements<T>(this IEnumerable<T> list, int elementsCount)
{
    return list.OrderBy(arg => Guid.NewGuid()).Take(elementsCount).ToList();
}

27

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

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

(0) คำตอบของ Kyle ซึ่งก็คือ O (n)

(1) สร้างรายการคู่ n [(0, แรนด์), (1, แรนด์), (2, แรนด์), ... ], จัดเรียงพวกเขาตามพิกัดที่สองและใช้ k แรก (สำหรับคุณ, k = 5) ดัชนีเพื่อรับเซ็ตย่อยแบบสุ่มของคุณ ฉันคิดว่านี่เป็นเรื่องง่ายที่จะใช้งานแม้ว่ามันจะเป็นเวลา O (n log n)

(2) เริ่มรายการว่าง s = [] ที่จะเติบโตเป็นดัชนีขององค์ประกอบสุ่ม k เลือกตัวเลข r ใน {0, 1, 2, ... , n-1} โดยการสุ่ม, r = rand% n และเพิ่มลงใน s ถัดไปใช้ r = rand% (n-1) และติดใน s; เพิ่มไปยังองค์ประกอบ # น้อยกว่าในเพื่อหลีกเลี่ยงการชน ถัดไปรับ r = rand% (n-2) และทำสิ่งเดียวกัน ฯลฯ จนกว่าคุณจะมีองค์ประกอบที่แตกต่างใน k กรณีนี้มีเวลาทำงานที่เลวร้ายที่สุด O (k ^ 2) ดังนั้นสำหรับ k << n นี่อาจเร็วกว่า หากคุณเรียงลำดับและติดตามช่วงเวลาที่ต่อเนื่องกันคุณสามารถนำไปใช้ใน O (k log k) แต่ทำงานได้มากกว่า

@Kyle - ถูกต้องในความคิดที่สองฉันเห็นด้วยกับคำตอบของคุณ ตอนแรกฉันอ่านมันอย่างเร่งรีบและคิดผิดพลาดว่าคุณกำลังเลือกให้แต่ละองค์ประกอบมีความเป็นไปได้คงที่ k / n ซึ่งอาจผิด - แต่วิธีการปรับตัวของคุณนั้นถูกต้องสำหรับฉัน ขอโทษด้วยกับเรื่องนั้น.

ตกลงและตอนนี้สำหรับนักเตะ: asymptotically (สำหรับการแก้ไข k, การเติบโต n), มี n ^ k / k! ตัวเลือกขององค์ประกอบย่อย k จากองค์ประกอบ n [นี่คือการประมาณของ (n เลือก k)] ถ้า n มีขนาดใหญ่และ k ไม่เล็กมากตัวเลขเหล่านี้ก็ใหญ่ ความยาวของรอบที่ดีที่สุดที่คุณสามารถหวังได้จากตัวสร้างตัวเลขสุ่ม 32 บิตมาตรฐานใด ๆ คือ 2 ^ 32 = 256 ^ 4 ดังนั้นถ้าเรามีรายการ 1,000 รายการและเราต้องการเลือก 5 แบบสุ่มไม่มีวิธีที่ตัวสร้างตัวเลขสุ่มแบบมาตรฐานจะเข้าถึงความเป็นไปได้ทั้งหมด อย่างไรก็ตามตราบใดที่คุณโอเคกับตัวเลือกที่ใช้งานได้ดีกับชุดเล็ก ๆ และสุ่ม "ดู" เสมออัลกอริธึมเหล่านี้ก็ควรจะโอเค

ภาคผนวก : หลังจากเขียนสิ่งนี้ฉันรู้ว่ามันยากที่จะใช้ความคิด (2) อย่างถูกต้องดังนั้นฉันจึงต้องการชี้แจงคำตอบนี้ ในการรับเวลา O (k log k) คุณต้องมีโครงสร้างคล้ายอาร์เรย์ที่รองรับการค้นหาและแทรก O (log m) - ต้นไม้ไบนารีที่สมดุลสามารถทำได้ ใช้โครงสร้างดังกล่าวเพื่อสร้างอาร์เรย์ที่เรียกว่า s นี่คือบางส่วนปลอม:

# Returns a container s with k distinct random numbers from {0, 1, ..., n-1}
def ChooseRandomSubset(n, k):
  for i in range(k):
    r = UniformRandom(0, n-i)                 # May be 0, must be < n-i
    q = s.FirstIndexSuchThat( s[q] - q > r )  # This is the search.
    s.InsertInOrder(q ? r + q : r + len(s))   # Inserts right before q.
  return s

ฉันขอแนะนำให้ใช้ตัวอย่างเล็ก ๆ น้อย ๆ เพื่อดูว่าการดำเนินการอย่างมีประสิทธิภาพใช้คำอธิบายภาษาอังกฤษข้างต้นได้อย่างไร


2
สำหรับ (1) คุณสามารถสลับรายการได้เร็วกว่าการเรียงลำดับสำหรับ (2) คุณจะให้การกระจายของคุณโดยใช้%
jk

จากการคัดค้านที่คุณยกเกี่ยวกับความยาวรอบของ rng มีวิธีใดบ้างที่เราสามารถสร้างอัลกอริทึมที่จะเลือกเซตทั้งหมดที่มีความน่าจะเป็นเท่ากัน?
Jonah

สำหรับ (1) เพื่อปรับปรุง O (n บันทึก (n)) คุณสามารถใช้การเรียงลำดับการเลือกเพื่อค้นหาองค์ประกอบที่เล็กที่สุด ที่จะทำงานใน O (n * k)
Jared

@Jonah: ฉันคิดอย่างนั้น สมมติว่าเราสามารถรวมตัวสร้างตัวเลขสุ่มอิสระหลายตัวเพื่อสร้างตัวที่ใหญ่กว่าได้ ( crypto.stackexchange.com/a/27431 ) จากนั้นคุณเพียงต้องการช่วงที่มีขนาดใหญ่พอที่จะจัดการกับขนาดของรายการที่เป็นปัญหา
Jared

16

ฉันคิดว่าคำตอบที่เลือกนั้นถูกต้องและน่ารักดี ฉันใช้มันแตกต่างกันแม้ว่าฉันต้องการผลลัพธ์แบบสุ่ม

    static IEnumerable<SomeType> PickSomeInRandomOrder<SomeType>(
        IEnumerable<SomeType> someTypes,
        int maxCount)
    {
        Random random = new Random(DateTime.Now.Millisecond);

        Dictionary<double, SomeType> randomSortTable = new Dictionary<double,SomeType>();

        foreach(SomeType someType in someTypes)
            randomSortTable[random.NextDouble()] = someType;

        return randomSortTable.OrderBy(KVP => KVP.Key).Take(maxCount).Select(KVP => KVP.Value);
    }

น่ากลัว! ช่วยฉันออกไปจริงๆ!
อาร์มสตรองที่

1
คุณมีเหตุผลที่จะไม่ใช้ Random () ใหม่ซึ่งยึดตาม Environment.TickCount เทียบกับ DateTime.Now.Millisecond หรือไม่
Lasse Espeholt

ไม่เพิ่งไม่ทราบว่ามีค่าเริ่มต้นอยู่
Frank Schwieterman

การพัฒนาของ randomSortTable: randomSortTable = someTypes.ToDictionary (x => random.NextDouble (), y => y); บันทึกลูป foreach
Keltex

2
ตกลงปลายปี แต่ ... นี่ไม่ใช่คำตอบที่สั้นกว่าของ @ ersin และจะไม่ล้มเหลวหากคุณได้รับหมายเลขสุ่มซ้ำ (โดยที่ Ersin จะมีอคติต่อรายการแรกของคู่ที่ซ้ำกัน)
Andiih

12

ฉันเพิ่งพบปัญหานี้และการค้นหา google อีกหลายข้อทำให้ฉันมีปัญหาในการสุ่มเลือกรายการ: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle

หากต้องการสุ่มเลือกรายการของคุณแบบสุ่มคุณจะทำดังนี้

ในการสลับอาเรย์ขององค์ประกอบ n รายการ (ดัชนี 0..n-1):

  for i from n  1 downto 1 do
       j  random integer with 0  j  i
       exchange a[j] and a[i]

หากคุณต้องการเพียง 5 องค์ประกอบแรกแทนที่จะใช้ i ตั้งแต่ n-1 ถึง 1 คุณจะต้องเรียกใช้ไปยัง n-5 เท่านั้น (เช่น: n-5)

ให้บอกว่าคุณต้องการรายการ k

สิ่งนี้กลายเป็น:

  for (i = n  1; i >= n-k; i--)
  {
       j = random integer with 0  j  i
       exchange a[j] and a[i]
  }

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

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

นอกจากนี้หากคุณไม่ต้องการแก้ไขรายการเริ่มต้นของคุณคุณสามารถเขียน swaps ทั้งหมดของคุณในรายการชั่วคราวย้อนกลับรายการนั้นและนำไปใช้อีกครั้งดังนั้นดำเนินการชุดของการแลกเปลี่ยนและกลับรายการแรกของคุณโดยไม่เปลี่ยน เวลาทำงาน O (k)

สุดท้ายสำหรับ stickler จริงถ้า (n == k) คุณควรหยุดที่ 1 ไม่ใช่ nk เนื่องจากจำนวนเต็มที่เลือกแบบสุ่มจะเป็น 0 เสมอ


ผมดำเนินการโดยใช้ C # ในโพสต์บล็อกของฉัน: vijayt.com/post/random-select-using-fisher-yates-algorithm หวังว่ามันจะช่วยให้คนที่กำลังมองหาวิธี C #
vijayst

9

คุณสามารถใช้สิ่งนี้ แต่การสั่งซื้อจะเกิดขึ้นในฝั่งไคลเอ็นต์

 .AsEnumerable().OrderBy(n => Guid.NewGuid()).Take(5);

ตกลง มันอาจจะไม่ใช่การแสดงที่ดีที่สุดหรือสุ่มที่สุด แต่สำหรับคนส่วนใหญ่แล้วมันจะดีพอ
ริชบัน

ลดลงเนื่องจากGuids รับประกันว่าจะไม่ซ้ำกันไม่ใช่แบบสุ่มจะรับประกันว่าจะไม่ซ้ำกันไม่สุ่ม
Theodor Zoulias

8

จากDragons ในอัลกอริทึมการตีความใน C #:

int k = 10; // items to select
var items = new List<int>(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 });
var selected = new List<int>();
double needed = k;
double available = items.Count;
var rand = new Random();
while (selected.Count < k) {
   if( rand.NextDouble() < needed / available ) {
      selected.Add(items[(int)available-1])
      needed--;
   }
   available--;
}

อัลกอริทึมนี้จะเลือกตัวบ่งชี้ที่ไม่ซ้ำกันของรายการรายการ


รับเฉพาะรายการที่เพียงพอในรายการ แต่ไม่ได้รับการสุ่ม
culithay

2
การนำไปใช้งานนี้ใช้งานไม่ได้เนื่องจากการใช้varผลลัพธ์ในneededและavailableทั้งคู่เป็นจำนวนเต็มซึ่งทำให้needed/available0 เสมอ
Niko

1
สิ่งนี้ดูเหมือนจะเป็นการดำเนินการตามคำตอบที่ยอมรับ
DCShannon

6

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

1) หากคุณต้องการค่าสุ่มที่แท้จริงซึ่งหมายความว่าไม่มีข้อ จำกัด เกี่ยวกับองค์ประกอบที่จะเลือก (เช่นเมื่อเลือกรายการที่เลือกแล้ว):

public static List<T> GetTrueRandom<T>(this IList<T> source, int count, 
                                       bool throwArgumentOutOfRangeException = true)
{
    if (throwArgumentOutOfRangeException && count > source.Count)
        throw new ArgumentOutOfRangeException();

    var randoms = new List<T>(count);
    randoms.AddRandomly(source, count);
    return randoms;
}

หากคุณปิดการตั้งค่าสถานะการยกเว้นคุณสามารถเลือกรายการสุ่มจำนวนเท่าใดก็ได้

หากคุณมี {1, 2, 3, 4} ก็สามารถให้ {1, 4, 4}, {1, 4, 3} ฯลฯ สำหรับ 3 รายการหรือแม้แต่ {1, 4, 3, 2, 4} สำหรับ 5 รายการ!

นี่ควรจะค่อนข้างเร็วเพราะไม่มีอะไรให้ตรวจ

2) หากคุณต้องการสมาชิกรายบุคคลจากกลุ่มที่ไม่มีการซ้ำซ้อนฉันจะต้องใช้พจนานุกรม

public static List<T> GetDistinctRandom<T>(this IList<T> source, int count)
{
    if (count > source.Count)
        throw new ArgumentOutOfRangeException();

    if (count == source.Count)
        return new List<T>(source);

    var sourceDict = source.ToIndexedDictionary();

    if (count > source.Count / 2)
    {
        while (sourceDict.Count > count)
            sourceDict.Remove(source.GetRandomIndex());

        return sourceDict.Select(kvp => kvp.Value).ToList();
    }

    var randomDict = new Dictionary<int, T>(count);
    while (randomDict.Count < count)
    {
        int key = source.GetRandomIndex();
        if (!randomDict.ContainsKey(key))
            randomDict.Add(key, sourceDict[key]);
    }

    return randomDict.Select(kvp => kvp.Value).ToList();
}

รหัสนี้มีความยาวกว่าพจนานุกรมเล็กน้อยที่นี่เพราะฉันไม่เพียง แต่เพิ่มเท่านั้น แต่ยังลบออกจากรายการด้วยดังนั้นจึงเป็นสองลูป คุณสามารถดูที่นี่ที่ฉันไม่ได้จัดลำดับสิ่งที่ทุกเมื่อจะเท่ากับcount source.Countนั่นเป็นเพราะผมเชื่อว่าการสุ่มควรจะอยู่ในชุดกลับเป็นทั้ง ผมหมายถึงถ้าคุณต้องการที่5รายการที่สุ่มจาก1, 2, 3, 4, 5มันไม่ควรว่าถ้ามัน1, 3, 4, 2, 5หรือ1, 2, 3, 4, 5แต่ถ้าคุณต้องการ4รายการจากชุดเดียวกันแล้วมันควรจะไม่สามารถคาดการณ์ผลผลิตใน1, 2, 3, 4, 1, 3, 5, 2,2, 3, 5, 4ฯลฯ ประการที่สองเมื่อนับของรายการแบบสุ่มที่จะ คืนเป็นมากกว่าครึ่งหนึ่งของกลุ่มดั้งเดิมจากนั้นจึงง่ายต่อการลบsource.Count - countรายการจากกลุ่มมากกว่าการเพิ่มcountรายการ สำหรับเหตุผลด้านประสิทธิภาพฉันใช้sourceแทนsourceDict เพื่อรับดัชนีแบบสุ่มในวิธีการลบ

ดังนั้นหากคุณมี {1, 2, 3, 4} สิ่งนี้อาจสิ้นสุดใน {1, 2, 3}, {3, 4, 1} ฯลฯ สำหรับ 3 รายการ

3) หากคุณต้องการค่าสุ่มที่แตกต่างอย่างแท้จริงจากกลุ่มของคุณโดยคำนึงถึงรายการที่ซ้ำกันในกลุ่มดั้งเดิมคุณอาจใช้วิธีเดียวกันกับข้างต้น แต่ a HashSetจะเบากว่าพจนานุกรม

public static List<T> GetTrueDistinctRandom<T>(this IList<T> source, int count, 
                                               bool throwArgumentOutOfRangeException = true)
{
    if (count > source.Count)
        throw new ArgumentOutOfRangeException();

    var set = new HashSet<T>(source);

    if (throwArgumentOutOfRangeException && count > set.Count)
        throw new ArgumentOutOfRangeException();

    List<T> list = hash.ToList();

    if (count >= set.Count)
        return list;

    if (count > set.Count / 2)
    {
        while (set.Count > count)
            set.Remove(list.GetRandom());

        return set.ToList();
    }

    var randoms = new HashSet<T>();
    randoms.AddRandomly(list, count);
    return randoms.ToList();
}

randomsตัวแปรทำHashSetเพื่อหลีกเลี่ยงการซ้ำกันที่เพิ่มเข้ามาในที่หาได้ยากในกรณีที่หายากที่Random.Nextสามารถให้ผลผลิตมูลค่าเท่ากันโดยเฉพาะอย่างยิ่งเมื่อมีการป้อนข้อมูลรายการที่มีขนาดเล็ก

ดังนั้น {1, 2, 2, 4} => 3 รายการสุ่ม => {1, 2, 4} และไม่เคย {1, 2, 2}

{1, 2, 2, 4} => 4 รายการสุ่ม => ข้อยกเว้น !! หรือ {1, 2, 4} ขึ้นอยู่กับชุดธง

วิธีการขยายที่ฉันใช้:

static Random rnd = new Random();
public static int GetRandomIndex<T>(this ICollection<T> source)
{
    return rnd.Next(source.Count);
}

public static T GetRandom<T>(this IList<T> source)
{
    return source[source.GetRandomIndex()];
}

static void AddRandomly<T>(this ICollection<T> toCol, IList<T> fromList, int count)
{
    while (toCol.Count < count)
        toCol.Add(fromList.GetRandom());
}

public static Dictionary<int, T> ToIndexedDictionary<T>(this IEnumerable<T> lst)
{
    return lst.ToIndexedDictionary(t => t);
}

public static Dictionary<int, T> ToIndexedDictionary<S, T>(this IEnumerable<S> lst, 
                                                           Func<S, T> valueSelector)
{
    int index = -1;
    return lst.ToDictionary(t => ++index, valueSelector);
}

หากทุกอย่างเกี่ยวกับประสิทธิภาพการทำงานกับหมื่นรายการในรายการจะต้องทำซ้ำ 10,000 ครั้งแล้วคุณอาจต้องการคลาสสุ่มเร็วกว่าSystem.Randomแต่ฉันไม่คิดว่ามันเป็นเรื่องใหญ่พิจารณาหลังส่วนใหญ่อาจไม่เคย คอขวดมันค่อนข้างเร็วพอ ..

แก้ไข:หากคุณต้องการจัดเรียงลำดับของรายการที่ส่งคืนอีกครั้งก็ไม่มีอะไรที่สามารถเอาชนะวิธี Fisher-Yates ของ dhakim - สั้นหวานและเรียบง่าย ..


6

คิดเกี่ยวกับความคิดเห็นโดย @JohnShedletsky เกี่ยวกับคำตอบที่ยอมรับเกี่ยวกับ (การถอดความ):

คุณควรจะทำสิ่งนี้ใน O (subset.Length) มากกว่า O (originalList.Length)

โดยพื้นฐานแล้วคุณควรจะสามารถสร้าง subsetดัชนีแบบสุ่มแล้วถอนออกจากรายการเดิม

วิธีการ

public static class EnumerableExtensions {

    public static Random randomizer = new Random(); // you'd ideally be able to replace this with whatever makes you comfortable

    public static IEnumerable<T> GetRandom<T>(this IEnumerable<T> list, int numItems) {
        return (list as T[] ?? list.ToArray()).GetRandom(numItems);

        // because ReSharper whined about duplicate enumeration...
        /*
        items.Add(list.ElementAt(randomizer.Next(list.Count()))) ) numItems--;
        */
    }

    // just because the parentheses were getting confusing
    public static IEnumerable<T> GetRandom<T>(this T[] list, int numItems) {
        var items = new HashSet<T>(); // don't want to add the same item twice; otherwise use a list
        while (numItems > 0 )
            // if we successfully added it, move on
            if( items.Add(list[randomizer.Next(list.Length)]) ) numItems--;

        return items;
    }

    // and because it's really fun; note -- you may get repetition
    public static IEnumerable<T> PluckRandomly<T>(this IEnumerable<T> list) {
        while( true )
            yield return list.ElementAt(randomizer.Next(list.Count()));
    }

}

หากคุณต้องการที่จะมีประสิทธิภาพมากขึ้นคุณอาจจะใช้HashSetของดัชนีไม่องค์ประกอบของรายการที่เกิดขึ้นจริง (ในกรณีที่คุณมีประเภทที่ซับซ้อนหรือการเปรียบเทียบราคาแพง);

การทดสอบหน่วย

และเพื่อให้แน่ใจว่าเราไม่มีการชนใด ๆ ฯลฯ

[TestClass]
public class RandomizingTests : UnitTestBase {
    [TestMethod]
    public void GetRandomFromList() {
        this.testGetRandomFromList((list, num) => list.GetRandom(num));
    }

    [TestMethod]
    public void PluckRandomly() {
        this.testGetRandomFromList((list, num) => list.PluckRandomly().Take(num), requireDistinct:false);
    }

    private void testGetRandomFromList(Func<IEnumerable<int>, int, IEnumerable<int>> methodToGetRandomItems, int numToTake = 10, int repetitions = 100000, bool requireDistinct = true) {
        var items = Enumerable.Range(0, 100);
        IEnumerable<int> randomItems = null;

        while( repetitions-- > 0 ) {
            randomItems = methodToGetRandomItems(items, numToTake);
            Assert.AreEqual(numToTake, randomItems.Count(),
                            "Did not get expected number of items {0}; failed at {1} repetition--", numToTake, repetitions);
            if(requireDistinct) Assert.AreEqual(numToTake, randomItems.Distinct().Count(),
                            "Collisions (non-unique values) found, failed at {0} repetition--", repetitions);
            Assert.IsTrue(randomItems.All(o => items.Contains(o)),
                        "Some unknown values found; failed at {0} repetition--", repetitions);
        }
    }
}

2
ความคิดที่ดีมีปัญหา (1) หากรายการที่ใหญ่กว่าของคุณใหญ่มาก (อ่านจากฐานข้อมูล) คุณจะเห็นรายการทั้งหมดซึ่งอาจเกินหน่วยความจำ (2) ถ้า K ใกล้เคียงกับ N คุณจะต้องค้นหาดัชนีที่ไม่มีเหตุสมควรในลูปของคุณเป็นจำนวนมากทำให้โค้ดต้องใช้เวลาไม่แน่นอน ปัญหาเหล่านี้แก้ไขได้
Paul Chernoch

1
ทางออกสำหรับปัญหาการฟาดฟันของฉันคือ: ถ้า K <N / 2 ทำตามที่คุณต้องการ หาก K> = N / 2 ให้เลือกดัชนีที่ไม่ควรเก็บไว้แทนที่จะเป็นดัชนีที่ควรเก็บไว้ ยังคงมีบางอย่างที่ฟาด แต่น้อยกว่ามาก
Paul Chernoch

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

โดยเฉลี่ยแล้วสำหรับ K = N / 2 (กรณีที่แย่ที่สุดสำหรับการปรับปรุงที่แนะนำของ Paul) อัลกอริทึม (ปรับปรุงการกดทับ) จะปรากฏขึ้นเพื่อทำซ้ำ ~ 0.693 * N ตอนนี้ทำการเปรียบเทียบความเร็ว ดีกว่าคำตอบที่ยอมรับหรือไม่ ตัวอย่างขนาดไหน
mbomb007

6

ฉันรวมคำตอบข้างต้นหลายข้อเพื่อสร้างวิธีส่วนขยายที่ประเมินโดย Lazily การทดสอบของฉันแสดงให้เห็นว่าวิธีการของ Kyle (คำสั่งซื้อ (N)) ช้ากว่าการใช้ชุดของ drzaus เพื่อเสนอดัชนีแบบสุ่มให้เลือก (คำสั่งซื้อ (K)) อดีตจะทำการโทรไปยังเครื่องกำเนิดตัวเลขแบบสุ่มจำนวนมากรวมทั้งจะทำซ้ำมากกว่ารายการ

เป้าหมายของการดำเนินการของฉันคือ:

1) อย่ารับรู้รายการทั้งหมดหากกำหนด IEnumerable ที่ไม่ใช่ IList หากฉันได้รับลำดับของ zillion รายการฉันไม่ต้องการหน่วยความจำไม่เพียงพอ ใช้แนวทางของ Kyle สำหรับการแก้ปัญหาออนไลน์

2) ถ้าฉันสามารถบอกได้ว่ามันเป็น IList ให้ใช้วิธีการของ drzaus โดยบิด หาก K มากกว่าครึ่งหนึ่งของ N ฉันเสี่ยงที่จะ thrashing เพราะฉันเลือกดัชนีสุ่มจำนวนมากซ้ำแล้วซ้ำอีกและต้องข้ามไป ดังนั้นฉันจึงเขียนรายการดัชนีที่จะไม่เก็บไว้

3) ฉันรับประกันว่ารายการจะถูกส่งกลับในลำดับเดียวกันกับที่พวกเขาพบ อัลกอริทึมของ Kyle ไม่จำเป็นต้องทำการเปลี่ยนแปลงใด ๆ อัลกอริทึมของ drzaus ต้องการให้ฉันไม่ปล่อยไอเท็มตามลำดับที่เลือกดัชนีแบบสุ่ม ฉันรวบรวมดัชนีทั้งหมดลงใน SortedSet แล้วปล่อยรายการตามลำดับดัชนีเรียง

4) ถ้า K มีขนาดใหญ่เมื่อเทียบกับ N และฉันกลับความรู้สึกของชุดแล้วฉันจะระบุรายการทั้งหมดและทดสอบว่าดัชนีไม่ได้อยู่ในชุด ซึ่งหมายความว่าฉันเสียเวลารันคำสั่งซื้อ (K) แต่เนื่องจาก K ใกล้เคียงกับ N ในกรณีเหล่านี้ฉันจึงไม่สูญเสียมากนัก

นี่คือรหัส:

    /// <summary>
    /// Takes k elements from the next n elements at random, preserving their order.
    /// 
    /// If there are fewer than n elements in items, this may return fewer than k elements.
    /// </summary>
    /// <typeparam name="TElem">Type of element in the items collection.</typeparam>
    /// <param name="items">Items to be randomly selected.</param>
    /// <param name="k">Number of items to pick.</param>
    /// <param name="n">Total number of items to choose from.
    /// If the items collection contains more than this number, the extra members will be skipped.
    /// If the items collection contains fewer than this number, it is possible that fewer than k items will be returned.</param>
    /// <returns>Enumerable over the retained items.
    /// 
    /// See http://stackoverflow.com/questions/48087/select-a-random-n-elements-from-listt-in-c-sharp for the commentary.
    /// </returns>
    public static IEnumerable<TElem> TakeRandom<TElem>(this IEnumerable<TElem> items, int k, int n)
    {
        var r = new FastRandom();
        var itemsList = items as IList<TElem>;

        if (k >= n || (itemsList != null && k >= itemsList.Count))
            foreach (var item in items) yield return item;
        else
        {  
            // If we have a list, we can infer more information and choose a better algorithm.
            // When using an IList, this is about 7 times faster (on one benchmark)!
            if (itemsList != null && k < n/2)
            {
                // Since we have a List, we can use an algorithm suitable for Lists.
                // If there are fewer than n elements, reduce n.
                n = Math.Min(n, itemsList.Count);

                // This algorithm picks K index-values randomly and directly chooses those items to be selected.
                // If k is more than half of n, then we will spend a fair amount of time thrashing, picking
                // indices that we have already picked and having to try again.   
                var invertSet = k >= n/2;  
                var positions = invertSet ? (ISet<int>) new HashSet<int>() : (ISet<int>) new SortedSet<int>();

                var numbersNeeded = invertSet ? n - k : k;
                while (numbersNeeded > 0)
                    if (positions.Add(r.Next(0, n))) numbersNeeded--;

                if (invertSet)
                {
                    // positions contains all the indices of elements to Skip.
                    for (var itemIndex = 0; itemIndex < n; itemIndex++)
                    {
                        if (!positions.Contains(itemIndex))
                            yield return itemsList[itemIndex];
                    }
                }
                else
                {
                    // positions contains all the indices of elements to Take.
                    foreach (var itemIndex in positions)
                        yield return itemsList[itemIndex];              
                }
            }
            else
            {
                // Since we do not have a list, we will use an online algorithm.
                // This permits is to skip the rest as soon as we have enough items.
                var found = 0;
                var scanned = 0;
                foreach (var item in items)
                {
                    var rand = r.Next(0,n-scanned);
                    if (rand < k - found)
                    {
                        yield return item;
                        found++;
                    }
                    scanned++;
                    if (found >= k || scanned >= n)
                        break;
                }
            }
        }  
    } 

ฉันใช้ตัวสร้างตัวเลขสุ่มแบบพิเศษ แต่คุณสามารถใช้การสุ่มของ C # ถ้าคุณต้องการ ( FastRandom)เขียนโดย Colin Green และเป็นส่วนหนึ่งของ SharpNEAT มันมีช่วงเวลา 2 ^ 128-1 ซึ่งดีกว่า RNG หลายตัว)

นี่คือการทดสอบหน่วย:

[TestClass]
public class TakeRandomTests
{
    /// <summary>
    /// Ensure that when randomly choosing items from an array, all items are chosen with roughly equal probability.
    /// </summary>
    [TestMethod]
    public void TakeRandom_Array_Uniformity()
    {
        const int numTrials = 2000000;
        const int expectedCount = numTrials/20;
        var timesChosen = new int[100];
        var century = new int[100];
        for (var i = 0; i < century.Length; i++)
            century[i] = i;

        for (var trial = 0; trial < numTrials; trial++)
        {
            foreach (var i in century.TakeRandom(5, 100))
                timesChosen[i]++;
        }
        var avg = timesChosen.Average();
        var max = timesChosen.Max();
        var min = timesChosen.Min();
        var allowedDifference = expectedCount/100;
        AssertBetween(avg, expectedCount - 2, expectedCount + 2, "Average");
        //AssertBetween(min, expectedCount - allowedDifference, expectedCount, "Min");
        //AssertBetween(max, expectedCount, expectedCount + allowedDifference, "Max");

        var countInRange = timesChosen.Count(i => i >= expectedCount - allowedDifference && i <= expectedCount + allowedDifference);
        Assert.IsTrue(countInRange >= 90, String.Format("Not enough were in range: {0}", countInRange));
    }

    /// <summary>
    /// Ensure that when randomly choosing items from an IEnumerable that is not an IList, 
    /// all items are chosen with roughly equal probability.
    /// </summary>
    [TestMethod]
    public void TakeRandom_IEnumerable_Uniformity()
    {
        const int numTrials = 2000000;
        const int expectedCount = numTrials / 20;
        var timesChosen = new int[100];

        for (var trial = 0; trial < numTrials; trial++)
        {
            foreach (var i in Range(0,100).TakeRandom(5, 100))
                timesChosen[i]++;
        }
        var avg = timesChosen.Average();
        var max = timesChosen.Max();
        var min = timesChosen.Min();
        var allowedDifference = expectedCount / 100;
        var countInRange =
            timesChosen.Count(i => i >= expectedCount - allowedDifference && i <= expectedCount + allowedDifference);
        Assert.IsTrue(countInRange >= 90, String.Format("Not enough were in range: {0}", countInRange));
    }

    private IEnumerable<int> Range(int low, int count)
    {
        for (var i = low; i < low + count; i++)
            yield return i;
    }

    private static void AssertBetween(int x, int low, int high, String message)
    {
        Assert.IsTrue(x > low, String.Format("Value {0} is less than lower limit of {1}. {2}", x, low, message));
        Assert.IsTrue(x < high, String.Format("Value {0} is more than upper limit of {1}. {2}", x, high, message));
    }

    private static void AssertBetween(double x, double low, double high, String message)
    {
        Assert.IsTrue(x > low, String.Format("Value {0} is less than lower limit of {1}. {2}", x, low, message));
        Assert.IsTrue(x < high, String.Format("Value {0} is more than upper limit of {1}. {2}", x, high, message));
    }
}

มีข้อผิดพลาดในการทดสอบหรือไม่? คุณมีความif (itemsList != null && k < n/2)หมายว่าอะไรอยู่ข้างในif invertSetเสมอfalseซึ่งหมายความว่าไม่เคยใช้ตรรกะ
NetMage

4

การขยายจากคำตอบของ @ ers หากมีใครกังวลเกี่ยวกับการใช้งาน OrderBy ที่แตกต่างกันที่เป็นไปได้สิ่งนี้ควรปลอดภัย:

// Instead of this
YourList.OrderBy(x => rnd.Next()).Take(5)

// Temporarily transform 
YourList
    .Select(v => new {v, i = rnd.Next()}) // Associate a random index to each entry
    .OrderBy(x => x.i).Take(5) // Sort by (at this point fixed) random index 
    .Select(x => x.v); // Go back to enumerable of entry

3

นี่คือสิ่งที่ดีที่สุดที่ฉันสามารถทำได้ในการตัดครั้งแรก:

public List<String> getRandomItemsFromList(int returnCount, List<String> list)
{
    List<String> returnList = new List<String>();
    Dictionary<int, int> randoms = new Dictionary<int, int>();

    while (randoms.Count != returnCount)
    {
        //generate new random between one and total list count
        int randomInt = new Random().Next(list.Count);

        // store this in dictionary to ensure uniqueness
        try
        {
            randoms.Add(randomInt, randomInt);
        }
        catch (ArgumentException aex)
        {
            Console.Write(aex.Message);
        } //we can assume this element exists in the dictonary already 

        //check for randoms length and then iterate through the original list 
        //adding items we select via random to the return list
        if (randoms.Count == returnCount)
        {
            foreach (int key in randoms.Keys)
                returnList.Add(list[randoms[key]]);

            break; //break out of _while_ loop
        }
    }

    return returnList;
}

การใช้รายการ randoms ในช่วง 1 - จำนวนรายการทั้งหมดจากนั้นเพียงดึงรายการเหล่านั้นในรายการดูเหมือนจะเป็นวิธีที่ดีที่สุด แต่การใช้พจนานุกรมเพื่อให้แน่ใจว่ามีเอกลักษณ์เป็นสิ่งที่ฉันยังครุ่นคิดอยู่

ยังทราบว่าฉันใช้รายการสตริงแทนที่ตามต้องการ


1
ทำงานตั้งแต่แรก!
Sangam

3

วิธีแก้ปัญหาแบบง่าย ๆ ที่ฉันใช้ (อาจไม่ดีสำหรับรายการขนาดใหญ่): คัดลอกรายการลงในรายการชั่วคราวจากนั้นให้สุ่มเลือก Item จากรายการ temp และวางไว้ในรายการไอเท็มที่เลือกขณะที่นำออกจากรายการ temp (ดังนั้นจึงไม่สามารถ reselected)

ตัวอย่าง:

List<Object> temp = OriginalList.ToList();
List<Object> selectedItems = new List<Object>();
Random rnd = new Random();
Object o;
int i = 0;
while (i < NumberOfSelectedItems)
{
            o = temp[rnd.Next(temp.Count)];
            selectedItems.Add(o);
            temp.Remove(o);
            i++;
 }

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

3

ที่นี่คุณมีการนำไปใช้งานอย่างหนึ่งโดยอ้างอิงจากFisher-Yates Shuffleซึ่งความซับซ้อนของอัลกอริทึมคือ O (n) โดยที่ n คือเซตย่อยหรือขนาดตัวอย่างแทนที่จะเป็นขนาดรายการตามที่ John Shedletsky ชี้ให้เห็น

public static IEnumerable<T> GetRandomSample<T>(this IList<T> list, int sampleSize)
{
    if (list == null) throw new ArgumentNullException("list");
    if (sampleSize > list.Count) throw new ArgumentException("sampleSize may not be greater than list count", "sampleSize");
    var indices = new Dictionary<int, int>(); int index;
    var rnd = new Random();

    for (int i = 0; i < sampleSize; i++)
    {
        int j = rnd.Next(i, list.Count);
        if (!indices.TryGetValue(j, out index)) index = j;

        yield return list[index];

        if (!indices.TryGetValue(i, out index)) index = i;
        indices[j] = index;
    }
}

2

จากคำตอบของ Kyle นี่คือการติดตั้ง c # ของฉัน

/// <summary>
/// Picks random selection of available game ID's
/// </summary>
private static List<int> GetRandomGameIDs(int count)
{       
    var gameIDs = (int[])HttpContext.Current.Application["NonDeletedArcadeGameIDs"];
    var totalGameIDs = gameIDs.Count();
    if (count > totalGameIDs) count = totalGameIDs;

    var rnd = new Random();
    var leftToPick = count;
    var itemsLeft = totalGameIDs;
    var arrPickIndex = 0;
    var returnIDs = new List<int>();
    while (leftToPick > 0)
    {
        if (rnd.Next(0, itemsLeft) < leftToPick)
        {
            returnIDs .Add(gameIDs[arrPickIndex]);
            leftToPick--;
        }
        arrPickIndex++;
        itemsLeft--;
    }

    return returnIDs ;
}

2

วิธีนี้อาจเทียบเท่ากับของ Kyle

สมมติว่ารายการของคุณมีขนาด n และคุณต้องการองค์ประกอบ k

Random rand = new Random();
for(int i = 0; k>0; ++i) 
{
    int r = rand.Next(0, n-i);
    if(r<k) 
    {
        //include element i
        k--;
    }
} 

ทำงานเหมือนมีเสน่ห์ :)

-Alex Gilbert


1
นั่นดูเทียบเท่าฉัน เปรียบเทียบกับstackoverflow.com/a/48141/2449863 ที่
DCShannon

1

ทำไมไม่เป็นเช่นนี้:

 Dim ar As New ArrayList
    Dim numToGet As Integer = 5
    'hard code just to test
    ar.Add("12")
    ar.Add("11")
    ar.Add("10")
    ar.Add("15")
    ar.Add("16")
    ar.Add("17")

    Dim randomListOfProductIds As New ArrayList

    Dim toAdd As String = ""
    For i = 0 To numToGet - 1
        toAdd = ar(CInt((ar.Count - 1) * Rnd()))

        randomListOfProductIds.Add(toAdd)
        'remove from id list
        ar.Remove(toAdd)

    Next
'sorry i'm lazy and have to write vb at work :( and didn't feel like converting to c#

1

มันยากกว่าที่คิด ดูบทความ "Shuffling" ที่ยอดเยี่ยมจาก Jeff

ฉันเขียนบทความสั้น ๆ เกี่ยวกับเรื่องนั้นรวมถึงรหัส C #:
ส่งคืนชุดย่อยขององค์ประกอบ N ของอาร์เรย์ที่กำหนด


1

เป้าหมาย: เลือก N จำนวนรายการจากแหล่งรวบรวมโดยไม่ซ้ำกัน ฉันสร้างส่วนขยายสำหรับคอลเลกชันทั่วไป นี่คือวิธีที่ฉันทำ:

public static class CollectionExtension
{
    public static IList<TSource> RandomizeCollection<TSource>(this IList<TSource> source, int maxItems)
    {
        int randomCount = source.Count > maxItems ? maxItems : source.Count;
        int?[] randomizedIndices = new int?[randomCount];
        Random random = new Random();

        for (int i = 0; i < randomizedIndices.Length; i++)
        {
            int randomResult = -1;
            while (randomizedIndices.Contains((randomResult = random.Next(0, source.Count))))
            {
                //0 -> since all list starts from index 0; source.Count -> maximum number of items that can be randomize
                //continue looping while the generated random number is already in the list of randomizedIndices
            }

            randomizedIndices[i] = randomResult;
        }

        IList<TSource> result = new List<TSource>();
        foreach (int index in randomizedIndices)
            result.Add(source.ElementAt(index));

        return result;
    }
}

0

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

    private class QuestionSorter : IComparable<QuestionSorter>
    {
        public double SortingKey
        {
            get;
            set;
        }

        public Question QuestionObject
        {
            get;
            set;
        }

        public QuestionSorter(Question q)
        {
            this.SortingKey = RandomNumberGenerator.RandomDouble;
            this.QuestionObject = q;
        }

        public int CompareTo(QuestionSorter other)
        {
            if (this.SortingKey < other.SortingKey)
            {
                return -1;
            }
            else if (this.SortingKey > other.SortingKey)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
    }

การใช้งาน:

    List<QuestionSorter> unsortedQuestions = new List<QuestionSorter>();

    // add the questions here

    unsortedQuestions.Sort(unsortedQuestions as IComparer<QuestionSorter>);

    // select the first k elements

0

นี่คือวิธีการของฉัน (ข้อความเต็มได้ที่นี่http://krkadev.blogspot.com/2010/08/random-numbers-without-repetition.html )

มันควรจะทำงานใน O (K) แทน O (N) โดยที่ K คือจำนวนองค์ประกอบที่ต้องการและ N คือขนาดของรายการให้เลือก:

public <T> List<T> take(List<T> source, int k) {
 int n = source.size();
 if (k > n) {
   throw new IllegalStateException(
     "Can not take " + k +
     " elements from a list with " + n +
     " elements");
 }
 List<T> result = new ArrayList<T>(k);
 Map<Integer,Integer> used = new HashMap<Integer,Integer>();
 int metric = 0;
 for (int i = 0; i < k; i++) {
   int off = random.nextInt(n - i);
   while (true) {
     metric++;
     Integer redirect = used.put(off, n - i - 1);
     if (redirect == null) {
       break;
     }
     off = redirect;
   }
   result.add(source.get(off));
 }
 assert metric <= 2*k;
 return result;
}

0

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

import numpy

N = 20
K = 5

idx = np.arange(N)
numpy.random.shuffle(idx)

print idx[:K]

0

ฉันจะใช้วิธีการขยาย

    public static IEnumerable<T> TakeRandom<T>(this IEnumerable<T> elements, int countToTake)
    {
        var random = new Random();

        var internalList = elements.ToList();

        var selected = new List<T>();
        for (var i = 0; i < countToTake; ++i)
        {
            var next = random.Next(0, internalList.Count - selected.Count);
            selected.Add(internalList[next]);
            internalList[next] = internalList[internalList.Count - selected.Count];
        }
        return selected;
    }

0
public static IEnumerable<T> GetRandom<T>(this IList<T> list, int count, Random random)
    {
        // Probably you should throw exception if count > list.Count
        count = Math.Min(list.Count, count);

        var selectedIndices = new SortedSet<int>();

        // Random upper bound
        int randomMax = list.Count - 1;

        while (selectedIndices.Count < count)
        {
            int randomIndex = random.Next(0, randomMax);

            // skip over already selected indeces
            foreach (var selectedIndex in selectedIndices)
                if (selectedIndex <= randomIndex)
                    ++randomIndex;
                else
                    break;

            yield return list[randomIndex];

            selectedIndices.Add(randomIndex);
            --randomMax;
        }
    }

หน่วยความจำ: ~ ​​นับ
ความซับซ้อน: O (นับ2 )


0

เมื่อ N มีขนาดใหญ่มากวิธีปกติที่สุ่มสับตัวเลข N และเลือกพูดตัวเลข k แรกสามารถห้ามได้เนื่องจากความซับซ้อนของพื้นที่ อัลกอริทึมต่อไปนี้ต้องการเพียง O (k) สำหรับทั้งความซับซ้อนของเวลาและพื้นที่

http://arxiv.org/abs/1512.00501

def random_selection_indices(num_samples, N):
    modified_entries = {}
    seq = []
    for n in xrange(num_samples):
        i = N - n - 1
        j = random.randrange(i)

        # swap a[j] and a[i] 
        a_j = modified_entries[j] if j in modified_entries else j 
        a_i = modified_entries[i] if i in modified_entries else i

        if a_i != j:
            modified_entries[j] = a_i   
        elif j in modified_entries:   # no need to store the modified value if it is the same as index
            modified_entries.pop(j)

        if a_j != i:
            modified_entries[i] = a_j 
        elif i in modified_entries:   # no need to store the modified value if it is the same as index
            modified_entries.pop(i)
        seq.append(a_j)
    return seq

0

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

new int[5].Select(o => (int)(rnd.NextDouble() * maxIndex)).Select(i => YourIEnum.ElementAt(i))

สำหรับการใช้งานของฉันฉันมีรายการของ 100,000 องค์ประกอบและเพราะพวกเขาถูกดึงออกจากฐานข้อมูลฉันประมาณครึ่งหนึ่ง (หรือดีกว่า) เวลาเมื่อเทียบกับ rnd ในรายการทั้งหมด

การมีรายการขนาดใหญ่จะช่วยลดโอกาสในการซ้ำซ้อนอย่างมาก


วิธีนี้อาจมีองค์ประกอบซ้ำ !! การสุ่มในรายการหลุมอาจไม่ได้
AxelWass

อืมมม จริง ที่ฉันใช้มันไม่สำคัญว่า แก้ไขคำตอบเพื่อสะท้อนว่า
Wolf5

-1

นี่จะช่วยแก้ปัญหาของคุณ

var entries=new List<T>();
var selectedItems = new List<T>();


                for (var i = 0; i !=10; i++)
                {
                    var rdm = new Random().Next(entries.Count);
                        while (selectedItems.Contains(entries[rdm]))
                            rdm = new Random().Next(entries.Count);

                    selectedItems.Add(entries[rdm]);
                }

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