views:

66

answers:

2

I'm not sure what kind of terminology to use in this so please edit the title to fit as necessary.

For learning purposes, I'm trying to make a screensaver that displays images randomly from a specified folder. Yes, Windows already comes with this but again, learning purposes. Though I'd prefer to learn the proper way the first time instead of having to go back through and relearn how to properly do things.

Now I already have it randomly choosing images and displaying them, etc. What I'd like to do is randomize based on how many times a specific image has been loaded.

For example:

Image 1: 0 views(20%) : 1 view (19%)
Image 2: 0 views(20%) : 0 views(24%)
Image 3: 0 views(20%) : 1 view (19%)
Image 4: 0 views(20%) : 2 views(14%)
Image 5: 0 views(20%) : 0 views(24%)

This way Image 2 and Image 5 would have the highest chance of being shown next.

I've been at this for a while but I'm not sure what's wrong as it's simply grabbing the next pic along the list. I tried to group them by finding all the pictures with the same number of views and then randomizing through that list but it doesn't seem to exactly work the greatest. Here's the code I have for displaying the pictures:

Random rnd = new Random();  
string file = "";  
int totalViews = 0;  
List<string> array = new List<string>();  
void ShowPicture()  
{  
    array.Clear();  
    label1.Text = "";  
    foreach (Screen screen in Screen.AllScreens)  
    {  
        bool done = false;  
        while (!done)  
        {  
            int rand = rnd.Next(100), total;  
            foreach (string info in files.Keys)  
            {  
                total = (100 / files.Count) - (files[info] * (files.Count - 1)) + totalViews;  
                if (total >= rand)  
                {  
                    foreach (string tmp in files.Keys) if (total >= files[tmp]) array.Add(tmp);  
                    label1.Text = "Percentage for image: " + total;  
                    done = true;  
                    break;  
                }  
            }  
        }  
        file = array[rnd.Next(array.Count)];  
        files[file]++;  
        totalViews++;  
        Image image = Image.FromFile(file);  
        pictureBox1.Image = image;  

        label1.Text += "\nTotal Views: " + totalViews;  
    }  
}

I hope this is clear enough and thanks in advance.

+1  A: 

I think you're making more work for yourself than it needs to be.

I'm not entirely sure what your variables all do, but this is how I would solve the problem (setup and error checking code etc. removed for clarity):

int totalViews;  // Assuming this stores the total number of images that 
                 //  have been shown so far (4 in your example)
double [] fileWeighting; // How to weight each file
int [] fileViews;     // How many times each file has been seen in total

// Note Sum(fileViews) = totalViews

for(int i = 0; i < files.Count; i++) {
  fileWeighting[i] = ((double)totalViews - (double)fileViews[i]) / 
                         (double)totalViews;
}

double rndChoice = Random.NextDouble();
double acc = 0;

for (int j = 0; j < files.Count; j++) {
  if ( (rndChoice - acc) < fileWeighting[j] ) {
    // Display image j
    break;
  }
  acc += fileWeighting[j];
}

Basically we build an array of file weightings based on their view count (lower views mean higher weightings). These weightings add up to 1 (e.g. [0.19, 0.24, 0.19, 0.14, 0.24]) and effectively divide up the 0 to 1 number line:

|--------|-----------|-------|------|---------|
0                                             1
  (0.19)    (0.24)     (0.19) (0.14)  (0.24)    - Weightings
    0         1          2      3       4       - Corresponding images

We then choose a random number between 0 and 1 (rndChoice) - this is the position in the number line we have chosen. The second loop simply finds which image lies at that point in the number line, and that image gets displayd.

Mark Pim
+1 for efficiency , had a similar answer in mind (but mine was load heavy, so i didn't even bother to post it),but you've proven to me again that the simplest answers lay right infront of you..you just choose to ignore them :D
Madi D.
I think I implemented this approach correctly but it's going through them sequentially and only going through them once and then stopping.
What loop are you using? `ShowPicture()` as defined above should always return a different image - it shouldn't ever 'stop'
Mark Pim
Well, part of the problem was that it was continually resulting in either 1.0 or 0.0 on the weighting. It wasn't calculating properly for some reason. So I just changed the line to:fileWeighting[i] = ((double)totalViews - (double)fileViews[i]) / (double)totalViews;Though the new error now is that it's only looping through 2 pics.
I honestly don't see how "weights" work in this fashion because the more you subtract, the more likely the images at the bottom of the list will never or rarely be displayed (this is the result I got). Granted I did the math manually to see what would happen and honestly it should be working. Maybe I was just implementing it incorrectly. Thanks for the help and time though :)
A: 

I found the idee when looking at this link.

Basically, multiply the weight with the random number when sorting the list.

So try this

List<KeyValuePair<string, decimal>> list = new List<KeyValuePair<string, decimal>>();

            for (int iItem = 0; iItem < 10; iItem++)
                list.Add(new KeyValuePair<string, decimal>(iItem.ToString(), iItem/10m));

            Random rnd = new Random();

            list.Sort(  delegate(KeyValuePair<string, decimal> item1, KeyValuePair<string, decimal> item2) 
                        {
                            if (item1.Value == item2.Value)
                                return 0;
                            decimal weight1 = 1 / (item1.Value == 0 ? 0.01m : item1.Value);
                            decimal weight2 = 1 / (item2.Value == 0 ? 0.01m : item2.Value);
                            return (weight1 * rnd.Next()).CompareTo(weight2 * rnd.Next()); 
                        });
            list.Reverse();
astander
Wow, lol. I plugged this right in and changed the for() to iterate through my files list and then displayed list[0].Key into the Image.FromFile() and it worked perfectly. Its even keeping the view counts balanced out.
Cool, glad i could help
astander