我试图从包含电影对象的对象中挑选一张随机电影。我需要能够反复调用该函数,直到每部电影都被使用为止。
我有这个函数,但它不起作用,因为即使内部函数调用自身,外部函数也没有返回,因为结果不是唯一的。
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
答案 0 :(得分:1)
T.J。克劳德已经给出了一个很好的答案,但我想展示一种使用OO解决问题的另一种方法。
您可以创建一个包装数组的对象,并确保每次都返回一个随机的未使用项。我创建的版本是循环的,这意味着它无限循环集合,但如果你想停止循环,你可以只跟踪选择的电影数量,并在达到电影总数后停止。
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
然后你可以简单地做一些事情:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
请注意,如果您在项目中循环,我实施的优势在于,即使在新周期开始时,同一项也不会连续两次返回。
答案 1 :(得分:0)
更新:您已经说过可以修改film
个对象,这样可以简化操作:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
这样做的好处是,如果您使用相同的movies
对象调用它并不重要。
注意安全阀。基本上,随着观看电影的数量接近电影的总数,我们随机选择候选人的几率变得更小。因此,如果我们在循环两次迭代次数之后我们没有做到这一点,那么我们放弃并选择第一个,如果有的话。
原创 (不修改电影对象)
如果你无法修改电影对象,你仍然需要watchedFilms
数组,但这很简单:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
请注意,与您的代码一样,这假定每次都使用相同的 getRandomFilm
对象调用movies
。