Radu094
Radu094

Reputation: 28424

Select a random element from IList<> other than this one

I have this pattern where I need to select any random element from a list, other than the current one (passed as argument). I came up with this method:

public Element GetAnyoneElseFromTheList(Element el)
{
  Element returnElement = el;
  Random rndElement = new Random();
  if (this.ElementList.Count>2) 
  {
  while (returnElement == el) 
  {
    returnElement = this.ElementList[rndElement.Next(0,this.ElementList.Count)];  
  }

  return returnElement;
  }
 else return null;
}

But that while loop has been bothering me for days and nights and I need to get some sleep. Any other good approaches to this? ie. something that returns in a fixed-number-of-steps ?

Edit : In my case, the list is guaranteed to contain the "el" Element to avoid, and the list contains no duplicates, but it would be interesting to see some more general cases aswell.

Upvotes: 2

Views: 9087

Answers (6)

Stephen Lacy
Stephen Lacy

Reputation:

public Element GetAnyoneElseFromTheList(Element el)
{
  if(this.ElementList.Count < 2) return null;
  Random rndElement = new Random();
  int random = rndElement.Next(0,this.ElementList.Count -1);
  if(random >= this.ElementList.indexOf(el)) random += 1;
  return this.ElementList[random];
}

Get a random number between 0 and the list length minus 2.

If that number is greater or equal to the index of your element add one to that number.

Return the element at the index of that number

Edit: Someone mentioned in a comment which they then deleted that this doesn't work so well if you have duplicates. In which case the best solution would actually be.

public Element GetAnyoneElseFromTheList(int elId)
{
  if(elId >= this.ElementList.Count) throw new ArgumentException(...)
  if(this.ElementList.Count < 2) return null;
  Random rndElement = new Random();
  int random = rndElement.Next(0,this.ElementList.Count -1);
  if(random >= elId) random += 1;
  return this.ElementList[random];
}

Edit 2: Another alternative for duplicate elements is you could use an optimised shuffle (random sort) operation on a cloned version of the list and then foreach through the list. The foreach would iterate up to the number of duplicate elements there were in the list. It all comes down to how optimised is your shuffle algorithm then.

Upvotes: 8

LukeH
LukeH

Reputation: 269428

public Element GetAnyoneElseFromTheList(Element el)
{
    // first create a new list and populate it with all non-matching elements
    var temp = this.ElementList.Where(i => i != el).ToList();

    // if we have some matching elements then return one of them at random
    if (temp.Count > 0) return temp[new Random().Next(0, temp.Count)];

    // if there are no matching elements then take the appropriate action
    // throw an exception, return null, return default(Element) etc
    throw new Exception("No items found!");
}

If you're not using C#3 and/or .NET 3.5 then you'll need to do it slightly differently:

public Element GetAnyoneElseFromTheList(Element el)
{
    // first create a new list and populate it with all non-matching elements
    List<Element> temp = new List<Element>();
    this.ElementList.ForEach(delegate(int i) { if (i != el) temp.Add(i); });

    // if we have some matching elements then return one of them at random
    if (temp.Count > 0) return temp[new Random().Next(0, temp.Count)];

    // if there are no matching elements then take the appropriate action
    // throw an exception, return null, return default(Element) etc
    throw new Exception("No items found!");
}

Upvotes: 2

Guffa
Guffa

Reputation: 700382

First count how many matching elements there are to avoid. Then pick a random number based on the remaining items and locate the picked item:

public Element GetAnyoneElseFromTheList(Element el) {
   int cnt = this.ElementList.Count(e => e != el);
   if (cnt < 1) return null;
   Random rand = new Random();
   int num = rand.Next(cnt);
   index = 0;
   while (num > 0) {
      if (this.ElementList[index] != el) num--;
      index++;
   }
   return this.ElementList[index];
}

Upvotes: 2

James
James

Reputation: 82096

I have had to resolve a similar problem. Here's what I would do:

public Element GetAnyoneElseFromTheList(Element el)
{   
     // take a copy of your element list, ignoring the currently selected element
     var temp = this.ElementList.Where(e => e != el).ToList();
     // return a randomly selected element
     return temp[new Random().Next(0, temp.Count)];
}

Upvotes: 2

Agg
Agg

Reputation: 465

public Element GetAnyoneElseFromTheList(Element el)
{
  Random rndElement = new Random();
  int index;
  if (this.ElementList.Count>1) 
  {
     index = rndElement.Next(0,this.ElementList.Count-1);
     if (this.ElementList[index] == el)
        return this.ElementList[this.ElementList.Count-1];
     else
        return this.ElementList[index];
  }
 else return null;
}

Upvotes: 7

Robban
Robban

Reputation: 6802

How about:

 public Element GetAnyoneElseFromTheList(Element el)
  {
  Random rand = new Random();
  Element returnEle = ElementList[rand.Next(0, this.ElementList.Count];
  return returnEle == el ? GetAnyoneElseFromTheList(Element el) : el;
 }

Or in the case you do not like the possibility of a loop:

public Element GetAnyoneElseFromTheList(Element el)
 {
  Random rand = new Random();
  List<Element> listwithoutElement = ElementList.Where(e=>e != el).ToList();
  return listwithoutElement[rand.Next(listwithoutElement.Count)];
 }

Upvotes: 1

Related Questions