C#: Displaying images based on number of views and random - c#

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.

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.

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();

Related

C# WinsForm, Frequency Distribution Table [Updated]

Update 01
Thanks to Caius, found the main problem, the logic on the "if" was wrong, now fixed and giving the correct results. The loop still create more positions than needed on the secondary List, an extra position for each number on the main List.
I've updated the code bellow for refence for the following question:
-001 I can figure out why it create positions that needed, the for loop should run only after the foreach finishes its loops correct?
-002 To kind of solving this issue, I've used a List.Remove() to remove all the 0's, so far no crashes, but, the fact that I'm creating the extra indexes, and than removing them, does means a big performance down if I have large list of numbers? Or is an acceptable solution?
Description
It supposed to read all numbers in a central List1 (numberList), and count how many numbers are inside a certain (0|-15 / 15|-20) range, for that I use another List, that each range is a position on the List2 (numberSubList), where each number on List2, tells how many numbers exists inside that range.
-The range changes as the numbers grows or decrease
Code:
void Frequency()
{
int minNumb = numberList.Min();
int maxNumb = numberList.Max();
int size = numberList.Count();
numberSubList.Clear();
dGrdVFrequency.Rows.Clear();
dGrdVFrequency.Refresh();
double k = (1 + 3.3 * Math.Log10(size));
double h = (maxNumb - minNumb) / k;
lblH.Text = $"H: {Math.Round(h, 2)} / Rounded = {Math.Round(h / 5) * 5}";
lblK.Text = $"K: {Math.Round(k, 4)}";
if (h <= 5) { h = 5; }
else { h = Math.Round(h / 5) * 5; }
int counter = 1;
for (int i = 0; i < size; i++)
{
numberSubList.Add(0); // 001 HERE, creating more positions than needed, each per number.
foreach (int number in numberList)
{
if (number >= (h * i) + minNumb && number < (h * (i + 1)) + minNumb)
{
numberSubList[i] = counter++;
}
}
numberSubList.Remove(0); // 002-This to remove all the extra 0's that are created.
counter = 1;
}
txtBoxSubNum.Clear();
foreach (int number in numberSubList)
{
txtBoxSubNum.AppendText($"{number.ToString()} , ");
}
lblSubTotalIndex.Text = $"Total in List: {numberSubList.Count()}";
lblSubSumIndex.Text = $"Sum of List: {numberSubList.Sum()}";
int inc = 0;
int sum = 0;
foreach (int number in numberSubList)
{
sum = sum + number;
int n = dGrdVFrequency.Rows.Add();
dGrdVFrequency.Rows[n].Cells[0].Value = $"{(h * inc) + minNumb} |- {(h * (1 + inc)) + minNumb}";
dGrdVFrequency.Rows[n].Cells[1].Value = $"{number}";
dGrdVFrequency.Rows[n].Cells[2].Value = $"{sum}";
dGrdVFrequency.Rows[n].Cells[3].Value = $"{(number * 100) / size} %";
dGrdVFrequency.Rows[n].Cells[4].Value = $"{(sum * 100) / size} %";
inc++;
}
}
Screen shot showing the updated version.
I think, if your aim is to only store eg 17 in the "15 to 25" slot, this is wonky:
if (number <= (h * i) + minNumb) // Check if number is smaller than the range limit
Because it's found inside a loop that will move on to the next range, "25 to 35" and it only asks if the number 17 is less than the upper limit (and 17 is less than 35) so 17 is accorded to the 25-35 range too
FWIW the range a number should be in can be derived from the number, with (number - min) / number_of_ranges - at the moment you create your eg 10 ranges and then you visit each number 10 times looking to put it in a range, so you do 9 times more operations than you really need to

Show a pyramid based on number input

I'm currently stuck on this problem the goal is to be able to print out a pyramid depending on the input number.
Ex: input = 3
It should look like this:
1
23
I'm able to make it work with some numbers but it doesn't work with other inputs.
The current logic that I follow is by dividing the input by 2 then add the remainder to the next one to be divided.
Example input: 10
10 / 2 = 5 remainder: 0
5 / 2 = 2 remainder: 1
2 + 1 (previous remainder)/ 2 = 1 remainder: 1
1
Whereas it would look like this:
5
2
2
1
The rules of the pyramid that I'm trying to make only needs to have one or two differences per row.
That's why I'll be needing to deduct 1 from 5 and add it to the next one:
4
3
2
1
Thus having pyramid like this: 10
****
***
**
*
The problem is this approach isn't applicable on other inputs and I'm having a hard time on finding a different approach for this.
This code works for your example, although it might be improved to detect whenever a number is not suitable to print a piramid.
static void Main(string[] args)
{
while (true)
{
Console.Write("Enter a number:");
int input = int.Parse(Console.ReadLine());
List<List<string>> piramid = new List<List<string>>();
int rowNumber = 1, dotsCount = 0;
while (dotsCount < input)
{
List<string> row = new List<string>();
for (int i = 1; i <= rowNumber && dotsCount < input; i++)
{
row.Add("*");
dotsCount++;
}
piramid.Add(row);
rowNumber++;
}
Console.WriteLine("Piramid");
Print(piramid);
Console.WriteLine("Piramid (inverted)");
PrintInverted(piramid);
}
}
static void Print(List<List<string>> piramid)
{
foreach (var item in piramid)
{
item.ForEach(Console.Write);
Console.Write("\n");
}
}
static void PrintInverted(List<List<string>> piramid)
{
for (int i = piramid.Count - 1; i >= 0; i--)
{
List<string> item = piramid[i];
item.ForEach(Console.Write);
Console.Write("\n");
}
}

int list won't display in console application

I come from a C++ background creating basic 2D games with it, but I am trying to teach myself C# to a point to where I can get the most basic of jobs using it. I am not in school but I am following the Problems on ProjectEuler.net.
The problem question is commented into the code. I can't tell whether I solved it or not because I can't get the numbers to display from a list into the console application.
I've tried writing to console directly from the variable value with Console.WriteLine but I'm not having any luck. I've also tried converting all int list values to a string value and displaying them but that also didn't work.
I'm not looking for the answer to number 4 just looking to display the list so I can find the answer on my own.
Why can't I get the list to write to the Console?
Any help is appreciated!
static void Main(string[] args)
{
/* A palindromic number reads the same both ways.
* The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.
Find the largest palindrome made from the product of two 3-digit numbers. */
// 100 x 100 = 10000
// 999 x 999 = 998001
List<int> palindromeContainer = new List<int>();
int Increment = 2;
int Holder = 0;
for (int i = 100; i <= 999; ++i)
{
int j = i;
while (j <= 999)
{
do
{ Holder = i * j; // Gets all Possible Combinations of i * j
if ((Holder % Increment) != 0) // Checks for Prime Numbers
{
++Increment;
}
else if (Increment == Holder - 1 && Holder % Increment != 0 )
{
palindromeContainer.Add(Holder);
Increment = 2;
break;
}
else if (Increment == Holder - 1 && Holder % Increment == 0)
{
Increment = 2;
break;
}
} while (Increment < Holder);
++j;
}
}
palindromeContainer.Sort();
foreach (int line in palindromeContainer)
{
Console.WriteLine(line); // Display all items in list
}
Firstly comment out your loop logic and test without:
List<int> palindromeContainer = new List<int>();
palindromeContainer.Add(2);
palindromeContainer.Add(1);
palindromeContainer.Sort();
foreach (int line in palindromeContainer)
{
Console.WriteLine(line); // Display all items in list
}
Console.ReadLine();
This will output to the console. Then you will know this is working and console output is not the problem.

Increase chances of name being picked from Array

For my program, I've prompted the user to put 20 names into an array (the array size is 5 for testing for now), this array is then sent to a text document. I need to make it so that it will randomly pick a name from the list and display it (which I have done). But I now need to make it increase the chances of a name being picked, how would I go about doing this?
Eg. I want to increase the chances of the name 'Jim' being picked from the array.
class Programt
{
static void readFile()
{
}
static void Main(string[] args)
{
string winner;
string file = #"C:\names.txt";
string[] classNames = new string[5];
Random RandString = new Random();
Console.ForegroundColor = ConsoleColor.White;
if (File.Exists(file))
{
Console.WriteLine("Names in the text document are: ");
foreach (var displayFile in File.ReadAllLines(file))
Console.WriteLine(displayFile);
Console.ReadKey();
}
else
{
Console.WriteLine("Please enter 5 names:");
for (int i = 0; i < 5; i++)
classNames[i] = Console.ReadLine();
File.Create(file).Close();
File.WriteAllLines(file, classNames);
Console.WriteLine("Writing names to file...");
winner = classNames[RandString.Next(0, classNames.Length)];
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("\nThe winner of the randomiser is: {0} Congratulations! ", winner);
Thread.Sleep(3000);
Console.Write("Completed");
Thread.Sleep(1000);
}
}
}
There's two ways of doing this. You can either produce a RNG with a normal distribution targeting one number.
Or the simpler way is a translational step. Generate in the range 0-100 and then produce code which translates to the answer in a biased way e.g.
0-5 : Answer 1
6-10: Answer 2
11-90: Answer 3
91-95: Answer 4
96-100: Answer 5
This gives an 80% chance of picking Answer 3, the others only get a 5% chance
So where you currently have RandString.Next(0, classNames.Length) you can replace that with a function something like GetBiasedIndex(0, classNames.Length, 3)
The function would look something like this (with test code):
public Form1()
{
InitializeComponent();
int[] results = new int[5];
Random RandString = new Random();
for (int i = 0; i < 1000; i++)
{
var output = GetBiasedIndex(RandString, 0, 4, 3);
results[output]++;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 5; i++)
{
builder.AppendLine(results[i].ToString());
}
label1.Text = builder.ToString();
}
private int GetBiasedIndex(Random rng, int start, int end, int target)
{
//Number between 0 and 100 (Helps to think percentage)
var check = rng.Next(0, 100);
//There's a few ways to do this next bit, but I'll try to keep it simple
//Allocate x% to the target and split the remaining y% among all the others
int x = 80;//80% chance of target
int remaining = 100 - x;//20% chance of something else
//Take the check for the target out of the last x% (we can take it out of any x% chunk but this makes it simpler
if (check > (100 - x))
{
return target;
}
else
{
//20% left if there's 4 names remaining that's 5% each
var perOther = (100 - x) / ((end - start) - 1);
//result is now in the range 0..4
var result = check / perOther;
//avoid hitting the target in this section
if (result >= target)
{
//adjust the index we are returning since we have already accounted for the target
result++;
}
//return the index;
return result;
}
}
and the output:
52
68
55
786
39
If you're going to call this function repeatedly you'll need to pass in the instance of the RNG so that you don't reset the seed each call.
If you want to target a name instead of an index you just need to look up that name first and have an else condition for when that name isn't found

How to write groups of numbers using Console.Write?

I'm very new to C# (And Stack Overflow, forgive me for any poor etiquette here), and I'm writing the game Mastermind in a console application. I'm trying to show a list of the user's guesses at the end of the game, and I know that using Console.WriteLine(); will just give me 30-odd lines off numbers which don't tell the user anything.
How can I alter my code so that the program displays 4 numbers in a group, at a time? For example:
1234
1234
1234
//Store numbers in a history list
ArrayList guesses = new ArrayList(); //This is the ArrayList
Console.WriteLine("Please enter your first guess.");
guess1 = Convert.ToInt32(Console.ReadLine());
guesses.Add(guess1);
foreach (int i in guesses)
{
Console.Write(i);
}
I assume that each element of your byte array is a single digit (0-9). If that assumption is invalid -- please let me know, I'll modify the code :)
Action<IEnumerable<int>> dump = null;
dump = items =>
{
if(items.Any())
{
var head = String.Join("", items.Take(4));
Console.WriteLine(head);
var tail = items.Skip(4);
dump(tail);
}
};
dump(guesses);
It looks like you're most of the way there, you have a console write that writes them all out without linebreaks. Next add an integer count and set it to zero. Increment it by one in the foreach loop. count % 4 == 0 will then be true for all counts that are a multiple of four. This means you can stick an if block there with a write-line to give you your groups of four.
List<int> endResult = new List<int>();
StringBuilder tempSb = new StringBuilder();
for(int i=0; i < groups.Count; i++)
{
if(i % 4 == 0) {
endResult.Add(int.Parse(sb.ToString()));
tempSb.Clear(); // remove what was already added
}
tempSb.Append(group[i]);
}
// check to make sure there aren't any stragglers left in
// the StringBuilder. Would happen if the count of groups is not a multiple of 4
if(groups.Count % 4 != 0) {
groups.Add(int.Parse(sb.ToString()));
}
This will give you a list of 4 digit ints and make sure you don't lose any if your the number of ints in your groups list is not a multiple of 4. Please note that I am continuing based on what you provided, so groups is the ArrayList of ints.
This is some thing I quickly put together:
Update:
ArrayList guesses = new ArrayList(); //This is the ArrayList
// Four or more
guesses.Add(1); guesses.Add(2);
guesses.Add(3);guesses.Add(4);
guesses.Add(5); guesses.Add(6); guesses.Add(7);guesses.Add(8); guesses.Add(9);
//Uncomment-Me for less than four inputs
//guesses.Add(1); guesses.Add(2);
int position = 0;
if (guesses.Count < 4)
{
for (int y = 0; y < guesses.Count; y++)
{
Console.Out.Write(guesses[y]);
}
}
else
{
for (int i = 1; i <= guesses.Count; i++)
{
if (i%4 == 0)
{
Console.Out.WriteLine(string.Format("{0}{1}{2}{3}", guesses[i - 4], guesses[i - 3],
guesses[i - 2], guesses[i - 1]));
position = i;
}
else
{
if (i == guesses.Count)
{
for (int j = position; j < i; j++)
{
Console.Out.Write(guesses[j]);
}
}
}
}
}

Categories