Grab 5 Unique Items from a List

Hey all,

Going in circles on this one so I figured Id ask here to see if anyone had a simple solution to this. I have a master class list of quests, I would like to get 5 random unique quests from this list and add them into a new list without ever removing them from the master list. The below code grabs 5 of the quests but they wont always be unique. I tried to make a copy of the list using
List tempList = new List(availMissions) but since its a Class list it makes only instances of the master list so If I remove them from the tempList it also removes them from the master list :frowning:

I am sure I am missing something simple that I just can’t seem to find on google, so hoping someone knows a better solution.

public List<Quest> availMissions = new List<Quest>();

void LoadNewMissions()
    {
      
        Debug.Log("Loading new missions");     
        //Get 5 more quests
        Quest newQuest1 = availMissions[Random.Range(0, availMissions.Count - 1)];
        Quest newQuest2 = availMissions[Random.Range(0, availMissions.Count - 1)];
        Quest newQuest3 = availMissions[Random.Range(0, availMissions.Count - 1)];
        Quest newQuest4 = availMissions[Random.Range(0, availMissions.Count - 1)];
        Quest newQuest5 = availMissions[Random.Range(0, availMissions.Count - 1)];
        //Add 5 quests to a new list
        stationMissions.Add(newQuest1);
        stationMissions.Add(newQuest2);
        stationMissions.Add(newQuest3);
        stationMissions.Add(newQuest4);
        stationMissions.Add(newQuest5);
    }

Thanks!

What you could try out is ‘cloning’ your master list so that tempList is a duplicate and then in the duplicate you can easily remove items. There should be several examples around the net that show cloning a list.

I think this is what you’re after. I change List to a List so I could test it. change it back to quest and it should work

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class QuestLists : MonoBehaviour {

    public List<string> availMissions = new List<string>(); //list from gamemaster?
    public List<string> stationMissions = new List<string>();


    // Use this for initialization
    void Start () {
        //add dummy data for testing
        availMissions.Add("string0");
        availMissions.Add("string1");
        availMissions.Add("string2");
        availMissions.Add("string3");
        availMissions.Add("string4");
        availMissions.Add("string5");
        availMissions.Add("string6");
        availMissions.Add("string7");
        availMissions.Add("string8");
        availMissions.Add("string9");

        LoadNewMissions();

    }


    void LoadNewMissions()
    {
        //temp list
        List<string> tempAvailMissions = new List<string>();

        //add all elements from gamemast list to the temp list
        for (int i = 0; i < availMissions.Count; i++)
        {
            tempAvailMissions.Add(availMissions[i]);
        }

        //pick 5 from the temp list, store them in stationMissions list
        for (int i = 0; i < 5; i++)
        {
            int rand = Random.Range(0, tempAvailMissions.Count - 1);
            stationMissions.Add(tempAvailMissions[rand]);
            tempAvailMissions.RemoveAt(rand);
        }
      
    }
}
1 Like

You can use linq and a simple shuffle like so:

Here’s the shuffle method, I took this from my ArrayUtil:

    public static IEnumerable<T> Shuffled<T>(this IEnumerable<T> coll)
    {
        if (coll == null) throw new System.ArgumentNullException("coll");

        IList<T> buffer = coll.ToList();
        int j;
        for (int i = 0; i < buffer.Count; i++)
        {
            j = Random.Range(i, buffer.Count);
            yield return buffer[j];
            buffer[j] = buffer[i];
        }
    }

then you can just say:

using System.Linq;

//...

var missions = availMissions.Shuffled().Take(5);
2 Likes

This is not true, it works.

The problem that you describe would occur if you did this:

List<Quest> copiedReference = AvailableQuests;

However, if you create a shallow copy:

List<Quest> shallowCopy = new List<Quest>(AvailableQuests);

You can manipulate it without affecting the original.

Now in regards to this:

Quest newQuest1 = availMissions[Random.Range(0, availMissions.Count - 1)];

Random.Range excludes the max, so you want to pass the number of elements (not count -1).

1 Like

Thanks for this! A Deep Copy is what I needed but only just learned the term (explains why my googling was failing). Seems a more simplified way to do the random choice as well. Ill give this a go when I get home from work.

He doesn’t do a deep copy. It’s a shallow copy, which can be written in one line using AddRange(…) or using the constructor.

Take a look at the shuffling approach, it avoids calling RemoveAt(INDEX) which internally has to shift all elements (except when it’s the last element you remove, usually not a big deal unless you do it very often).