2/9/14

Randomizing Display Ads

I was working on the home page of a website that had a small area on the top right corner that was set aside for advertisements. The real-estate had to be shared by four different Ads provided they satisfied the criteria for display. Each had a different criteria that determined their eligibility for display. Criteria included the logged-in user's age, postal code and gender.

So it was possible that at any given time there could be one, two, three or four Ads eligible for display.Then the Ads that were eligible for display had to be randomized to ensure that each had an equal likelihood of appearing on the page.

The model for the page was as follows:


 public class MyHomeModel
  {
   public bool ShowNexusAd {get;set;}
   public bool ShowIpadAd {get;set;}
   public bool ShowSurfaceAd {get;set;}
   public bool ShowKindleAd {get;set;}
  }


So when we returned the model to the view, each of the properties had to have a value of true or false, that would be used in the view to determine whether to hide or show the particular Ad as follows:

@if(model.ShowNexusAd)
{
  //show Nexus Ad
}


For randomization, I decide to use the Random class in .NET to generate a random number in a specified range (1-9) and then use the value generated to determine which ad to show. The code would like this:

if(ShowNexusAd && ShowIpadAd && ShowSurfaceAd && ShowKindleAd)
{
   //generate a number between 1 and 9(including 1)
   Random randomNumber = new Random(1,9) 
   if(randomNumber <=2)
      ShowNexusAd = true;
   if(randomNumber >2 && randomNumber <=4)
      ShowIpadAd = true;
   if(randomNumber >4 && randomNumber <=6)
      ShowSurfaceAd = true;
   if(randomNumber >6 && randomNumber <=8)
      ShowKindleAd = true;
}


But there is a problem here.......

what if only three of the ads were eligible for display or two or just one? we would end up with code as follows:
if(ShowNexusAd && ShowIpadAd && ShowSurfaceAd && ShowKindleAd)

{
 Random randomNumber = new Random(1,9)
 if(randomNumber <=2) 
  ShowNexusAd = true;
 if(randomNumber >2 && randomNumber <=4) 
  ShowIpadAd = true;
 if(randomNumber >4 && randomNumber <=6) 
  ShowSurfaceAd = true;
 if(randomNumber >6 && randomNumber <=8) 
  ShowKindleAd = true;
}else
if(ShowNexusAd && ShowIpadAd && ShowSurfaceAd )
{
 Random randomNumber = new Random(1,7)
 if(randomNumber <=2) 
  ShowNexusAd = true;
 if(randomNumber >2 && randomNumber <=4) 
  ShowIpadAd = true;
 if(randomNumber >4 && randomNumber <=6) 
  ShowSurfaceAd = true;
}else
if(ShowNexusAd && ShowIpadAd && ShowKindleAd)

{
 Random randomNumber = new Random(1,7)
 if(randomNumber <=2) 
  ShowNexusAd = true;
 if(randomNumber >2 && randomNumber <=4) 
  ShowIpadAd = true;
 if(randomNumber >4 && randomNumber <=6)
  ShowKindleAd= true;
}
.......................and so on with all the possible permutations and combinations

As the number of Ads increase(or even for the existing four Ads) it will become a nightmare to extend this logic and even maintain it. So how do we make the code smart enough to handle this?

I hit upon the following idea(which might not be the greatest idea, but is pretty decent)

I created the following classes:

public abstract class AdDisplayBase
{
 public MyHomeModel ViewModel { get; set; }
 public abstract bool ShowAd { set; }
}

public class NexusAdDisplay : AdDisplayBase
{
 public override bool ShowAd
 {
  set { ViewModel.ShowNexusAd = value; }
 }
}

public class IpadAdDisplay : AdDisplayBase
{
 public override bool ShowAd
 {
  set { ViewModel.ShowIpadAd = value; }
 }
}

public class SurfaceAdDisplay : AdDisplayBase
{
 public override bool ShowAd
 {
  set { ViewModel.ShowSurfaceAd = value; }
 }
}

public class KindleAdDisplay : AdDisplayBase
{
 public override bool ShowAd
 {
  set { ViewModel.ShowKindleAd = value; }
 }
}

My goal was to convert the simple value types(the boolean properties) into reference types, so that I could treat them as objects. This would enable me to add the objects to a collection and handle them in a generic manner. To achieve this, I created an abstraction around the boolean properties by creating a class for each type of Ad.

I then refactored the unmanageable conditional logic above to the following method:

void ManageAdsDisplay(MyHomeModel model)
{
  int randomNum;
  var lstAdsToDisplay = new List();

  if (model.ShowNexusAd ) 
     lstAdsToDisplay.Add(new NexusAdDisplay () { ViewModel = model });
  if (model.ShowIpadAd ) 
     lstAdsToDisplay.Add(new IpadAdDisplay () { ViewModel = model });
  if (model.ShowSurfaceAd ) 
     lstAdsToDisplay.Add(new SurfaceAdDisplay () { ViewModel = model });
  if (model.ShowKindleAd ) 
     lstAdsToDisplay.Add(new KindleAdDisplay () { ViewModel = model });
  
  switch (lstAdsToDisplay.Count)
  {
    case 1:
 lstAdsToDisplay[0].ShowAd = true;
 break;
    case 2:
 randomNum = new Random().Next(1, 9);
 lstAdsToDisplay[0].ShowAd = (randomNum <= 4);
 lstAdsToDisplay[1].ShowAd = (randomNum > 4);
 break;
   case 3:
 randomNum = new Random().Next(1, 10);
 lstAdsToDisplay[0].ShowAd = (randomNum <= 3);
 lstAdsToDisplay[1].ShowAd = (randomNum > 3 && randomNum <= 6);
 lstAdsToDisplay[2].ShowAd = (randomNum > 6 && randomNum <= 9);
 break;
  case 4:
 randomNum = new Random().Next(1, 9);
 lstAdsToDisplay[0].ShowAd = (randomNum <= 2);
 lstAdsToDisplay[1].ShowAd = (randomNum > 2 && randomNum <= 4);
 lstAdsToDisplay[2].ShowAd = (randomNum > 4 && randomNum <= 6);
 lstAdsToDisplay[3].ShowAd = (randomNum > 6 && randomNum <= 8);
 break;
  }
}

By doing this we avoid the need to write complicated and confusing conditional statements and it is pretty easy to extend this design if there are more Ads in the future. To be honest, I am not completely happy with the Switch/Case part of the solution and I am trying to find a better way to do this. It will definitely end up as another blog post if I do.