C# Weighted Random Questions, Tests - c#

Im trying to write an program for students. I want it to generate tests with question database based on Student success on topics.
For starting i writed this,
using MatematikYapayZeka.HelperClasses;
class Program
{
static void Main(string[] args)
{
double[] topicWeights = { 3.0, 2.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 3.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
string[] topicNames = { "Temel Kavramlar ve İşlem Yeteneği", "Sayı Basamakları", "Bölme ve Bölünebilme", "EBOB-EKOK", "Rasyonel Sayılar", "Basit Eşitsizlikler", "Mutlak Değer", "Üslü Sayılar", "Köklü Sayılar", "Çarpanlara Ayırma", "Denklem Çözme", "Oran-Orantı", "Problemler", "Kümeler", "Fonksiyonlar", "Permütasyon-Kombinasyon", "Olasılık", "İstatistik", "Polinomlar", "Mantık" };
bool[][] userResponses = new bool[topicNames.Count()][];
double[] userAccuracy = new double[topicNames.Count()];
//topic id, question id, question
List<Tuple<int,int, string>> easyQuestions = new List<Tuple<int,int, string>>();
List<Tuple<int, int, string>> mediumQuestions = new List<Tuple<int, int, string>>();
List<Tuple<int, int, string>> hardQuestions = new List<Tuple<int, int, string>>();
//How many questions per topic
int testQuestionCount = 1000;
int testUserResponseCount = 10000;
//Adding questions on every topic
for (int i = 0; i < topicNames.Count(); i++)
{
for (int i2 = 0; i2 < testQuestionCount; i2++)
{
//topic id, question id, question
easyQuestions.Add(new Tuple<int,int, string>(i,i2, topicNames[i]+"Easy Question number "+i2));
mediumQuestions.Add(new Tuple<int, int, string>(i, i2, topicNames[i]+"Medium Question number "+i2));
hardQuestions.Add(new Tuple<int, int, string>(i, i2, topicNames[i]+"Hard Question number "+i2));
}
}
//Generation Random true false user response per topic
//Calculating user accuracy for every topic
for (int i = 0; i < userResponses.Count(); i++)
{
for (int i2 = 0; i2 < userResponses.Length; i2++)
{
var randomTf = GenerateRandomValue.RandomTrueFalseList(testUserResponseCount);
userResponses[i] = randomTf;
}
double count = userResponses[i].Count(c => c);
double length = userResponses[i].Length;
double avg = count / length;
userAccuracy[i] = avg;
}
//Calculating user avgScore, Calculating user standartDeviation, Calculating zScores for every topic
double avgScore = userAccuracy.Average();
double standardDeviation = Math.Sqrt(userAccuracy.Select(val => (val - avgScore) * (val - avgScore)).Sum() / userAccuracy.Length);
double[] zScores = new double[topicNames.Count()];
for (int i = 0; i < userAccuracy.Length; i++)
{
zScores[i] = (userAccuracy[i] - avgScore) / standardDeviation;
Console.WriteLine("kullanıcının " + i + " numaralı konudaki z skoru "+zScores[i]);
if (zScores[i] > zScores.Average())
{
Console.WriteLine("kullanıcı bu konuda diğer konulara nazaran daha başarılı");
}
else
{
Console.WriteLine("kullanıcı bu konuda diğer konulara nazaran daha başarısız");
}
}
//Generating new list with topic id and zscore, binding topic zscores to zscore colon.
var qList = new List<IdAndZcore>() { };
for (int i = 0; i < zScores.Length; i++)
{
qList.Add(new IdAndZcore()
{
Id = i,
Zcore = zScores[i]
});
}
//Min to max ordered zScore list for user.
var myList = qList.OrderBy(a => a.Zcore).ToList();
//Telling user which topic id should be preferred.
var tavsiye = 1;
foreach (var item in myList)
{
Console.WriteLine(item.Id + " numaralı konu " + tavsiye + ". önceliğin olmalı");
tavsiye++;
}
//Writing random data.
Console.WriteLine("standart sapma " + standardDeviation);
Console.WriteLine("ortalama kullanıcı skoru " + avgScore);
Console.WriteLine("toplam " + userAccuracy.Length + " konu için avg puanlardır.");
Console.WriteLine("her bir konu için " + userResponses[0].Count() + " userResponse");
Console.ReadLine();
}
}
It saves user responses for spesific topics. Generating user scores based on user responses accuracy.
How can i generate new test of questions with using Weights?.
Thank you.
I tried Rnd function with int but it does not work for float values.

Related

How to share random numbers out evenly in C#

I am looking at sharing out a fixed number of 32 teams between a varied number of people.
Of course, 32 may not always be evenly divisible, but for the sake of this exercise, lets say I am looking to share the 32 teams between 4 people, so a maximum number of 8 teams per person.
int max = 32 / numb;
foreach (string value in wcteams)
{
//Assigning teams to players
int selection = random.Next(0, numb);
int[] counter = new int[max];
counter[selection] = counter[selection] + 1;
if (counter[selection] < max)
{
Console.WriteLine(inputtedNames[selection] + " has drawn " + value);
}
}
Right now, I can run that code and I will get a list back of randomly chosen people along with their team. But the limit will not be implemented and some players will end up with more teams than others.
I understand that the following code:
counter[selection] = counter[selection] + 1;
Is not working to add up the number of teams that the user has received, am I on the right track here with how to tally up the number of times a player has been randomly selected or is there another method that I should be doing?
One problem in your code is you are initializing counter inside the loop. Also what happens if the count[selection] > max? you leave the team and don't assign it to anyone else.
Try the following code.
int numb = 4;
int max = 32 / numb;
int[] counter = new int[max];
foreach (string value in wcteams)
{
bool selectionComplete = false;
while(!selectionComplete)
{
int selection = random.Next(0, numb);
counter[selection] = counter[selection] + 1;
if (counter[selection] <= max)
{
selectionComplete = true;
Console.WriteLine(selection + " has drawn " + value);
}
}
}
I cannot figure your code but this should work.
public static Random randomT = new Random();
public static List<List<string>> DivideTeams(string[] teams, int personCount)
{
List<List<string>> divideTeams = new List<List<string>>();
if (teams.Length % personCount != 0)
{
throw new ArgumentOutOfRangeException();
}
//shuffle teams
for(int k = teams.Length -1; k > 0; k--)
{
int trade = random.Next(k + 1);
string temp = teams[trade];
teams[trade] = teams[k];
teams[k] = temp;
}
for (int j = 0; j < personCount; j++)
{
divideTeams.Add(new List<string>());
for (int i = 0; i < teams.Length / personCount; i++)
{
divideTeams[j].Add(teams[i]);
}
}
return divideTeams;
}

What's wrong with this simple method to sample from multinomial in C#?

I wanted to implement a simple method to sample from a multinomial distribution in C# (the first argument is an array of integers we want to sample and the second one is the probabilities of selecting each of those integers).
When I do this with numpy in python, the results make sense.
np.random.choice(np.array([1,2,3,4,5,6]),p=np.array([.624,.23,.08,.04, .02, .006]),size=len(b))
I get a lot of 1's (probability 62%), a bunch of 2's, some 3's etc.
However, when I try the implementation below in C# (pretty straightforward inverse transform sampling for multinomial, only relies on a uniform random variable), I get really weird results. For all 1000 samples, I'll often find all 1's. Sometimes, I'll find all 3's (!!??). The results never look like what you would expect (and what you get from the python function - try running it yourself a few times). This is really scary since we rely on these primitives. Does anyone have insight into what might be wrong with the C# version?
static void Main(string[] args)
{
int[] iis = new int[7];
int[] itms = new int[] { 1, 2, 3, 4, 5, 6 };
double[] probs = new double[] { .624, .23, .08, .04, .02, .006 };
for (int i = 0; i < 1000; i++)
{
iis[MultinomialSample(itms, probs)] += 1;
}
foreach (var ii in iis)
{
Console.Write(ii + ",");
}
Console.Read();
}
private static int MultinomialSample(int[] s, double[] ps)
{
double[] cumProbs = new double[ps.Length];
cumProbs[0] = ps[0];
for (int i = 1; i < ps.Length; i++)
{
cumProbs[i] = cumProbs[i - 1] + ps[i];
}
Random random = new Random();
double u = random.NextDouble();
for (int i = 0; i < cumProbs.Length - 1; i++)
{
if (u < cumProbs[i])
{
return s[i];
}
}
return s[s.Length - 1];
}
You're initializing Random each time you call MultinomialSample. If these calls are very close together, Random will be initialized with the same seed (based on the system clock). Try either making Random a private class field: private static Random random = new Random(); or pass it into the method as an argument from Main, where it would be initialized only once:
private static Random random = new Random();
private static int MultinomialSample(IReadOnlyList<int> sample,
IReadOnlyList<double> probabilities)
{
var cumProbs = new double[probabilities.Count];
cumProbs[0] = probabilities[0];
for (var i = 1; i < probabilities.Count; i++)
{
cumProbs[i] = cumProbs[i - 1] + probabilities[i];
}
for (var i = 0; i < cumProbs.Length - 1; i++)
{
if (random.NextDouble() < cumProbs[i])
{
return sample[i];
}
}
return sample[sample.Count - 1];
}
private static void Main()
{
var iis = new int[7];
var items = new[] {1, 2, 3, 4, 5, 6};
var probabilities = new[] {.624, .23, .08, .04, .02, .006};
for (int i = 0; i < 1000; i++)
{
iis[MultinomialSample(items, probabilities)] ++;
}
Console.WriteLine(string.Join(", ", iis));
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
I used Rufus' code in a simulation I was working on and noticed there is still a problem, even after seeding the random number generator just once (which is the correct thing to do). You will notice that as we are iterating, the call to random.NextDouble() generates a new random number each time. This is wrong.
for (var i = 0; i < cumProbs.Length - 1; i++)
{
if (random.NextDouble() < cumProbs[i])
{
return sample[i];
}
}
The random number should be generated outside of the loop, as follows:
var r = random.NextDouble();
for (var i = 0; i < cumProbs.Length - 1; i++)
{
if (r < cumProbs[i])
{
return sample[i];
}
}
You can compare it to the Excel algorithm given on Wikipedia: https://en.wikipedia.org/wiki/Multinomial_distribution. When I made the above change to Rufus' code, I got the desired frequency distribution as specified by the probabilities array.

Almost Ordered not sorting the exact amount of values i give it

this is a really easy question but i cant figure out a way around it. Apparently the almost ordered has a bug that it might randomize a little bit more than you ask it. the code is rather simple:
public void Section1Task1AlmostOrdered(int arraySize, int percentage)
{
int[] testArray = new int[arraySize];
Console.WriteLine("Ordered List: ");
for (int i = 1; i <= testArray.Length; i++)
{
testArray[i-1] = i;
Console.Write(i + "\t");
}
Console.WriteLine("Almost Ordered List: ");
testArray = shuffler.AlmostOrdered(arraySize, percentage);
for (int i = 0; i < testArray.Length; i++)
{
Console.Write(testArray[i] + "\t");
}
}
The shuffler is this part of the code:
public int[] AlmostOrdered(int n, double p)
{
if (p > 100)
{
throw new InvalidOperationException("Cannot shuffle more than 100% of the numbers");
}
int shuffled = 0;
//Create and Populate an array
int[] array = new int[n];
for(int i = 1; i <= n; i++)
{
array[i-1] = i;
}
//Calculate numbers to shuffle
int numsOutOfPlace = (int) Math.Ceiling(n * (p / 100));
int firstRandomIndex = 0;
int secondRandomIndex = 0;
do
{
firstRandomIndex = this.random.Next(n-1);
// to make sure that the two numbers are not the same
do
{
secondRandomIndex = this.random.Next(n - 1);
} while (firstRandomIndex == secondRandomIndex);
int temp = array[firstRandomIndex];
array[firstRandomIndex] = array[secondRandomIndex];
array[secondRandomIndex] = temp;
shuffled++;
}
while (shuffled < numsOutOfPlace);
return array;
}
When i enter values 10 for array size and 40 for percentage to be shuffled, it is shuffling 5 numbers instead of 4. Is there a way to perfect this method to make it more accurate?
Likely the problem is with the calculation:
int numsOutOfPlace = (int)Math.Ceiling(n * (p / 100));
So if p=40 and n=10, then in theory you should get 4. But you're dealing with floating point numbers. So if (p/100) returns 0.400000000001, then the result will be 4.000000001, and Math.Ceiling will round that up to 5.
You might want to replace Math.Ceiling with Math.Round and see how that works out.

How to perform combinatoric task in C#

i have a problem with an combinatoric task. I want to have a code which can handle this math calculation 48/5 = 1712304 (5! = 5*4*3*2*1 = 120 48*47*46*45*44 = 205476480 205476480/120 = 1712304)
Here some more details.
I have a string array with 48 strings. The strings have a length between 2 and 3 chars.
string array[] = new string[]{"A1","1D","410" /*[....]*/}
i want to combine 5 strings together, and i want to do this with the whole array, my biggest problem is that a combined string is just allowed to include each of the 48 strings just once. And each combined string have to be unique.
In the end i need a List with 1712304 string entries, which have to be between 10 and 15 degits long. For example one string could look like this "A11A4103E1T".
Back to the math, i know that there are 1712304 combined options so any other result must be wrong. That's where my problem appears.
I created the following code without succesful result
string[] siDigit = new string[iNumDigits];
List<string> liste = new List<string>();
const int iDigitBase = 48;
const int iNumDigits = 5;
for (int i = 0; i < 1712303; ++i)
{
int i2 = i;
for (int i3 = 0; i3 < iNumDigits; ++i3)
{
siDigit[i3] = array[i2 % iDigitBase];
i2 /= iDigitBase;
}
bool duplicate_free = siDigit.Distinct().Count() == siDigit.Length;
if (duplicate_free == true)
{
liste.Add(siDigit[0] + siDigit[1] + siDigit[2] + siDigit[3] + siDigit[4]);
Console.Write(siDigit[0] + siDigit[1] + siDigit[2] + siDigit[3] + siDigit[4]);
Console.WriteLine();
}
}
What i get is way to less entries in my list, i just get 1317051 entries and dont know why. I cant find any solution for my problem, may someone out there can help me out.
Any help would be greatly appreciated.
P.S In germany they dont teach better english :)
I think your algorithm is wrong.
Consider i equals 0, i2 equals 0, so siDigit[i3](i3 = 0, 1, 2, 3 ,4) are array[0], duplicate_free is false. You lose one. So, you can not get all entries.
Here is a recursive algorithm:
private static void GetCombination(ref List<string[]> list, string[] t, int n, int m, int[] b, int M)
{
for (int i = n; i >= m; i--)
{
b[m - 1] = i - 1;
if (m > 1)
{
GetCombination(ref list, t, i - 1, m - 1, b, M);
}
else
{
if (list == null)
{
list = new List<string[]>();
}
string[] temp = new string[M];
for (int j = 0; j < b.Length; j++)
{
temp[j] = t[b[j]];
}
list.Add(temp);
}
}
}
public static List<string[]> GetCombination(string[] t, int n)
{
if (t.Length < n)
{
return null;
}
int[] temp = new int[n];
List<string[]> list = new List<string[]>();
GetCombination(ref list, t, t.Length, n, temp, n);
return list;
}

Data histogram - optimized binwidth optimization

I'm looking to produce a data histogram from a given dataset. I've read about different options for constructing the histogram and I'm most interested in a method based on the work of
Shimazaki, H.; Shinomoto, S. (2007). "A method for selecting the bin
size of a time histogram"
The above method uses estimation to determine the optimal bin width and distribution, which is needed in my case because the sample data will vary in distribution and hard to determine the bin count and width in advance.
Can someone recommend a good source or a starting point for writing such a function in c# or have a close enough c# histogram code.
Many thanks.
The following is a port I wrote of the Python version of this algorithm from here. I know the API could do with some work, but this should be enough to get you started. The results of this code are identical to those produced by the Python code for the same input data.
public class HistSample
{
public static void CalculateOptimalBinWidth(double[] x)
{
double xMax = x.Max(), xMin = x.Min();
int minBins = 4, maxBins = 50;
double[] N = Enumerable.Range(minBins, maxBins - minBins)
.Select(v => (double)v).ToArray();
double[] D = N.Select(v => (xMax - xMin) / v).ToArray();
double[] C = new double[D.Length];
for (int i = 0; i < N.Length; i++)
{
double[] binIntervals = LinearSpace(xMin, xMax, (int)N[i] + 1);
double[] ki = Histogram(x, binIntervals);
ki = ki.Skip(1).Take(ki.Length - 2).ToArray();
double mean = ki.Average();
double variance = ki.Select(v => Math.Pow(v - mean, 2)).Sum() / N[i];
C[i] = (2 * mean - variance) / (Math.Pow(D[i], 2));
}
double minC = C.Min();
int index = C.Select((c, ix) => new { Value = c, Index = ix })
.Where(c => c.Value == minC).First().Index;
double optimalBinWidth = D[index];
}
public static double[] Histogram(double[] data, double[] binEdges)
{
double[] counts = new double[binEdges.Length - 1];
for (int i = 0; i < binEdges.Length - 1; i++)
{
double lower = binEdges[i], upper = binEdges[i + 1];
for (int j = 0; j < data.Length; j++)
{
if (data[j] >= lower && data[j] <= upper)
{
counts[i]++;
}
}
}
return counts;
}
public static double[] LinearSpace(double a, double b, int count)
{
double[] output = new double[count];
for (int i = 0; i < count; i++)
{
output[i] = a + ((i * (b - a)) / (count - 1));
}
return output;
}
}
Run it like this:
double[] x =
{
4.37, 3.87, 4.00, 4.03, 3.50, 4.08, 2.25, 4.70, 1.73,
4.93, 1.73, 4.62, 3.43, 4.25, 1.68, 3.92, 3.68, 3.10,
4.03, 1.77, 4.08, 1.75, 3.20, 1.85, 4.62, 1.97, 4.50,
3.92, 4.35, 2.33, 3.83, 1.88, 4.60, 1.80, 4.73, 1.77,
4.57, 1.85, 3.52, 4.00, 3.70, 3.72, 4.25, 3.58, 3.80,
3.77, 3.75, 2.50, 4.50, 4.10, 3.70, 3.80, 3.43, 4.00,
2.27, 4.40, 4.05, 4.25, 3.33, 2.00, 4.33, 2.93, 4.58,
1.90, 3.58, 3.73, 3.73, 1.82, 4.63, 3.50, 4.00, 3.67,
1.67, 4.60, 1.67, 4.00, 1.80, 4.42, 1.90, 4.63, 2.93,
3.50, 1.97, 4.28, 1.83, 4.13, 1.83, 4.65, 4.20, 3.93,
4.33, 1.83, 4.53, 2.03, 4.18, 4.43, 4.07, 4.13, 3.95,
4.10, 2.27, 4.58, 1.90, 4.50, 1.95, 4.83, 4.12
};
HistSample.CalculateOptimalBinWidth(x);
Check the Histogram function. If any data elements are unlucky to be equal to a bin boundary (other than the first or last bin), they will be counted in both consecutive bins.
The code needs to check (lower <= data[j] && data[j] < upper) and handle the case that all elements equal to xMax go into the last bin.
A small update to nick_w answer.
If you actually need the bins after. Plus optimized the double loop in histogram function away, plus got rid of linspace function.
/// <summary>
/// Calculate the optimal bins for the given data
/// </summary>
/// <param name="x">The data you have</param>
/// <param name="xMin">The minimum element</param>
/// <param name="optimalBinWidth">The width between each bin</param>
/// <returns>The bins</returns>
public static int[] CalculateOptimalBinWidth(List<double> x, out double xMin, out double optimalBinWidth)
{
var xMax = x.Max();
xMin = x.Min();
optimalBinWidth = 0;
const int MIN_BINS = 1;
const int MAX_BINS = 20;
int[] minKi = null;
var minOffset = double.MaxValue;
foreach (var n in Enumerable.Range(MIN_BINS, MAX_BINS - MIN_BINS).Select(v => v*5))
{
var d = (xMax - xMin)/n;
var ki = Histogram(x, n, xMin, d);
var ki2 = ki.Skip(1).Take(ki.Length - 2).ToArray();
var mean = ki2.Average();
var variance = ki2.Select(v => Math.Pow(v - mean, 2)).Sum()/n;
var offset = (2*mean - variance)/Math.Pow(d, 2);
if (offset < minOffset)
{
minKi = ki;
minOffset = offset;
optimalBinWidth = d;
}
}
return minKi;
}
private static int[] Histogram(List<double> data, int count, double xMin, double d)
{
var histogram = new int[count];
foreach (var t in data)
{
var bucket = (int) Math.Truncate((t - xMin)/d);
if (count == bucket) //fix xMax
bucket --;
histogram[bucket]++;
}
return histogram;
}
I would recommend binary search to speed up the assignment to the class intervals.
public void Add(double element)
{
if (element < Bins.First().LeftBound || element > Bins.Last().RightBound)
return;
var min = 0;
var max = Bins.Length - 1;
var index = 0;
while (min <= max)
{
index = min + ((max - min) / 2);
if (element >= Bins[index].LeftBound && element < Bins[index].RightBound)
break;
if (element < Bins[index].LeftBound)
max = index - 1;
else
min = index + 1;
}
Bins[index].Count++;
}
"Bins" is a list of items of type "HistogramItem" which defines properties like "Leftbound", "RightBound" and "Count".

Categories

Resources