Получить 3 случайных, не дублирующих объекты из массива объектов - PullRequest
1 голос
/ 09 февраля 2020

Я пытаюсь случайным образом извлечь 3 объекта из массива объектов. Мой массив исходит из Обещания. ниже приведен код.

Код:

var getLocations = admin.firestore().collection('locations')
                getLocations = getLocations.where('area', '==', game_area)
                let locations = new Promise((resolve, reject) => {
                    getLocations.onSnapshot(snapshot => {
                        var results = []
                        snapshot.forEach(doc => {
                            let result = doc.data()
                            result.id = doc.id
                            results.push(result)
                        })
                        return resolve(results)
                    })
                })

                locations.then(() => {
                    // Shuffle locations array and get 3
                    const amountOfLocations = 3
                    const shuffled = locations.sort(() => 0.5 - Math.random())
                    // Get sub-array of first n elements after shuffled
                    let selectedLocations = shuffled.slice(0, amountOfLocations)
                }).catch(err => {
                    console.log(`ERROR IN QUERY: ${JSON.stringify(err)}`);
                })

Вот выборка массива, возвращенного из Обещания. ПРИМЕЧАНИЕ: я упростил результаты для этого форума.

locations=[ { name: 'Radio Coffee and Beer',
    timestamp: 1580676902040,
    id: '1KALzdUbf7y3ex2C' },
  { name: 'ZACH Theater',
    timestamp: 1580676946375,
    id: 'Lpxl8xLKCFDKxIhc' },
  { name: 'Alamo Draft House',
    timestamp: 1580676636972,
    id: 'b5F3Tq2y9cD4WQJq' },
  { name: 'Stevie Ray Vaughn Statue',
    timestamp: 1580676764120,
    id: 'bIUl4JU7kUSh6eyi' },
  { timestamp: 1580676967508,
    name: 'The Long Center',
    id: 'xJJOprzYDt3fWqVa' } ]

Как видите, этот образец массива имеет 5 объектов. Я хотел бы случайно вытащить 3 объекта из массива. Я думаю, что моя проблема связана с тем, как Promise возвращает массив, но я не уверен.

Ответы [ 2 ]

1 голос
/ 09 февраля 2020

Вы непосредственно работаете над местами, что является обещанием. При использовании обещания затем мы получим массив точных местоположений.

Попробуйте, как показано ниже,

locations
  .then((locs) => {
     // Shuffle locations array and get 3
     const amountOfLocations = 3
     const shuffled = locs.sort(() => 0.5 - Math.random())
     // Get sub-array of first n elements after shuffled
     let selectedLocations = shuffled.slice(0, amountOfLocations);
     console.log(selectedLocations); 
  })
  .catch(err => {
     console.log(`ERROR IN QUERY: ${JSON.stringify(err)}`);
  })
0 голосов
/ 09 февраля 2020

Как отмечают другие, фундаментальная проблема заключается в том, что функция, переданная в then, не передает информацию о местоположении. Код ниже исправляет это. Но это также решает еще одну, более тонкую проблему:

Ваша перетасовка на самом деле не случайна!

Старая статья Роба Вейра подробно объясняет почему это так, но вот краткий глянец этой идеи: ваш тасовщик использует несколько из того, что по сути является броском монеты в вызове на sort, 50-50 вариантов. Сколько бы из них ни было, количество раз, когда он выбирает одну конкретную комбинацию результатов (скажем, «Alamo Draft House», «Stev ie Ray Vaughn Statue», «Radio Coffee and Beer»), будет отображать некоторую долю время, значение которого должно иметь знаменатель, который является степенью двойки. Он может показывать половину времени, 3/8 или 117/1024 раз. Но он не может отображаться 1/10 времени или 1/60. Монетные сальто не могут вам этого дать. Существует десять различных комбинаций из трех предметов (или 60, если важен порядок). Таким образом, вы не можете случайным образом перетасовать набор предметов, просто подбрасывая монеты. Это реальная проблема, как показывает эта статья.

Наиболее распространенный способ перемешать - это использовать алгоритм Фишера-Йейтса . Приведенный ниже код изменяет это, чтобы прекратить тасование после выбора n элементов (три для вашего случая.)

Таким образом, этот код исправляет обратный вызов для обещаний и предоставляет метод shuffle, который справедливо выбирает подмножество .

// Dummy implementation for demo
const locations = new Promise((res, rej) => setTimeout(
  () => res([ { name: 'Radio Coffee and Beer', timestamp: 1580676902040, id: '1KALzdUbf7y3ex2C' }, { name: 'ZACH Theater', timestamp: 1580676946375, id: 'Lpxl8xLKCFDKxIhc' }, { name: 'Alamo Draft House', timestamp: 1580676636972, id: 'b5F3Tq2y9cD4WQJq' }, { name: 'Stevie Ray Vaughn Statue', timestamp: 1580676764120, id: 'bIUl4JU7kUSh6eyi' }, { timestamp: 1580676967508, name: 'The Long Center', id: 'xJJOprzYDt3fWqVa' } ]),
  50 // 50 ms delay
))

const randoms = (count) => (xs) => {
  const arr = xs .slice (0), max = arr.length - 1
  for (let i = max; i > max - count; i--) {
    const j = Math .floor (Math .random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr .slice (-count)
}          

locations 
  .then ((locs) => randoms (3) (locs))
  .then (console .log)
...