จะรับองค์ประกอบสุ่มจำนวนหนึ่งจากอาร์เรย์ได้อย่างไร?


107

ฉันกำลังดำเนินการเกี่ยวกับ 'วิธีเข้าถึงองค์ประกอบแบบสุ่มจากอาร์เรย์ในจาวาสคริปต์' ฉันพบลิงค์มากมายเกี่ยวกับเรื่องนี้ ชอบ: รับ ไอเท็มสุ่มจากอาร์เรย์ JavaScript

var item = items[Math.floor(Math.random()*items.length)];

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


5
เพียงแค่ดำเนินการหลาย ๆ ครั้ง?
Bergi

2
จากคำพูดนี้เราทำได้ไหม ?? วนซ้ำสร้างรายการซ้ำ
Shyam Dixit

1
จากคำสั่งที่แน่นอนคุณไม่สามารถรับมากกว่าหนึ่งองค์ประกอบ
เซบาสเตียน

1
คุณควรจะบอกว่าคุณไม่ต้องการซ้ำ จากนั้นตรวจสอบหมายเลขสุ่มเฉพาะใน O (1)? และคำตอบของฉันที่ สร้างหมายเลขที่ไม่ซ้ำกันภายในช่วง (0 - X) เก็บประวัติเพื่อป้องกันไม่ให้ซ้ำกัน
Bergi

1
ฉันสร้าง JsPerf เพื่อทดสอบวิธีแก้ปัญหาบางอย่างที่นี่ @ Bergi ดูเหมือนจะดีที่สุดโดยทั่วไปในขณะที่ของฉันทำงานได้ดีขึ้นหากคุณต้องการองค์ประกอบมากมายจากอาร์เรย์ jsperf.com/k-random-elements-from-array
Tibos

คำตอบ:


108

ลองใช้ฟังก์ชันที่ไม่ทำลายล้าง (และรวดเร็ว ) นี้:

function getRandom(arr, n) {
    var result = new Array(n),
        len = arr.length,
        taken = new Array(len);
    if (n > len)
        throw new RangeError("getRandom: more elements taken than available");
    while (n--) {
        var x = Math.floor(Math.random() * len);
        result[n] = arr[x in taken ? taken[x] : x];
        taken[x] = --len in taken ? taken[len] : len;
    }
    return result;
}

13
เฮ้ฉันแค่อยากจะบอกว่าฉันใช้เวลาประมาณสิบนาทีในการชื่นชมความงามของอัลกอริทึมนี้
Prajeeth Emanuel

ฉันขอแนะนำให้ใช้การใช้งานPythonหากคุณต้องการความเร็ว: jsperf.com/pick-random-elements-from-an-array
Derek 朕會功夫

@Derek 朕會功夫อาฉลาดที่ทำงานได้ดีกว่ามากสำหรับกลุ่มตัวอย่างขนาดเล็กจากช่วงใหญ่แน่นอน โดยเฉพาะอย่างยิ่งกับการใช้ ES6 Set(ซึ่งไม่มีใน '13: - /)
Bergi

@AlexWhite ขอบคุณสำหรับคำติชมฉันไม่อยากเชื่อเลยว่าข้อบกพร่องนี้จะหลบเลี่ยงทุกคนมาหลายปีแล้ว แก้ไขแล้ว. คุณควรจะโพสต์ความคิดเห็น แต่ไม่แนะนำให้แก้ไข
Bergi

2
@ cbdev420 ใช่มันเป็นเพียงบางส่วน (บางส่วน) fisher-yates สับเปลี่ยน
Bergi

196

เพียงสองบรรทัด:

// Shuffle array
const shuffled = array.sort(() => 0.5 - Math.random());

// Get sub-array of first n elements after shuffled
let selected = shuffled.slice(0, n);

สาธิต :


23
ดีมาก! แน่นอนว่าซับได้ 1 สาย:let random = array.sort(() => .5 - Math.random()).slice(0,n)
unitario

1
อัจฉริยะ! หรูหราสั้นและเรียบง่ายรวดเร็วโดยใช้ฟังก์ชันในตัว
Vlad

37
เป็นเรื่องดี แต่ยังห่างไกลจากการสุ่ม รายการแรกมีโอกาสถูกเลือกมากกว่ารายการสุดท้ายหลายเท่า ดูเหตุผลที่นี่: stackoverflow.com/a/18650169/1325646
pomber

สิ่งนี้ไม่เก็บการเรียงลำดับของอาร์เรย์ดั้งเดิม
almathie

4
สุดทึ่ง! หากคุณต้องการให้อาร์เรย์เหมือนเดิมคุณสามารถเปลี่ยนบรรทัดแรกได้ดังนี้ const shuffled = [... array] .sort (() => 0.5 - Math.random ());
Yair Levy

19

มีวิธีแก้ปัญหาเฉพาะซับเดียวที่นี่

 array.sort(() => Math.random() - Math.random()).slice(0, n)

2
ในขณะที่ใช้งานได้อาจช้าสำหรับอาร์เรย์ที่ใหญ่กว่า
philk

1
สิ่งนี้ไม่ได้ทำให้คุณได้รับการกระจายแบบสม่ำเสมอ ดูrobweir.com/blog/2010/02/microsoft-random-browser-ballot.html
JasonWoof

12

รับรายการสุ่ม 5 รายการโดยไม่ต้องเปลี่ยนอาร์เรย์เดิม:

const n = 5;
const sample = items
  .map(x => ({ x, r: Math.random() }))
  .sort((a, b) => a.r - b.r)
  .map(a => a.x)
  .slice(0, n);

(อย่าใช้สำหรับรายการใหญ่)


เราขอคำอธิบายที่ดีกว่านี้ได้ไหมว่ามันทำงานอย่างไร
Qasim

12

การย้าย.sampleจากไลบรารีมาตรฐาน Python:

function sample(population, k){
    /*
        Chooses k unique random elements from a population sequence or set.

        Returns a new list containing elements from the population while
        leaving the original population unchanged.  The resulting list is
        in selection order so that all sub-slices will also be valid random
        samples.  This allows raffle winners (the sample) to be partitioned
        into grand prize and second place winners (the subslices).

        Members of the population need not be hashable or unique.  If the
        population contains repeats, then each occurrence is a possible
        selection in the sample.

        To choose a sample in a range of integers, use range as an argument.
        This is especially fast and space efficient for sampling from a
        large population:   sample(range(10000000), 60)

        Sampling without replacement entails tracking either potential
        selections (the pool) in a list or previous selections in a set.

        When the number of selections is small compared to the
        population, then tracking selections is efficient, requiring
        only a small set and an occasional reselection.  For
        a larger number of selections, the pool tracking method is
        preferred since the list takes less space than the
        set and it doesn't suffer from frequent reselections.
    */

    if(!Array.isArray(population))
        throw new TypeError("Population must be an array.");
    var n = population.length;
    if(k < 0 || k > n)
        throw new RangeError("Sample larger than population or is negative");

    var result = new Array(k);
    var setsize = 21;   // size of a small set minus size of an empty list

    if(k > 5)
        setsize += Math.pow(4, Math.ceil(Math.log(k * 3) / Math.log(4)))

    if(n <= setsize){
        // An n-length list is smaller than a k-length set
        var pool = population.slice();
        for(var i = 0; i < k; i++){          // invariant:  non-selected at [0,n-i)
            var j = Math.random() * (n - i) | 0;
            result[i] = pool[j];
            pool[j] = pool[n - i - 1];       // move non-selected item into vacancy
        }
    }else{
        var selected = new Set();
        for(var i = 0; i < k; i++){
            var j = Math.random() * n | 0;
            while(selected.has(j)){
                j = Math.random() * n | 0;
            }
            selected.add(j);
            result[i] = population[j];
        }
    }

    return result;
}

การติดตั้งพอร์ตจากLib / random.py lib

หมายเหตุ:

  • setsizeถูกตั้งค่าตามคุณสมบัติใน Python เพื่อประสิทธิภาพ แม้ว่าจะไม่ได้รับการปรับแต่งสำหรับ JavaScript แต่อัลกอริทึมจะยังคงทำงานตามที่คาดไว้
  • คำตอบอื่น ๆ ที่อธิบายไว้ในหน้านี้ไม่ปลอดภัยตามข้อกำหนด ECMAScript เนื่องจากการใช้งานในทางที่ผิด Array.prototype.sortเนื่องจากการใช้ผิดวัตถุประสงค์ของ อย่างไรก็ตามอัลกอริทึมนี้รับประกันว่าจะสิ้นสุดในเวลาที่ จำกัด
  • เบราว์เซอร์รุ่นเก่าที่ไม่ได้มีการSetดำเนินการชุดที่สามารถแทนที่ด้วยArrayและแทนที่ด้วย.has(j).indexOf(j) > -1

ประสิทธิภาพเทียบกับคำตอบที่ยอมรับ:


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

10

สร้าง funcion ซึ่งทำเช่นนั้น:

var getMeRandomElements = function(sourceArray, neededElements) {
    var result = [];
    for (var i = 0; i < neededElements; i++) {
        result.push(sourceArray[Math.floor(Math.random()*sourceArray.length)]);
    }
    return result;
}

คุณควรตรวจสอบด้วยว่า sourceArray มีองค์ประกอบเพียงพอที่จะส่งคืนหรือไม่ และหากคุณต้องการส่งคืนองค์ประกอบที่ไม่ซ้ำกันคุณควรลบองค์ประกอบที่เลือกออกจาก sourceArray


คำตอบที่ดี! ดูคำตอบของฉันคัดลอกโค้ดของคุณและเพิ่มฟังก์ชัน "เฉพาะองค์ประกอบที่ไม่ซ้ำกัน"
evilReiko

1
ฟังก์ชันนี้สามารถส่งคืนองค์ประกอบเดียวกันได้sourceArrayหลายครั้ง
Sampo

6

ไวยากรณ์ ES6

const pickRandom = (arr,count) => {
  let _arr = [...arr];
  return[...Array(count)].map( ()=> _arr.splice(Math.floor(Math.random() * _arr.length), 1)[0] ); 
}

6

หากคุณต้องการสุ่มรับไอเท็มจากอาร์เรย์แบบวนซ้ำโดยไม่ซ้ำกันคุณสามารถลบไอเท็มที่เลือกออกจากอาร์เรย์ด้วยsplice:

var items = [1, 2, 3, 4, 5];
var newItems = [];

for (var i = 0; i < 3; i++) {
  var idx = Math.floor(Math.random() * items.length);
  newItems.push(items[idx]);
  items.splice(idx, 1);
}

console.log(newItems);


1
ในคำสั่ง items.splice (idx, 1) เหตุใดคุณจึงใช้ '1' นี้ ประกบ ??
Shyam Dixit

2
ยัม Dixitตามเอกสาร MDN1เป็นdeleteCountแสดงให้เห็นจำนวนขององค์ประกอบอาร์เรย์เก่าที่จะลบ (บังเอิญฉันลดสองบรรทัดสุดท้ายเป็นnewItems.push(items.splice(idx, 1)[0]))
Kurt Peek

5

lodash ( https://lodash.com/ ) _.sampleและ_.sampleSize .

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

_.sample([1, 2, 3, 4]);
// => 2

_.sampleSize([1, 2, 3], 2);
// => [3, 1]
 
_.sampleSize([1, 2, 3], 3);
// => [2, 3, 1]

คืออะไร_? ไม่ใช่วัตถุ Javascript มาตรฐาน
vanowm

2
Array.prototype.getnkill = function() {
    var a = Math.floor(Math.random()*this.length);
    var dead = this[a];
    this.splice(a,1);
    return dead;
}

//.getnkill() removes element in the array 
//so if you like you can keep a copy of the array first:

//var original= items.slice(0); 


var item = items.getnkill();

var anotheritem = items.getnkill();

2

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

function sampleArr<T>(arr: T[], size: number): T[] {
  const setOfIndexes = new Set<number>();
  while (setOfIndexes.size < size && setOfIndexes.size < arr.length) {
    setOfIndexes.add(randomIntFromInterval(0, arr.length - 1));
  }
  return Array.from(setOfIndexes.values()).map(i => arr[i]);
}

const randomIntFromInterval = (min: number, max: number): number =>
  Math.floor(Math.random() * (max - min + 1) + min);

2

ในคำตอบนี้ฉันต้องการแบ่งปันการทดสอบกับคุณว่าฉันต้องรู้วิธีที่ดีที่สุดที่ให้โอกาสเท่ากันที่องค์ประกอบทั้งหมดจะมี subarray แบบสุ่ม

วิธี 01

array.sort(() => Math.random() - Math.random()).slice(0, n)

เมื่อใช้วิธีนี้องค์ประกอบบางอย่างมีโอกาสสูงกว่าเมื่อเปรียบเทียบกับองค์ประกอบอื่น ๆ

calculateProbability = function(number=0 ,iterations=10000,arraySize=100) { 
let occ = 0 
for (let index = 0; index < iterations; index++) {
   const myArray= Array.from(Array(arraySize).keys()) //=> [0, 1, 2, 3, 4, ... arraySize]
   
  /** Wrong Method */
    const arr = myArray.sort(function() {
     return val= .5 - Math.random();
      });
     
  if(arr[0]===number) {
    occ ++
    }

    
}

console.log("Probability of ",number, " = ",occ*100 /iterations,"%")

}

calculateProbability(0)
calculateProbability(0)
calculateProbability(0)
calculateProbability(50)
calculateProbability(50)
calculateProbability(50)
calculateProbability(25)
calculateProbability(25)
calculateProbability(25)

วิธีที่ 2

เมื่อใช้วิธีนี้องค์ประกอบมีความน่าจะเป็นเหมือนกัน:

 const arr = myArray
      .map((a) => ({sort: Math.random(), value: a}))
      .sort((a, b) => a.sort - b.sort)
      .map((a) => a.value)

calculateProbability = function(number=0 ,iterations=10000,arraySize=100) { 
let occ = 0 
for (let index = 0; index < iterations; index++) {
   const myArray= Array.from(Array(arraySize).keys()) //=> [0, 1, 2, 3, 4, ... arraySize]
   

  /** Correct Method */
  const arr = myArray
  .map((a) => ({sort: Math.random(), value: a}))
  .sort((a, b) => a.sort - b.sort)
  .map((a) => a.value)
    
  if(arr[0]===number) {
    occ ++
    }

    
}

console.log("Probability of ",number, " = ",occ*100 /iterations,"%")

}

calculateProbability(0)
calculateProbability(0)
calculateProbability(0)
calculateProbability(50)
calculateProbability(50)
calculateProbability(50)
calculateProbability(25)
calculateProbability(25)
calculateProbability(25)

คำตอบที่ถูกต้องอยู่ในลิงค์ต่อไปนี้: https://stackoverflow.com/a/46545530/3811640


2


รูปแบบการเขียนโปรแกรมฟังก์ชันที่ไม่ทำลายล้างในปี2020ทำงานในบริบทที่ไม่เปลี่ยนรูป

const _randomslice = (ar, size) => {
  let new_ar = [...ar];
  new_ar.splice(Math.floor(Math.random()*ar.length),1);
  return ar.length <= (size+1) ? new_ar : _randomslice(new_ar, size);
}


console.log(_randomslice([1,2,3,4,5],2));


ฉันตระหนักดีว่าฟังก์ชันนี้ไม่ได้สร้างอาร์เรย์แบบสุ่มที่เป็นไปได้ทั้งหมดจากอาร์เรย์ต้นทาง ในโลกอื่นผลลัพธ์ไม่สุ่มเท่าที่ควร ... มีความคิดที่จะปรับปรุงหรือไม่?
MAMY Sébastien

_shuffleฟังก์ชั่นอยู่ที่ไหน
vanowm

นอกจากนี้เมื่อsize >= ar.lengthผลจะเป็นsize-1
idleberg

1

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

หากคุณต้องการองค์ประกอบแบบสุ่มที่ไม่ซ้ำกันคุณสามารถสับเปลี่ยนอาร์เรย์ของคุณแล้วรับได้มากเท่าที่คุณต้องการ:

function shuffle(array) {
    var counter = array.length, temp, index;

    // While there are elements in the array
    while (counter--) {
        // Pick a random index
        index = (Math.random() * counter) | 0;

        // And swap the last element with it
        temp = array[counter];
        array[counter] = array[index];
        array[index] = temp;
    }

    return array;
}

var arr = [0,1,2,3,4,5,7,8,9];

var randoms = shuffle(arr.slice(0)); // array is cloned so it won't be destroyed
randoms.length = 4; // get 4 random elements

สาธิต: http://jsbin.com/UHUHuqi/1/edit

ฟังก์ชั่นสุ่มจากที่นี่: https://stackoverflow.com/a/6274398/1669279


ขึ้นอยู่กับเปอร์เซ็นต์ของรายการสุ่มที่ต้องการจากอาร์เรย์ หากคุณต้องการ 9 องค์ประกอบแบบสุ่มจากอาร์เรย์องค์ประกอบ 10 องค์ประกอบมันจะเร็วกว่าการแยกองค์ประกอบแบบสุ่ม 9 รายการออกจากกัน หากเปอร์เซ็นต์นี้มีประโยชน์น้อยกว่า 50% แสดงว่ามีกรณีการใช้งานที่วิธีนี้เร็วที่สุด มิฉะนั้นฉันยอมรับว่ามันไม่มีประโยชน์ :)
Tibos

ฉันหมายความว่าการสับ 9 องค์ประกอบนั้นเร็วกว่าการสับ 10 องค์ประกอบ Btw ฉันมั่นใจว่า OP ไม่ต้องการทำลายอาร์เรย์อินพุตของเขา…
Bergi

ฉันไม่คิดว่าฉันเข้าใจว่าการสับ 9 องค์ประกอบช่วยในปัญหานี้ได้อย่างไร ฉันทราบดีว่าหากคุณต้องการอาร์เรย์มากกว่าครึ่งหนึ่งคุณสามารถแบ่งองค์ประกอบแบบสุ่มออกได้จนกว่าคุณจะยังคงมีจำนวนที่คุณต้องการจากนั้นสับเปลี่ยนเพื่อให้ได้ลำดับแบบสุ่ม ฉันพลาดอะไรไปหรือเปล่า? PS: แก้ไขการทำลายอาร์เรย์ขอบคุณ
Tibos

ไม่จำเป็นต้องทำอะไรกับ "ครึ่งหนึ่ง" คุณเพียงแค่ต้องทำงานให้มากพอ ๆ กับองค์ประกอบที่คุณต้องการกลับมาคุณไม่จำเป็นต้องปฏิบัติต่ออาร์เรย์ทั้งหมด ณ จุดใดก็ได้ รหัสปัจจุบันของคุณมีความซับซ้อนของO(n+k)(n องค์ประกอบในอาร์เรย์คุณต้องการ k ของพวกเขา) ในขณะที่O(k)เป็นไปได้ (และเหมาะสมที่สุด)
Bergi

1
ตกลงรหัสของคุณมีลักษณะO(2n)ที่สามารถลดลงได้มากกว่าO(n+k)ถ้าคุณเปลี่ยนการวนซ้ำwhile (counter-- > len-k)และนำองค์ประกอบสุดท้าย (แทนที่จะเป็นรายการแรก) kออกจากมัน แน่นอนsplice(i, 1)ไม่มีO(1)แต่O(k)ยังสามารถแก้ปัญหาได้ (ดูคำตอบของฉัน) อย่างไรก็ตามความซับซ้อนของพื้นที่ยังคงอยู่อย่างO(n+k)น่าเสียดาย แต่อาจO(2k)ขึ้นอยู่กับการใช้อาร์เรย์แบบกระจัดกระจาย
Bergi

1

ฉันต้องการฟังก์ชั่นในการแก้ปัญหาประเภทนี้ดังนั้นฉันจึงแบ่งปันที่นี่

    const getRandomItem = function(arr) {
        return arr[Math.floor(Math.random() * arr.length)];
    }

    // original array
    let arr = [4, 3, 1, 6, 9, 8, 5];

    // number of random elements to get from arr
    let n = 4;

    let count = 0;
    // new array to push random item in
    let randomItems = []
    do {
        let item = getRandomItem(arr);
        randomItems.push(item);
        // update the original array and remove the recently pushed item
        arr.splice(arr.indexOf(item), 1);
        count++;
    } while(count < n);

    console.log(randomItems);
    console.log(arr);

หมายเหตุ: ถ้าn = arr.lengthโดยพื้นฐานแล้วคุณกำลังสับอาร์เรย์arrและrandomItemsส่งคืนอาร์เรย์แบบสับนั้น

การสาธิต


1

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

// Chooses k unique random elements from pool.
function sample(pool, k, destructive) {
    var n = pool.length;
    
    if (k < 0 || k > n)
        throw new RangeError("Sample larger than population or is negative");
    
    if (destructive || n <= (k <= 5 ? 21 : 21 + Math.pow(4, Math.ceil(Math.log(k*3) / Math.log(4))))) {
        if (!destructive)
            pool = Array.prototype.slice.call(pool);
        for (var i = 0; i < k; i++) { // invariant: non-selected at [i,n)
            var j = i + Math.random() * (n - i) | 0;
            var x = pool[i];
            pool[i] = pool[j];
            pool[j] = x;
        }
        pool.length = k; // truncate
        return pool;
    } else {
        var selected = new Set();
        while (selected.add(Math.random() * n | 0).size < k) {}
        return Array.prototype.map.call(selected, i => pool[i]);
    }
}

เมื่อเปรียบเทียบกับการใช้งานของ Derek อัลกอริทึมแรกนั้นเร็วกว่ามากใน Firefox ในขณะที่ Chrome ทำงานช้าลงเล็กน้อยแม้ว่าตอนนี้จะมีตัวเลือกในการทำลายล้างซึ่งเป็นตัวเลือกที่มีประสิทธิภาพมากที่สุด อัลกอริทึมที่สองเร็วขึ้นเพียง 5-15% ฉันพยายามที่จะไม่ให้ตัวเลขที่เป็นรูปธรรมเนื่องจากมันแตกต่างกันไปขึ้นอยู่กับ k และ n และอาจไม่มีความหมายอะไรในอนาคตกับเบราว์เซอร์เวอร์ชันใหม่

ฮิวริสติกที่ทำให้ตัวเลือกระหว่างอัลกอริทึมนั้นมาจากโค้ด Python ฉันปล่อยมันไว้ตามที่เป็นอยู่แม้ว่าบางครั้งมันจะเลือกอันที่ช้ากว่า ควรปรับให้เหมาะสมสำหรับ JS แต่เป็นงานที่ซับซ้อนเนื่องจากประสิทธิภาพของกรณีมุมขึ้นอยู่กับเบราว์เซอร์และขึ้นอยู่กับเวอร์ชัน ตัวอย่างเช่นเมื่อคุณพยายามเลือก 20 จาก 1,000 หรือ 1,050 ระบบจะเปลี่ยนไปใช้อัลกอริทึมแรกหรือที่สองตามลำดับ ในกรณีนี้ตัวแรกทำงานเร็วกว่าตัวที่สองใน Chrome 80 ถึง 2 เท่า แต่ช้ากว่า 3 เท่าใน Firefox 74


มีข้อผิดพลาดlog(k*3, 4)เนื่องจาก JS ไม่มีbaseอาร์กิวเมนต์ ควรจะlog(k*3)/log(4)
เสียโฉม

นอกจากนี้ฉันเห็นข้อเสียในส่วนที่คุณใช้ซ้ำpoolเป็นไฟล์result. เนื่องจากคุณตัดทอนpoolจึงไม่สามารถใช้เป็นแหล่งที่มาสำหรับการสุ่มตัวอย่างได้อีกต่อไปและในครั้งต่อไปที่คุณใช้sampleคุณจะต้องสร้างใหม่poolจากแหล่งที่มาบางแห่งอีกครั้ง การใช้งานของ Derek จะสลับพูลเท่านั้นดังนั้นจึงสามารถใช้ซ้ำได้อย่างสมบูรณ์แบบสำหรับการสุ่มตัวอย่างโดยไม่ต้องสร้างใหม่ และฉันเชื่อว่านี่เป็นกรณีที่ใช้บ่อยที่สุด
เสียโฉม

@ ติดขอบคุณแก้ไขlogในรหัสของฉันและเดเร็ก สำหรับการนำกลับมาใช้ใหม่poolอย่าเปิดใช้งานdestructiveตัวเลือกนั้นpoolอาร์กิวเมนต์จะถูกเงาด้วยสำเนา
ผู้ใช้

0

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

function getNRandomValuesFromArray(srcArr, n) {
    // making copy to do not affect original srcArray
    srcArr = srcArr.slice();
    resultArr = [];
    // while srcArray isn't empty AND we didn't enough random elements
    while (srcArr.length && resultArr.length < n) {
        // remove one element from random position and add this element to the result array
        resultArr = resultArr.concat( // merge arrays
            srcArr.splice( // extract one random element
                Math.floor(Math.random() * srcArr.length),
                1
            )
        );
    }

    return resultArr;
}


ยินดีต้อนรับสู่ SO! เมื่อโพสต์คำตอบสิ่งสำคัญคือต้องระบุว่าโค้ดของคุณทำงานอย่างไรและ / หรือวิธีแก้ปัญหาของ OP :)
Joel

0

2019

สิ่งนี้เหมือนกับคำตอบของLaurynas Mališauskasเพียงแค่ว่าองค์ประกอบนั้นไม่ซ้ำกัน (ไม่มีรายการที่ซ้ำกัน)

var getMeRandomElements = function(sourceArray, neededElements) {
    var result = [];
    for (var i = 0; i < neededElements; i++) {
    var index = Math.floor(Math.random() * sourceArray.length);
        result.push(sourceArray[index]);
        sourceArray.splice(index, 1);
    }
    return result;
}

ตอนนี้เพื่อตอบคำถามเดิม "วิธีรับองค์ประกอบแบบสุ่มหลายรายการโดย jQuery" ได้ที่นี่:

var getMeRandomElements = function(sourceArray, neededElements) {
    var result = [];
    for (var i = 0; i < neededElements; i++) {
    var index = Math.floor(Math.random() * sourceArray.length);
        result.push(sourceArray[index]);
        sourceArray.splice(index, 1);
    }
    return result;
}

var $set = $('.someClass');// <<<<< change this please

var allIndexes = [];
for(var i = 0; i < $set.length; ++i) {
    allIndexes.push(i);
}

var totalRandom = 4;// <<<<< change this please
var randomIndexes = getMeRandomElements(allIndexes, totalRandom);

var $randomElements = null;
for(var i = 0; i < randomIndexes.length; ++i) {
    var randomIndex = randomIndexes[i];
    if($randomElements === null) {
        $randomElements = $set.eq(randomIndex);
    } else {
        $randomElements.add($set.eq(randomIndex));
    }
}

// $randomElements is ready
$randomElements.css('backgroundColor', 'red');

0

นี่คือฟังก์ชั่นที่ฉันใช้ที่ช่วยให้คุณสามารถสุ่มตัวอย่างอาร์เรย์โดยมีหรือไม่มีการแทนที่:

  // Returns a random sample (either with or without replacement) from an array
  const randomSample = (arr, k, withReplacement = false) => {
    let sample;
    if (withReplacement === true) {  // sample with replacement
      sample = Array.from({length: k}, () => arr[Math.floor(Math.random() *  arr.length)]);
    } else { // sample without replacement
      if (k > arr.length) {
        throw new RangeError('Sample size must be less than or equal to array length         when sampling without replacement.')
      }
      sample = arr.map(a => [a, Math.random()]).sort((a, b) => {
        return a[1] < b[1] ? -1 : 1;}).slice(0, k).map(a => a[0]); 
      };
    return sample;
  };

ใช้งานง่าย:

โดยไม่ต้องเปลี่ยน (พฤติกรรมเริ่มต้น)

randomSample([1, 2, 3], 2) อาจกลับมา [2, 1]

ด้วยการเปลี่ยน

randomSample([1, 2, 3, 4, 5, 6], 4) อาจกลับมา [2, 3, 3, 2]


0
var getRandomElements = function(sourceArray, requiredLength) {
    var result = [];
    while(result.length<requiredLength){
        random = Math.floor(Math.random()*sourceArray.length);
        if(result.indexOf(sourceArray[random])==-1){
            result.push(sourceArray[random]);
        }
    }
    return result;
}

0

ฉันไม่อยากจะเชื่อเลยว่าไม่มีใครไม่ได้พูดถึงวิธีนี้สะอาดและตรงไปตรงมา

const getRnd = (a, n) => new Array(n).fill(null).map(() => a[Math.floor(Math.random() * a.length)]);

คุณไม่แน่ใจว่าสองรายการจะไม่ซ้ำกัน
Valery

-2

นี่คือคำตอบที่ถูกต้องที่สุดและจะให้องค์ประกอบแบบสุ่ม + ไม่ซ้ำกับคุณ

function randomize(array, n)
{
    var final = [];
    array = array.filter(function(elem, index, self) {
        return index == self.indexOf(elem);
    }).sort(function() { return 0.5 - Math.random() });

    var len = array.length,
    n = n > len ? len : n;

    for(var i = 0; i < n; i ++)
    {
        final[i] = array[i];
    }

    return final;
}

// randomize([1,2,3,4,5,3,2], 4);
// Result: [1, 2, 3, 5] // Something like this

มีบางอย่างแปลก ๆ เกิดขึ้นกับการสุ่มในอันนี้ - ฉันได้ผลลัพธ์เดียวกันแสดง 6 จาก 9 ครั้ง (โดย n จาก 8 และขนาดอาร์เรย์ 148) คุณอาจจะคิดเกี่ยวกับการเปลี่ยนไปFisher-Yatesวิธี; มันคือสิ่งที่ฉันทำและตอนนี้ทำงานได้ดีขึ้นมาก
asetniop

สิ่งนี้ใช้เวลากำลังสองเนื่องจากจะทำการตรวจสอบความเป็นเอกลักษณ์ที่ไม่ดีและไม่มีโอกาสที่เท่าเทียมกันในการเลือกทุกรายการเนื่องจากเรียงลำดับด้วยการเปรียบเทียบแบบสุ่ม
Ry-

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