Search code examples
pythonrandomflaskqueuedeque

How to choose a random but non-recent asset from a list?


I have the issue of trying to pull a "random" item out of a database in my Flask app. This function only needs to return a video that wasn't recently watched by the user. I am not worried about multiple users right now. My current way of doing this does not work. This is what I am using:

@app.route('/_new_video')
def new_video():

Here's the important part I'm asking about:

    current_id = request.args.get('current_id')
    video_id = random.choice(models.Video.query.all()) # return list of all video ids
    while True:
        if video_id != current_id:
            new_video = models.Video.query.get(video_id)

and then I return it:

            webm = new_video.get_webm() #returns filepath in a string
            mp4 = new_video.get_mp4() #returns filepath in a string 
            return jsonify(webm=webm,mp4=mp4,video_id=video_id)

The random range starts at 1 because the first asset was deleted from the database, so the number 0 isn't associated with an id. Ideally, the user would not get a video they had recently watched.


Solution

  • I recommend using a collections.deque to store a recently watched list. It saves a list like collection of items, and as you add to it, if it gets to its max length, it automatically drops the oldest items, on a first in, first out basis.

    import collections
    

    And here's a generator that you can use to get random vids, that haven't been recently watched. The denom argument will allow you to change the length of the recently watched list because it's used to determine the max length of your recently_watched as a fraction of your list of vids.

    def gen_random_vid(vids, denom=2):
        '''return  a random vid id that hasn't been recently watched'''
        recently_watched = collections.deque(maxlen=len(vids)//denom)
        while True:
            selection = random.choice(vids)
            if selection not in recently_watched:
                 yield selection
                 recently_watched.append(selection)
    

    I'll create a quick list to demo it:

    vids = ['vid_' + c for c in 'abcdefghijkl']
    

    And here's the usage:

    >>> recently_watched_generator = gen_random_vid(vids)
    >>> next(recently_watched_generator)
    'vid_e'
    >>> next(recently_watched_generator)
    'vid_f'
    >>> for _ in range(10):
    ...     print(next(recently_watched_generator))
    ... 
    vid_g
    vid_d
    vid_c
    vid_f
    vid_e
    vid_g
    vid_a
    vid_f
    vid_e
    vid_c