Variables not updating c# - c#

I'm writing a C# Program to display the average grades, highest grade and lowerst grade
The result is supposed to be
the highest grade is 90.5
the lowerst grade is 77.3
the average grade is 85.633
But unfortunately show up like this
the highest grade is 1.7976931348623157E+308
the lowerst grade is -1.7976931348623157E+308
the average grade is 85.633
I had two days looking for a solution, but I couldn't, so I had to ask a question
Thank u
using System;
using System.Collections.Generic;
namespace GradeBook
{
public class Statistics
{
public double Average;
public double High;
public double Low;
}
public class Book
{
public Book()
{
grades = new List<double>();
}
public void addGrade(double grade)
{
grades.Add(grade);
}
public Statistics GetStatistics()
{
var result = new Statistics();
result.Average = 0.0;
result.High = double.MaxValue;
result.Low = double.MinValue;
foreach (var grade in grades)
{
result.Low = Math.Min(grade, result.Low);
result.High = Math.Max(grade, result.High);
result.Average += grade;
}
result.Average /= grades.Count;
return result;
}
private List<double> grades;
}
class Program
{
static void Main(string[] args)
{
Book book = new Book();
book.addGrade(89.1);
book.addGrade(90.5);
book.addGrade(77.3);
var stats = book.GetStatistics();
Console.WriteLine("the highest grade is " + stats.High);
Console.WriteLine("the lowerst grade is " + stats.Low);
Console.WriteLine($"the average grade is {stats.Average:N3}");
}
}
}

The problem is that you assgigned double.MaxValue to the High score and double.MinValue to the low.
So when you use Math.max / Math.min you are constantly getting the double.Max / double.Min.
try to just switch them in the following way (will only work assuming values are provided) :
result.High = double.MinValue;
result.Low = double.MaxValue;

So you have this code to evaluate each grade against the variables result.Low and result.High outside of the loop:
result.Low = Math.Min(grade, result.Low);
result.High = Math.Max(grade, result.High);
But you have defined them as this:
result.High = double.MaxValue;
result.Low = double.MinValue;
Therefore it isn't possible for grade > double.MaxValue, or grade < double.MinValue
A better solution is this:
var result = new Statistics();
var firstGrade = grades.DefaultIfEmpty().First(); // get the first grade in the list, or 0 if it's empty
// assign that to average, high, and low
result.Average = firstGrade;
result.High = firstGrade;
result.Low = firstGrade;
// visit every grade after the first
foreach (var grade in grades.Skip(1))
{
result.Low = Math.Min(grade, result.Low);
result.High = Math.Max(grade, result.High);
result.Average += grade;
}
result.Average /= grades.Count;
return result;
We should also consider the case that grades.Count == 0, which would throw an error on the result.Average /= grades.Count line. We can fix it like so:
if (grades.Count > 0)
{
result.Average /= grades.Count;
}
return result;

Swapping result.High to double.MinValue and result.Low to double.MaxValue should do the trick!

As others have said, you've mixed double.MaxValue anddouble.MinValue` around. Alternatively you could avoid them entirely by doing this:
public Statistics GetStatistics()
{
return new Statistics()
{
Average = grades.Average(),
High = grades.Max(),
Low = grades.Min(),
};
}

Related

Create average grade with double areas

I must create a console application. Write a LepsiStudent method that performs the following parameters on your input:
the name of the first student
field of first student marks
the name of the second student
field of second student marks
The method calculates the arithmetic means of the marks of these students and lists which student has a better average.
In the main method, prepare two fields of marks and student names. Then call the LepsiStudent method.
I code this and I don't know how to continue average grade can someone help me please?
{
class Program
{
static void Main(string[] args)
{
double[] znamkyjan = { 2, 4 };
double[] znamkydan = { 1, 5 };
LepsiStudent(znamkyjan,znamkydan);
}
static void LepsiStudent (double [] znamky, double[] znamky2)
{
Console.WriteLine("Jan Novák");
foreach (var znam in znamky)
{
//Console.WriteLine(znam);
Console.WriteLine(znam / 2);
}
Console.WriteLine("Daniel Havlík");
foreach (var znam2 in znamky2)
{
Console.WriteLine(znam2);
}
}
}
}
The Linq library has a built-in Average method:
using System;
using System.Linq;
public class Program
{
public static void Main(string[] args)
{
double[] znamkyjan = { 2, 4, 5, 7 };
double[] znamkydan = { 1, 5, 5, 9 };
LepsiStudent(znamkyjan,znamkydan);
}
static void LepsiStudent (double [] znamky, double[] znamky2)
{
double m1 = znamky.Average();
double m2 = znamky2.Average();
Console.WriteLine("Jan Novák: " + m1);
Console.WriteLine("Daniel Havlík: " + m2);
}
}
Output
Jan Novák: 4.5
Daniel Havlík: 5
To calculate an average you need to sum the values and then divide by the number of grades,
and you want to do that for each student.
We could do this with less variables, but this is a little more readable for someone new to programming.
we go over all the first student grades- sum them and divide, and the same for 2nd student- then we compare and print the result..
static void LepsiStudent(double[] znamky, double[] znamky2)
{
double student1Sum = 0;
double student1Avg = 0;
double student2Sum = 0;
double student2Avg = 0;
foreach (var znam in znamky)
{
student1Sum += znam;
}
student1Avg = student1Sum / znamky.Length;
foreach (var znam2 in znamky2)
{
student2Sum += znam2;
}
student2Avg = student2Sum / znamky2.Length;
if (student1Avg > student2Avg)
{
Console.WriteLine("first student has the higher average");
}
else if (student1Avg == student2Avg)
{
Console.WriteLine("neither student has the higher average");
}
else
{
Console.WriteLine("second student has the higher average");
}
}
Just for fun, this does the same thing with linq:
static void LepsiStudent(double[] znamky, double[] znamky2)
{
double student1Avg = znamky.Sum() / znamky.Length; ;
double student2Avg = znamky2.Sum() / znamky2.Length;
string name = (student1Avg == student2Avg) ? "neither" :
Math.Max(student1Avg, student2Avg) == student1Avg ? "first" : "second";
Console.WriteLine($"{name} student has the higher average");
}

C# Detects the maximum and lists all the elements that acquire

Please can somebody help me with C# issue?
Create a program that detects the maximum and lists all the elements that acquire this value. The name of the pupil and the height of the pupil will be entered at the entrance with the precision per cm. At the output, the program lists the maximum altitude of all pupils and the next line of the names of all pupils who reach this maximum height (in the same order as the names were entered).
Inputs:
Martin Novacek 171
Bohumil Betak 177
Ladislav Zlatohlavek 150
Hana Drbohlavova 177
Result:
177
Bohumil Betak
Hana Drbohlavova
My code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ulozka5
{
class Program
{
static void Main(string[] args)
{
string input;
int max = 0;
string[] array = new string[9999];
for (int i = 0; i < array.Length; i++) // inputs
{
input = Console.ReadLine();
if (input == "" || input == null)
{
Array.Resize(ref array, i);
break;
}
else
{
array[i] = input;
}
}
for (int i = 0; i < array.Length; i++) //max value
{
var number = int.Parse(new string(array[i].Where(char.IsDigit).ToArray()));
if (max < number)
{
max = number;
}
}
Console.WriteLine(max);
for (int i = 0; i < array.Length; i++) //compare with max
{
var cislo = int.Parse(new string(array[i].Where(char.IsDigit).ToArray()));
if (cislo == max)
{
var name = Regex.Replace(array[i], #"[\d-]", string.Empty);
Console.WriteLine(name);
}
}
}
}
}
Everything works fine, but scholar system gave me Score 20 for this :( ... Thanks for any idea..
There are lot of ways in which you can improve the code. Here are some suggestions.
Input Part
Instead of defining a huge array upfront, you can create a List and wait for User to enter a special terminating character (in this case, Programs waits for User to enter "q" to end input). Also, you can use regex to parse the Name/Altitude parts and store in a object.
string input = string.Empty;
var list = new List<Pupils>();
var regex = new Regex(#"(?<name>[a-zA-Z\s]+)\s+(?<altitude>[0-9]+)",RegexOptions.Compiled);
while((input = Console.ReadLine()) != "q")
{
var matches = regex.Match(input.Trim());
list.Add(new Pupils
{
Name = matches.Groups["name"].Value,
Altitude = int.Parse(matches.Groups["altitude"].Value)
});
}
Where Pupils is defined as
public class Pupils
{
public string Name {get;set;}
public int Altitude {get;set;}
}
Querying Max and related Users
Then you can calculate Max Altitude can be found using Linq
var maxAltitude = list.Max(x=>x.Altitude);
With maxAltitude in hand, you can now parse the Users with max value.
var result = list.Where(x=>x.Altitude.Equals(maxAltitude)).Select(x=>x.Name);
Complete code:
void Main()
{
string input = string.Empty;
var list = new List<Pupils>();
var regex = new Regex(#"(?<name>[a-zA-Z\s]+)\s+(?<altitude>[0-9]+)",RegexOptions.Compiled);
while((input = Console.ReadLine()) != "q")
{
var matches = regex.Match(input.Trim());
list.Add(new Pupils
{
Name = matches.Groups["name"].Value,
Altitude = int.Parse(matches.Groups["altitude"].Value)
});
}
var maxAltitude = list.Max(x=>x.Altitude);
var result = list.Where(x=>x.Altitude.Equals(maxAltitude)).Select(x=>x.Name);
Console.WriteLine($"Max Altitude :{maxAltitude}");
foreach(var item in result)
{
Console.WriteLine(item);
}
}
public class Pupils
{
public string Name {get;set;}
public int Altitude {get;set;}
}
Just for fun.
Your code can be little bid simpler if you will use separator character between name and height.
// Get pupils and calculate max height
var pupils =
Enumerable.Range(0, int.MaxValue)
.Select(i => Console.ReadLine())
.TakeWhile(input => string.IsNullOrWhiteSpace(input) == false)
.Select(input => input.Split(':'))
.Select(values =>
{
var name = values.First();
var validHeight = int.TryParse(values.Last(), out int height);
return (Name: name, Height: height, Valid: validHeight);
})
.Where(pupil => pupil.Valid)
.ToList();
var maxHeight = pupils.Max(pupil => pupil.Height);
// Build output string
var output =
pupils.Where(pupil => pupil.Height == maxHeight)
.Aggregate(new StringBuilder().AppendLine(maxHeight.ToString()),
(builder, pupil) => builder.AppendLine(pupil.Name));
Console.WriteLine(output);

Object calculating validation C#

My goal is to make this program to take a number of pizzas and types of pizzas and count how much they cost. I decided to go with an object solution. The problem is it doesn't calculate it and it lets the program run even when The fields are empty. I literally have no idea why it doesn't calculate it. I'm also new to objects so there may be some logical mistakes.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Assignment_2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void OrderButton_Click(object sender, EventArgs e)
{
double withTax = 0;
double tax = 0;
double subTotal = 0;
var pizzas = new Pizza[3];
if(ValidateAndDeclareQuantities())
{
pizzas = Declare();
subTotal = CalcSubTotal(pizzas);
tax = CalcTax(pizzas);
withTax = CalcWithTax(pizzas);
}
}
bool ValidateAndDeclareQuantities()
{
var combolist = new List<ComboBox>();
combolist.Add(comboBox1);
combolist.Add(comboBox2);
combolist.Add(comboBox3);
var textboxlist = new List<TextBox>();
textboxlist.Add(Quantity1);
textboxlist.Add(Quantity2);
textboxlist.Add(Quantity3);
for (int i = 0; i < 3; i++)
{
if (combolist[i].Text == "Cheese" || combolist[i].Text == "Vegetable" || combolist[i].Text == "Meat")
{ }
else combolist[i].Text = "Wrong input";
}
int[] Quantities = new int[3];
for (int i = 0; i < 3; i++)
{
if (int.TryParse(textboxlist[i].Text, out Quantities[i])&&textboxlist[i].Text!=null)
{ }
else { textboxlist[i].Text = "Wrong input"; }
}
return true;
}
Pizza[] Declare()
{
var pizzas = new Pizza[3];
string type;
int price;
type = comboBox1.Text;
price = int.Parse(Quantity1.Text);
Pizza pizza1 = new Pizza(type, price);
pizzas[0] = pizza1;
type = comboBox2.Text;
price = int.Parse(Quantity2.Text);
Pizza pizza2 = new Pizza(type, price);
pizzas[1] = pizza2;
type = comboBox3.Text;
price = int.Parse(Quantity3.Text);
Pizza pizza3 = new Pizza(type, price);
pizzas[2] = pizza3;
return pizzas;
}
double CalcSubTotal(Pizza[] pizzas)
{
double subTotal = 0;
for (int i = 0; i < 3; i++)
{
subTotal += pizzas[i].Price;
}
return subTotal;
}
double CalcTax(Pizza[] pizzas)
{
double tax = 0;
for (int i = 0; i < 3; i++)
{
tax += pizzas[i].Tax;
}
return tax;
}
double CalcWithTax(Pizza[] pizzas)
{
double withTax = 0;
for (int i = 0; i < 3; i++)
{
withTax += pizzas[i].WithTax;
}
return withTax;
}
void WriteOut(double subTotal, double tax, double withTax)
{
lblSubTotal.Text = "" + subTotal;
lblTax.Text = "" + tax;
lblTotal.Text = "" + withTax;
}
}
}
And the class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Assignment_2
{
class Pizza
{
string type;
int quantity;
public double Price;
public double SubTotal;
public double Tax;
public double WithTax;
public Pizza(string type, int quantity)
{
this.type = type;
this.quantity = quantity;
FindPrice();
CalcSubTotal();
CalcTax();
CalcWithTax();
}
private void FindPrice()
{
switch (type)
{
case "Cheese":
Price = 9.95;
break;
case "Vegetables":
Price = 10.95;
break;
case "Meat":
Price = 11.95;
break;
}
}
private void CalcSubTotal()
{
SubTotal = Price * quantity;
}
private void CalcTax()
{
Tax = SubTotal * 0.13;
}
private void CalcWithTax()
{
WithTax = SubTotal + Tax;
}
}
}
Solution form
The quick answers:
ValidateAndDeclareQuantities never returns false. It should (probably) return false when you set "Wrong Input".
(Minor) int[] Quantities = new int[3]; is never used, aside from writing to it.
(Minor) var pizzas = new Pizza[3]; is also never used. It just gets overwritten by Declare a few lines later. Pizza[] pizzas=null; or just Pizza[] pizzas; is a better alternative. Not the greatest structure here though.
(Minor) Your variable called price in Declare is poorly named as it appears to actually be quantity. Things like this easily throw people off.
WriteOut is never called. withTax, tax and subTotal in OrderButton_Click are probably being computed correctly, but the values aren't being outputted.
The longer answer
It's a bit on the messy side! I appreciate that it's just a learning thing - we've all been there - but good code hygiene is just as important (if not more important) than the structure of the language.
UX: Don't overwrite what the user entered - specifically, don't replace the textbox input with "wrong input"; That's better off going on some other label. I would imagine you've already felt how weird this kind of experience is whilst testing the code.
Named things that don't need a specific class: Like a cheese pizza and a ham one. Enums are your friend! Use them instead of strings like "Cheese":
public enum PizzaType{
Cheese,
Tomato
}
Using enums in this way helps avoid the wonderful world of pain that is unexpected capitalisation and it's considerably faster too. CheEse pizza anyone?
Repetition: Large portions of your code are repetitive too; You'll want to practice avoiding it as much as you can. ('DRY'/ 'Don't Repeat Yourself'). A little forward planning helps massively. Everybody has preferences on code structure; mine here would be a separate "Pizza displayer" class which holds a quantity input box and does the validation too.
Junk: Slightly related to the above, you're creating a bunch of Lists and arrays which get created each time the function is called and then are just chucked out. Create a single array of some more abstract type (like an array of "Pizza displayers") and keep that array as a property on the Form. It's minor here, but being more aware of how much trash your program creates helps make your code go faster.
Notes on floats: You should never, ever use float/ double for money. Use decimal instead, or just do everything in pennies. Floating points aren't precise and you'll hit a rounding issue sooner or later.

Find Minimum and Maximum values within a multi dimensional struct array

For a school project in learning c# I am making a data collection console application which saves floating point entries for 4 different locations along with the time/date, and user who recorded the entry. I am required to use a multi dimensional struct array.
I need to have a view that displays the average of the values, along with the minimum and maximum values and the first and last date. I've got the average figured out by counting through the values and adding cumulatively but I can't figure out how to find the minimum and maximum values.
I tried searching and came across this page: http://www.dotnetperls.com/max
I tried to implement this syntax into my code to no avail due to it being a much more complex array.
It worked in my test with integers:
class Program
{
static int[][] intarray = new int[4][] { new int[10], new int[10], new int[10], new int[10] };
static void Main(string[] args)
{
intarray[0][0] = 5;
intarray[0][1] = 3;
intarray[0][2] = 10;
intarray[0][3] = 4;
intarray[0][4] = 2;
Console.WriteLine(intarray[0].Max());
Console.ReadLine();
}
}
The above code perfectly displays the number 10! :)
But when I tried to implement this in to my program with a struct array, it doesn't work:
class Program
{
static byte location = 0;
static float entriesmin;
static float entriesmax;
static Entry[][] entriesarray = new Entry[4][] { new Entry[10], new Entry[10], new Entry[10], new Entry[10] };
struct Entry
{
public float value;
public string user;
public DateTime datetime;
}
static byte LoopEntries(bool display)
{
float runningtotal = 0;
byte entrycount = 0;
foreach (Entry entry in entriesarray[0])
{
if (entry.user != null)
{
if (display)
{
string ten;
if (entrycount == 9)
{
ten = " #";
}
else
{
ten = " #";
}
Console.WriteLine(ten + (entrycount + 1) + " " + entry.datetime + " " + entry.user + new string(' ', 16 - entry.user.Length) + entry.value);
}
runningtotal += entry.value;
entrycount += 1;
}
}
entriesmin = (entriesarray[location]).value.Min();
entriesmax = (entriesarray[location]).value.Max();
if (entrycount == 0 && display)
{
Console.WriteLine("No entries to show!");
}
return entrycount;
}
I need to hand this project in on Monday! I really hope someone can help me! ;)
What you have is not a multidimensional array, it's an array of array (a.k.a. a jagged array), but you work with them in a similar way.
To loop through the array of arrays you need a loop in a loop:
foreach (Entry[] arr in entriesarray) {
foreach (Entry entry in arr) {
// ...
}
}
You could use the Min and Max extension methods to get the minimum and maximum value of each inner array, but you still would have to find the minimum and maximum between those values. As you are already looping through all the entries anyway to get the average you can just keep a record of the smallest and largest values found:
float runningtotal = 0;
int entrycount = 0;
float min = float.MaxValue;
float max = float.MinValue;
foreach (Entry[] arr in entriesarray) {
foreach (Entry entry in arr) {
runningtotal += entry.value;
entrycount++;
if (entry.value < min) {
min = entry.value;
}
if (entry.value > max) {
max = entry.value;
}
}
}

selection based on percentage weighting

I have a set of values, and an associated percentage for each:
a: 70% chance
b: 20% chance
c: 10% chance
I want to select a value (a, b, c) based on the percentage chance given.
how do I approach this?
my attempt so far looks like this:
r = random.random()
if r <= .7:
return a
elif r <= .9:
return b
else:
return c
I'm stuck coming up with an algorithm to handle this. How should I approach this so it can handle larger sets of values without just chaining together if-else flows.
(any explanation or answers in pseudo-code are fine. a python or C# implementation would be especially helpful)
Here is a complete solution in C#:
public class ProportionValue<T>
{
public double Proportion { get; set; }
public T Value { get; set; }
}
public static class ProportionValue
{
public static ProportionValue<T> Create<T>(double proportion, T value)
{
return new ProportionValue<T> { Proportion = proportion, Value = value };
}
static Random random = new Random();
public static T ChooseByRandom<T>(
this IEnumerable<ProportionValue<T>> collection)
{
var rnd = random.NextDouble();
foreach (var item in collection)
{
if (rnd < item.Proportion)
return item.Value;
rnd -= item.Proportion;
}
throw new InvalidOperationException(
"The proportions in the collection do not add up to 1.");
}
}
Usage:
var list = new[] {
ProportionValue.Create(0.7, "a"),
ProportionValue.Create(0.2, "b"),
ProportionValue.Create(0.1, "c")
};
// Outputs "a" with probability 0.7, etc.
Console.WriteLine(list.ChooseByRandom());
For Python:
>>> import random
>>> dst = 70, 20, 10
>>> vls = 'a', 'b', 'c'
>>> picks = [v for v, d in zip(vls, dst) for _ in range(d)]
>>> for _ in range(12): print random.choice(picks),
...
a c c b a a a a a a a a
>>> for _ in range(12): print random.choice(picks),
...
a c a c a b b b a a a a
>>> for _ in range(12): print random.choice(picks),
...
a a a a c c a c a a c a
>>>
General idea: make a list where each item is repeated a number of times proportional to the probability it should have; use random.choice to pick one at random (uniformly), this will match your required probability distribution. Can be a bit wasteful of memory if your probabilities are expressed in peculiar ways (e.g., 70, 20, 10 makes a 100-items list where 7, 2, 1 would make a list of just 10 items with exactly the same behavior), but you could divide all the counts in the probabilities list by their greatest common factor if you think that's likely to be a big deal in your specific application scenario.
Apart from memory consumption issues, this should be the fastest solution -- just one random number generation per required output result, and the fastest possible lookup from that random number, no comparisons &c. If your likely probabilities are very weird (e.g., floating point numbers that need to be matched to many, many significant digits), other approaches may be preferable;-).
Knuth references Walker's method of aliases. Searching on this, I find http://code.activestate.com/recipes/576564-walkers-alias-method-for-random-objects-with-diffe/ and http://prxq.wordpress.com/2006/04/17/the-alias-method/. This gives the exact probabilities required in constant time per number generated with linear time for setup (curiously, n log n time for setup if you use exactly the method Knuth describes, which does a preparatory sort you can avoid).
Take the list of and find the cumulative total of the weights: 70, 70+20, 70+20+10. Pick a random number greater than or equal to zero and less than the total. Iterate over the items and return the first value for which the cumulative sum of the weights is greater than this random number:
def select( values ):
variate = random.random() * sum( values.values() )
cumulative = 0.0
for item, weight in values.items():
cumulative += weight
if variate < cumulative:
return item
return item # Shouldn't get here, but just in case of rounding...
print select( { "a": 70, "b": 20, "c": 10 } )
This solution, as implemented, should also be able to handle fractional weights and weights that add up to any number so long as they're all non-negative.
Let T = the sum of all item weights
Let R = a random number between 0 and T
Iterate the item list subtracting each item weight from R and return the item that causes the result to become <= 0.
def weighted_choice(probabilities):
random_position = random.random() * sum(probabilities)
current_position = 0.0
for i, p in enumerate(probabilities):
current_position += p
if random_position < current_position:
return i
return None
Because random.random will always return < 1.0, the final return should never be reached.
import random
def selector(weights):
i=random.random()*sum(x for x,y in weights)
for w,v in weights:
if w>=i:
break
i-=w
return v
weights = ((70,'a'),(20,'b'),(10,'c'))
print [selector(weights) for x in range(10)]
it works equally well for fractional weights
weights = ((0.7,'a'),(0.2,'b'),(0.1,'c'))
print [selector(weights) for x in range(10)]
If you have a lot of weights, you can use bisect to reduce the number of iterations required
import random
import bisect
def make_acc_weights(weights):
acc=0
acc_weights = []
for w,v in weights:
acc+=w
acc_weights.append((acc,v))
return acc_weights
def selector(acc_weights):
i=random.random()*sum(x for x,y in weights)
return weights[bisect.bisect(acc_weights, (i,))][1]
weights = ((70,'a'),(20,'b'),(10,'c'))
acc_weights = make_acc_weights(weights)
print [selector(acc_weights) for x in range(100)]
Also works fine for fractional weights
weights = ((0.7,'a'),(0.2,'b'),(0.1,'c'))
acc_weights = make_acc_weights(weights)
print [selector(acc_weights) for x in range(100)]
today, the update of python document give an example to make a random.choice() with weighted probabilities:
If the weights are small integer ratios, a simple technique is to build a sample population with repeats:
>>> weighted_choices = [('Red', 3), ('Blue', 2), ('Yellow', 1), ('Green', 4)]
>>> population = [val for val, cnt in weighted_choices for i in range(cnt)]
>>> random.choice(population)
'Green'
A more general approach is to arrange the weights in a cumulative distribution with itertools.accumulate(), and then locate the random value with bisect.bisect():
>>> choices, weights = zip(*weighted_choices)
>>> cumdist = list(itertools.accumulate(weights))
>>> x = random.random() * cumdist[-1]
>>> choices[bisect.bisect(cumdist, x)]
'Blue'
one note: itertools.accumulate() needs python 3.2 or define it with the Equivalent.
I think you can have an array of small objects (I implemented in Java although I know a little bit C# but I am afraid can write wrong code), so you may need to port it yourself. The code in C# will be much smaller with struct, var but I hope you get the idea
class PercentString {
double percent;
String value;
// Constructor for 2 values
}
ArrayList<PercentString> list = new ArrayList<PercentString();
list.add(new PercentString(70, "a");
list.add(new PercentString(20, "b");
list.add(new PercentString(10, "c");
double percent = 0;
for (int i = 0; i < list.size(); i++) {
PercentString p = list.get(i);
percent += p.percent;
if (random < percent) {
return p.value;
}
}
If you are really up to speed and want to generate the random values quickly, the Walker's algorithm mcdowella mentioned in https://stackoverflow.com/a/3655773/1212517 is pretty much the best way to go (O(1) time for random(), and O(N) time for preprocess()).
For anyone who is interested, here is my own PHP implementation of the algorithm:
/**
* Pre-process the samples (Walker's alias method).
* #param array key represents the sample, value is the weight
*/
protected function preprocess($weights){
$N = count($weights);
$sum = array_sum($weights);
$avg = $sum / (double)$N;
//divide the array of weights to values smaller and geq than sum/N
$smaller = array_filter($weights, function($itm) use ($avg){ return $avg > $itm;}); $sN = count($smaller);
$greater_eq = array_filter($weights, function($itm) use ($avg){ return $avg <= $itm;}); $gN = count($greater_eq);
$bin = array(); //bins
//we want to fill N bins
for($i = 0;$i<$N;$i++){
//At first, decide for a first value in this bin
//if there are small intervals left, we choose one
if($sN > 0){
$choice1 = each($smaller);
unset($smaller[$choice1['key']]);
$sN--;
} else{ //otherwise, we split a large interval
$choice1 = each($greater_eq);
unset($greater_eq[$choice1['key']]);
}
//splitting happens here - the unused part of interval is thrown back to the array
if($choice1['value'] >= $avg){
if($choice1['value'] - $avg >= $avg){
$greater_eq[$choice1['key']] = $choice1['value'] - $avg;
}else if($choice1['value'] - $avg > 0){
$smaller[$choice1['key']] = $choice1['value'] - $avg;
$sN++;
}
//this bin comprises of only one value
$bin[] = array(1=>$choice1['key'], 2=>null, 'p1'=>1, 'p2'=>0);
}else{
//make the second choice for the current bin
$choice2 = each($greater_eq);
unset($greater_eq[$choice2['key']]);
//splitting on the second interval
if($choice2['value'] - $avg + $choice1['value'] >= $avg){
$greater_eq[$choice2['key']] = $choice2['value'] - $avg + $choice1['value'];
}else{
$smaller[$choice2['key']] = $choice2['value'] - $avg + $choice1['value'];
$sN++;
}
//this bin comprises of two values
$choice2['value'] = $avg - $choice1['value'];
$bin[] = array(1=>$choice1['key'], 2=>$choice2['key'],
'p1'=>$choice1['value'] / $avg,
'p2'=>$choice2['value'] / $avg);
}
}
$this->bins = $bin;
}
/**
* Choose a random sample according to the weights.
*/
public function random(){
$bin = $this->bins[array_rand($this->bins)];
$randValue = (lcg_value() < $bin['p1'])?$bin[1]:$bin[2];
}
Here is my version that can apply to any IList and normalize the weight. It is based on Timwi's solution : selection based on percentage weighting
/// <summary>
/// return a random element of the list or default if list is empty
/// </summary>
/// <param name="e"></param>
/// <param name="weightSelector">
/// return chances to be picked for the element. A weigh of 0 or less means 0 chance to be picked.
/// If all elements have weight of 0 or less they all have equal chances to be picked.
/// </param>
/// <returns></returns>
public static T AnyOrDefault<T>(this IList<T> e, Func<T, double> weightSelector)
{
if (e.Count < 1)
return default(T);
if (e.Count == 1)
return e[0];
var weights = e.Select(o => Math.Max(weightSelector(o), 0)).ToArray();
var sum = weights.Sum(d => d);
var rnd = new Random().NextDouble();
for (int i = 0; i < weights.Length; i++)
{
//Normalize weight
var w = sum == 0
? 1 / (double)e.Count
: weights[i] / sum;
if (rnd < w)
return e[i];
rnd -= w;
}
throw new Exception("Should not happen");
}
I've my own solution for this:
public class Randomizator3000
{
public class Item<T>
{
public T value;
public float weight;
public static float GetTotalWeight<T>(Item<T>[] p_itens)
{
float __toReturn = 0;
foreach(var item in p_itens)
{
__toReturn += item.weight;
}
return __toReturn;
}
}
private static System.Random _randHolder;
private static System.Random _random
{
get
{
if(_randHolder == null)
_randHolder = new System.Random();
return _randHolder;
}
}
public static T PickOne<T>(Item<T>[] p_itens)
{
if(p_itens == null || p_itens.Length == 0)
{
return default(T);
}
float __randomizedValue = (float)_random.NextDouble() * (Item<T>.GetTotalWeight(p_itens));
float __adding = 0;
for(int i = 0; i < p_itens.Length; i ++)
{
float __cacheValue = p_itens[i].weight + __adding;
if(__randomizedValue <= __cacheValue)
{
return p_itens[i].value;
}
__adding = __cacheValue;
}
return p_itens[p_itens.Length - 1].value;
}
}
And using it should be something like that (thats in Unity3d)
using UnityEngine;
using System.Collections;
public class teste : MonoBehaviour
{
Randomizator3000.Item<string>[] lista;
void Start()
{
lista = new Randomizator3000.Item<string>[10];
lista[0] = new Randomizator3000.Item<string>();
lista[0].weight = 10;
lista[0].value = "a";
lista[1] = new Randomizator3000.Item<string>();
lista[1].weight = 10;
lista[1].value = "b";
lista[2] = new Randomizator3000.Item<string>();
lista[2].weight = 10;
lista[2].value = "c";
lista[3] = new Randomizator3000.Item<string>();
lista[3].weight = 10;
lista[3].value = "d";
lista[4] = new Randomizator3000.Item<string>();
lista[4].weight = 10;
lista[4].value = "e";
lista[5] = new Randomizator3000.Item<string>();
lista[5].weight = 10;
lista[5].value = "f";
lista[6] = new Randomizator3000.Item<string>();
lista[6].weight = 10;
lista[6].value = "g";
lista[7] = new Randomizator3000.Item<string>();
lista[7].weight = 10;
lista[7].value = "h";
lista[8] = new Randomizator3000.Item<string>();
lista[8].weight = 10;
lista[8].value = "i";
lista[9] = new Randomizator3000.Item<string>();
lista[9].weight = 10;
lista[9].value = "j";
}
void Update ()
{
Debug.Log(Randomizator3000.PickOne<string>(lista));
}
}
In this example each value has a 10% chance do be displayed as a debug =3
Based loosely on python's numpy.random.choice(a=items, p=probs), which takes an array and a probability array of the same size.
public T RandomChoice<T>(IEnumerable<T> a, IEnumerable<double> p)
{
IEnumerator<T> ae = a.GetEnumerator();
Random random = new Random();
double target = random.NextDouble();
double accumulator = 0;
foreach (var prob in p)
{
ae.MoveNext();
accumulator += prob;
if (accumulator > target)
{
break;
}
}
return ae.Current;
}
The probability array p must sum to (approx.) 1. This is to keep it consistent with the numpy interface (and mathematics), but you could easily change that if you wanted.

Categories

Resources