I have a very specific and long-winded question for you all. This question is both about programming and game-theory. I recently added spawnable ore to my Turn Based Strategy Game: http://imgur.com/gallery/0F5D5Ij (For those of you that look please forgive the development textures).
Now, onto the enigma that I have been contemplating. In my game, ore is generated each time a new map is created. 0-8 ore nodes are generated per level-creation. I already have this working; except it only generates "Emeraldite" at this point, which brings me to my question.
How would I, the programmer, make it so nodes have specific rarity? Consider this short mockup which is not actually game data:
(Pseudo Chances that a node will be one of the following)
Bloodstone 1 in 100
Default(Empty Node) 1 in 10
Copper 1 in 15
Emeraldite 1 in 35
Gold 1 in 50
Heronite 1 in 60
Platinum 1 in 60
Shadownite 1 in 75
Silver 1 in 35
Soranite 1 in 1000
Umbrarite 1 in 1000
Cobalt 1 in 75
Iron 1 in 15
I want to make it so that a generated node could be, theoretically, any of the above, however, with the odds also considered. I hope that question is clear enough. I have been trying to wrap my head around this, and even tried to write out a few if statements with randoms, however, I keep coming up empty handed.
Basically, I just want you guys to see my issue, and hopefully provide me with some insight on how I could approach this in a dynamic kind of way.
If any clarification is needed, please ask; sorry again if this was convoluted.
(I am adding C# as a tag only because that is the language I am using for this project)
I'd first represent the probability of each loot type as a simple number.
A probability in pure mathematics is conventionally expressed as a floating point number in the range 0 to 1, but for efficiency, you can use integers in any (large enough) range (each value is the 0-1 value multiplied by the maximum (which I'm calling MaxProbability here)).
e.g. Bloodstone (1 in 100) is 1/100 = 0.01, or MaxProbability * (1/100).
Copper (1 in 15) is 1/15 = 0.06667, or MaxProbability * (1/15).
I'm assuming that 'Default (Empty Node)' means the probability of none of the others.
In this case, the simplest way is not to define it - you get it if none of the others are chosen.
If 'Default' was included, the sum of all these probabilities would be 1 (i.e. 100%) (or MaxProbability, if using integers).
The 1/10 probability of 'Default' in your example is actually a contradiction because the total of all those probabilities is not 1 (it's 0.38247619 - the sum of the probability as calculated in my examples above).
Then you would choose a random number in the range 0 to 1 (or MaxProbability if using integers), and the chosen loot type is the first one in the list such that the sum of the probabilities of it and all previous ones ("cumulative probability") is greater than the random number.
e.g.
MaxProbability = 1000 (I'm using this to make it easy to read).
(For accurate probabilities, you could use 0x7FFFFFFF).
Type Probability Cumulative
---- ----------- ----------
Bloodstone 10 10 (0..9 yield Bloodstone)
Copper 67 77 (10+67) (10..76 yield Copper)
Emeraldite 29 105 (77+29)
Gold 20 125 etc.
Heronite 17 142
Platinum 17 159
Shadownite 13 172
Silver 29 200
Soranite 1 201
Umbrarite 1 202
Cobalt 13 216
Iron 67 282
Default (Empty Node) 7175 1000 (anything else)
e.g. If your random number in the range 0 to 999 (inclusive) was 184 (or anything in the range 172 to 199), you would choose "Silver" (the first one with cumulative probability greater than this).
You could hold the cumulative probabilities in an array and loop through it until you find one higher than the random number, or reach the end.
The order of the list does not matter.
You chose a random number only once per instance.
Including 'Default (Empty Node)' in the list means that the last cumulative probability will always be MaxProbability and the loop that searches it would never go past the end. (Alternatively, 'Default' can be omitted, and you choose it if the loop reaches the end of the list.)
Note that choosing a random number for each one in turn, e.g. a 1/10 chance of 'Bloodstone', then a 1/15 chance of Copper if not Bloodstone, skews the probabilities towards the earlier items:
The actual probability of Copper would be (1/15) * (1 - (1/10)) - 10% less than 1/15.
Here's code to do it (the actual choosing is 5 statements - in the method Choose ).
using System;
namespace ConsoleApplication1
{
class LootChooser
{
/// <summary>
/// Choose a random loot type.
/// </summary>
public LootType Choose()
{
LootType lootType = 0; // start at first one
int randomValue = _rnd.Next(MaxProbability);
while (_lootProbabilites[(int)lootType] <= randomValue)
{
lootType++; // next loot type
}
return lootType;
}
/// <summary>
/// The loot types.
/// </summary>
public enum LootType
{
Bloodstone, Copper, Emeraldite, Gold, Heronite, Platinum,
Shadownite, Silver, Soranite, Umbrarite, Cobalt, Iron, Default
};
/// <summary>
/// Cumulative probabilities - each entry corresponds to the member of LootType in the corresponding position.
/// </summary>
protected int[] _lootProbabilites = new int[]
{
10, 77, 105, 125, 142, 159, 172, 200, 201, 202, 216, 282, // (from the table in the answer - I used a spreadsheet to generate these)
MaxProbability
};
/// <summary>
/// The range of the probability values (dividing a value in _lootProbabilites by this would give a probability in the range 0..1).
/// </summary>
protected const int MaxProbability = 1000;
protected Random _rnd = new Random((int)(DateTime.Now.Ticks & 0x7FFFFFFF));
/// <summary>
/// Simple 'main' to demonstrate.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
var chooser = new LootChooser();
for(int n=0; n < 100; n++)
Console.Out.WriteLine(chooser.Choose());
}
}
}
You could rewrite all chances so that they use the same divisor (e.g. 1000), your chances then become
Bloodstone 10 in 1000
Default(Empty Node) 100 in 1000
Gold 20 in 1000
Next, create an array of a 1000 elements, and fill it with
10 Bloodstone elements,
100 Empty elements,
20 Gold elements,
etc.
Finally, generate a random number between 0 and 1000, and use that as the index into the element array, this will give
you your random element.
You might have to play with the chances a bit, since you'll probably want all 1000 array elements to be filled, but this is the general idea.
edit its not the most efficient implementation (at least in terms of memory usage, its running time should be good), but I chose this since it allows for a concise explanation that doesn't require a whole lot of math.
First of all, specifying the the default-empty node's probability is unnecessary. The other probabilities should be defined in such a way, that the empty node is created if no other type is created.
How to do this and ensure the generation probabiltiies are equal to those you specified? In short:
convert the probabilities to a floating point (it's a value with a common divisor of 1)
sum all probabilities and check if they are < 1
write a class which will store the all the probabilities
write a function which will get a random node based on those probabilities
For your example:
Bloodstone 1 in 100 = 0.01
Copper 1 in 15 ~= 0.07
Emeraldite 1 in 35 ~= 0.03
Gold 1 in 50 = 0.02
Default = 0.87
Now the class can be implemented in at least two ways. My option consumes much memory, does the computations once, but it also rounds the probability values which may introduce some error. Note, that the error depends on the arrSize variable - the larger it is, the smaller the error.
The other option is as in Bogusz's answer. It is more precise, but required more operations per each generated element.
Option suggested by Thomas requires a lot of repeatable code for each option hence is not versatile. Shellshock's answer will have invalid effective probabilities.
Astrotrain's idea to force yourself to use the same divisor is virtually the same as my own, though the implementation would be slightly different.
Here's a sample implementation of my idea (in java, but should be ported very easily):
public class NodeEntry {
String name;
double probability;
public NodeEntry(String name, double probability) {
super();
this.name = name;
this.probability = probability;
}
public NodeEntry(String name, int howMany, int inHowMany) {
this.name = name;
this.probability = 1.0 * howMany / inHowMany;
}
public final String getName() {
return name;
}
public final void setName(String name) {
this.name = name;
}
public final double getProbability() {
return probability;
}
public final void setProbability(double probability) {
this.probability = probability;
}
#Override
public String toString() {
return name+"("+probability+")";
}
static final NodeEntry defaultNode = new NodeEntry("default", 0);
public static final NodeEntry getDefaultNode() {
return defaultNode;
}
}
public class NodeGen {
List<NodeEntry> nodeDefinitions = new LinkedList<NodeEntry>();
public NodeGen() {
}
public boolean addNode(NodeEntry e) {
return nodeDefinitions.add(e);
}
public boolean addAllNodes(Collection<? extends NodeEntry> c) {
return nodeDefinitions.addAll(c);
}
static final int arrSize = 10000;
NodeEntry randSource[] = new NodeEntry[arrSize];
public void compile() {
checkProbSum();
int offset = 0;
for (NodeEntry ne: nodeDefinitions) {
int amount = (int) (ne.getProbability() * arrSize);
for (int a=0; a<amount;a++) {
randSource[a+offset] = ne;
}
offset+=amount;
}
while (offset<arrSize) {
randSource[offset] = NodeEntry.getDefaultNode();
offset++;
}
}
Random gen = new Random();
public NodeEntry getRandomNode() {
return randSource[gen.nextInt(arrSize)];
}
private void checkProbSum() {
double sum = 0;
for (NodeEntry ne: nodeDefinitions) {
sum+=ne.getProbability();
}
if (sum >1) {
throw new RuntimeException("nodes probability > 1");
}
}
public static void main(String[] args) {
NodeGen ng = new NodeGen();
ng.addNode(new NodeEntry("Test 1", 0.1));
ng.addNode(new NodeEntry("Test 2", 0.2));
ng.addNode(new NodeEntry("Test 3", 0.2));
ng.compile();
Map<NodeEntry, Integer> resCount = new HashMap<NodeEntry, Integer>();
int generations = 10000;
for (int a=0; a<generations; a++) {
NodeEntry node = ng.getRandomNode();
Integer val = resCount.get(node);
if (val == null) {
resCount.put(node, new Integer(1));
} else {
resCount.put(node, new Integer(val+1));
}
}
for (Map.Entry<NodeEntry, Integer> entry: resCount.entrySet()) {
System.out.println(entry.getKey()+": "+entry.getValue()+" ("+(100.0*entry.getValue()/generations)+"%)");
}
}
}
This makes sure the probabilities are actually uniform. If you checked for the first node spawn, then the other, then the other - you would get wrong results: nodes checked first would have increased probability.
Sample run:
Test 2(0.2): 1975 (19.75%)
Test 1(0.1): 1042 (10.42%)
Test 3(0.2): 1981 (19.81%)
default(0.0): 5002 (50.02%)
I think that it is easy to understand how it works.
(Cobalt, 20: means 1 of 20 -> 5%)
Dictionary<string, double> ore = new Dictionary<string, double>();
Random random = new Random();
private void AddOre(string Name, double Value)
{
ore.Add(Name, 1.0 / Value);
}
private string GetOreType()
{
double probSum = 0;
double rand = random.NextDouble();
foreach (var pair in ore)
{
probSum += pair.Value;
if (probSum >= rand)
return pair.Key;
}
return "Normal Ore"; //Reaches this point only if an error occurs.
}
private void Action()
{
AddOre("Cobalt", 20);
AddOre("Stone", 10);
AddOre("Iron", 100);
AddOre("GreenOre", 300);
//Add Common ore and sort Dictionary
AddOre("Common ore", 1 / (1 - ore.Values.Sum()));
ore = ore.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
Console.WriteLine(GetOreType());
}
Edit:
I add section "Add Common ore and sort Dictionary".
I recently had to do something similar, and I ended up with this generic "spawn generator".
public interface ISpawnable : ICloneable
{
int OneInThousandProbability { get; }
}
public class SpawnGenerator<T> where T : ISpawnable
{
private class SpawnableWrapper
{
readonly T spawnable;
readonly int minThreshold;
readonly int maxThreshold;
public SpawnableWrapper(T spawnable, int minThreshold)
{
this.spawnable = spawnable;
this.minThreshold = minThreshold;
this.maxThreshold = this.minThreshold + spawnable.OneInThousandProbability;
}
public T Spawnable { get { return this.spawnable; } }
public int MinThreshold { get { return this.minThreshold; } }
public int MaxThreshold { get { return this.maxThreshold; } }
}
private ICollection<SpawnableWrapper> spawnableEntities;
private Random r;
public SpawnGenerator(IEnumerable<T> objects, int seed)
{
Debug.Assert(objects != null);
r = new Random(seed);
var cumulativeProbability = 0;
spawnableEntities = new List<SpawnableWrapper>();
foreach (var o in objects)
{
var spawnable = new SpawnableWrapper(o, cumulativeProbability);
cumulativeProbability = spawnable.MaxThreshold;
spawnableEntities.Add(spawnable);
}
Debug.Assert(cumulativeProbability <= 1000);
}
//Note that it can spawn null (no spawn) if probabilities dont add up to 1000
public T Spawn()
{
var i = r.Next(0, 1000);
var retVal = (from s in this.spawnableEntities
where (s.MaxThreshold > i && s.MinThreshold <= i)
select s.Spawnable).FirstOrDefault();
return retVal != null ? (T)retVal.Clone() : retVal;
}
}
And you'd use it like:
public class Gem : ISpawnable
{
readonly string color;
readonly int oneInThousandProbability;
public Gem(string color, int oneInThousandProbability)
{
this.color = color;
this.oneInThousandProbability = oneInThousandProbability;
}
public string Color { get { return this.color; } }
public int OneInThousandProbability
{
get
{
return this.oneInThousandProbability;
}
}
public object Clone()
{
return new Gem(this.color, this.oneInThousandProbability);
}
}
var RedGem = new Gem("Red", 250);
var GreenGem = new Gem("Green", 400);
var BlueGem = new Gem("Blue", 100);
var PurpleGem = new Gem("Purple", 190);
var OrangeGem = new Gem("Orange", 50);
var YellowGem = new Gem("Yellow", 10);
var spawnGenerator = new SpawnGenerator<Gem>(new[] { RedGem, GreenGem, BlueGem, PurpleGem, OrangeGem, YellowGem }, DateTime.Now.Millisecond);
var randomGem = spawnGenerator.Spawn();
Evidently the spawn algorithm was not considered critical code so the overhead of this implementation was of no concern when compared to the ease of use. Spawns were run on world creation and it was easily more than fast enough.
Astrotrain already gave my answer but since I coded it up already I'll post it. Sorry for the syntax, I work mostly in Powershell and that is the context currently in my mind. Consider this psuedo code:
// Define the odds for each loot type
// Description,Freq,Range
LootOddsArray = "Bloodstone",1,100,
"Copper",1,15,
"Emeraldite,"1,35,
"Gold",1,50,
"Heronite",1,60,
"Platinum",1,60,
"Shadownite",1,75,
"Silver",1,35,
"Soranite",1,1000,
"Umbrarite",1,1000,
"Cobalt",1,75,
"Iron",1,15
// Define your lookup table. It should be as big as your largest odds range.
LootLookupArray(1000)
// Fill all the 'default' values with "Nothing"
for (i=0;i<LootLookupArray.length;i++) {
LootOddsArray(i) = "Nothing"
}
// Walk through your various treasures
for (i=0;i<LootOddsArray.length;i++)
// Calculate how often the item will appear in the table based on the odds
// and place that many of the item in random places in the table, not overwriting
// any other loot already in the table
NumOccsPer1000 = Round(LootOddsArray(i).Freq * 1000/LootOddsArray(i).Range)
for (l=0;l<NumOccsPer1000;l++) {
// Find an empty slot for the loot
do
LootIndex = Random(1000)
while (LootLookupArray(LootIndex) != "Nothing")
// Array(Index) is empty, put loot there
LootLookupArray(LootIndex) = LootOddsArray(i).Description
}
}
// Roll for Loot
Loot = LootLookupArray(Random(1000))
Use Random.Next http://msdn.microsoft.com/en-us/library/2dx6wyd4(v=vs.110).aspx:
Random rnd = new Random();
if (rnd.Next(1, 101) == 1)
// spawn Bloodstone
if (rnd.Next(1, 16) == 1)
// spawn Copper
if (rnd.Next(1, 36) == 1)
// spawn Emeraldite
The minimum value should always be 1, the maximum value is the odds of spawning the item + 1 (minValue is inclusive, maxValue is exclusive). Always test the return value for 1, e.g., for Bloodstone there is a 1 in 100 chance that the randomly generated number is 1. Of course, this uses a pseudo random number generator, which should be good enough for a game.
A slightly different approach to Astrotrains idea would be that instead of an array to use if statements . The upside is then that you need less memory, the downside that it will need more cpu time to calc the value of the node.
Thus:
Random rnd = new Random();
var number = rnd.next(1,1000);
if (number >= 1 && number <10)
{
// empty
}
else
{
if (number >= 10 && number <100)
{
// bloodstone
}
else
{
//......
}
}
Also a downside to this variant comapred to the array variant is that this one takes more place codewise at the location where you use it and is more prone to errors / corrections (try to add something inside it you need to update all variants).
Thus this here is metnioned for completeness sake but the array vairant (memory usage aside) is less prone to the problems that the if variant has.
Related
Foreword: Zeckendorf representation
This mathematical theorem states that for each number n, there are k addition operands all present in the Fibonacci sequence {0, 1, 1, 2, 3, 5 et al}. For example, we got 100. To find its Zeckendorf representation, we first must find the largest value smaller or equal to 100 present in the Fibonacci sequence. In this case, it's 89. 100-89 is 11. The same process repeated gives us 8, and then 3. Therefore 89+8+3 = 100. This works for all numbers. If you have a strong computer that doesn't overflow with large numbers, and if you have a programming language supervised for math that holds numbers with large values, you can calculate the Zeckendorf representation for almost any given number.
However since we're using C# that's tricky with types and how large the numbers are, we have to practice caution when dealing with Zeckendorf representation. I used long to hold my numbers, and I calculated 20th number of the Fibonacci sequence.
static void Fbonacci_Init(int roof)
{
for (int i = 1; i < roof; i++) //where roof = 20
{
Fibonacci.Add(Fibonacci[i - 1] + Fibonacci[i]);
}
}
I then created two public static lists as fields:
public static List<long> Fibonacci = new List<long>() { 0, 1 };
public static List<long> ZeckendorfRep = new List<long>();
And then created a function called FindLesser than finds the a value lesser or equal to long given argument and adds it to ZeckendorfRep:
static void FindLesser(long number)
{
for (int i = Fibonacci.Count - 1; i >= 0; i--)
{
if (Fibonacci[i] <= number)
{
ZeckendorfRep.Add(Fibonacci[i]);
break;
}
}
}
And function called Difference for recursion of both FindLesser() and itself:
static void Difference(long init)
{
FindLesser(init - ZeckendorfRep[0]);
long summation = ZeckendorfRep.Sum();
if (summation != init)
{
for (int i = 1; i < ZeckendorfRep.Count; i++)
{
long difference = ZeckendorfRep[i] - ZeckendorfRep[i - 1];
FindLesser(difference);
Difference(difference);
}
}
}
I then call each function with the argument long input which is Console.ReadLine().
Anyways, the code doesn't work properly. It only returns the first two operands of a given representation, for example, for 100, it only returns 89 and 8. What could be the problem?
I am using a generic class to convert an INT to a X base:
BaseX basex = new BaseX("abcdefghijklmnopqrstuvwxyz");
var a = basex.ToBaseX(1002);
var b = basex.FromBaseX("aghe");
And the BaseX class is as follows:
public class BaseX {
private readonly string _digits;
public BaseX(string digits) {
_digits = digits;
}
public string ToBaseX(int number) {
var output = "";
do {
output = _digits[number % _digits.Length] + output;
number = number / _digits.Length;
}
while (number > 0);
return output;
}
public int FromBaseX(string number) {
return number.Aggregate(0, (a, c) => a * _digits.Length + _digits.IndexOf(c));
}
}
I am using the lowercase base but I can use any other base.
Is it possible to make the output in the base X always the same length?
I think I should use "Multiplicative Inverse" and some similar process with mapping and encoding but I am not sure how to do this ...
Could I get some help to create this?
Basically, my objective is instead of creating random fixed lenght codes to use in promotions or in ID obfuscation just create a fixed length of an INT (The ID on the database).
Thank You,
Miguel
If I understand you correctly you want to pad the generated value with "zeroes". E.g. if you were using plain numbers and you wanted an ID of length 10 and the ID was 1234 the padded ID would be 0000001234.
The simplest way is to pad the generated value. You can add a new method to the BaseX class:
public string ToBaseX(int number, int width) {
var output = ToBaseX(number);
return output.PadLeft(width, _digits[0]);
}
With this method basex.ToBaseX(1002, 10) returns
aaaaaaabmo
and basex.FromBaseX("aaaaaaabmo") returns
1002
In the comments you indicate that the resulting string aaaaaaabmo does not seem very random. But then you can use the approach that Eric Lippert describes in the article A practical use of multiplicative inverses that you are referring to.
First you need to pick an upper limit to the numbers you want to obfuscate (and this number should fit into a 32 bit integer). Eric Lippert uses 1000000000 (1 billion). You then need to pick a number less than the limit that is coprime with the limit (e.g. they do not share any prime factors). Eric Lippert chooses 387420489 (and explains that any number that ends in 9 will be coprime with a number that is a power of 10). You then need to calculate the modular multiplicative inverse of this number, e.g. a number inverse-x that satisfies the following condition:
387420489 * inverse-x = 1 (mod 1000000000)
You can use the extended Euclidian algorithm for this calculation for instance using an online calculator. The modular multiplicative inverse is 513180409.
To obfuscate you number you can use this code (to avoid overflow it is important to perform the calculation using 64 bit integers):
var value = 1002;
var m = 1000000000L;
var x = 387420489L;
var inverseX = 513180409L;
var encoded = value*x%m;
var decoded = encoded*inverseX%m;
For this particular calculation encoded is 195329978.
If you want to use the lower case letters to represent the obfuscated number you can use your BaseX class to convert the number to base 26. You can compute the maximum letters required to represent any number below 1 billion:
Math.Log(1000000000)/Math.Log(26) = 6.36054383137796
This means that you need no more than 7 letters to represent your number.
I have combined all this into two simple methods using some constants you can easily customize:
static class Obfuscator {
const Int64 modulo = 1000000000L;
const Int64 coprime = 280619659L;
const Int64 inverseCoprime = 687208739L;
const String digits = "abcdefghijklmnopqrstuvwxyz";
const Int32 maxDigits = 7; // Math.Log(modulo)/Math.Log(digits.Length) rounded up.
public static String Obfuscate(Int32 originalValue) {
if (originalValue >= modulo || originalValue < 0)
throw new ArgumentOutOfRangeException();
var value = (Int32) (originalValue*coprime%modulo);
var buffer = new Char[maxDigits];
var i = maxDigits;
do {
buffer[--i] = digits[value%digits.Length];
value /= digits.Length;
} while (value > 0);
while (i > 0)
buffer[--i] = digits[0];
return new String(buffer);
}
public static Int32 Deobfuscate(String obfuscatedValue) {
if (String.IsNullOrEmpty(obfuscatedValue))
throw new ArgumentException();
var value = obfuscatedValue
.Aggregate(0, (a, c) => a*digits.Length + digits.IndexOf(c));
return (Int32) (value*inverseCoprime%modulo);
}
}
Only detail to be aware of is that 0 is obfuscated into aaaaaaa. For any number between 1 and 999999999 (inclusive) you get what looks like a random string of 7 characters.
I have this code that finds numbers in a given range that contain only 3 and 5 and are polynoms(symetrical, 3553 for example). The problem is that the numbers are between 1 and 10^18, so there are cases in which I have to work with big numbers, and using BigInteger makes the program way too slow, so is there a way to fix this ? Here's my code:
namespace Lucky_numbers
{
class Program
{
static void Main(string[] args)
{
string startString = Console.ReadLine();
string finishString = Console.ReadLine();
BigInteger start = BigInteger.Parse(startString);
BigInteger finish = BigInteger.Parse(finishString);
int numbersFound = 0;
for (BigInteger i = start; i <= finish; i++)
{
if (Lucky(i.ToString()))
{
if (Polyndrome(i.ToString()))
{
numbersFound++;
}
}
}
}
static bool Lucky(string number)
{
if (number.Contains("1") || number.Contains("2") || number.Contains("4") || number.Contains("6") || number.Contains("7") || number.Contains("8") || number.Contains("9") || number.Contains("0"))
{
return false;
}
else
{
return true;
}
}
static bool Polyndrome(string number)
{
bool symetrical = true;
int middle = number.Length / 2;
int rightIndex = number.Length - 1;
for (int leftIndex = 0; leftIndex <= middle; leftIndex++)
{
if (number[leftIndex] != number[rightIndex])
{
symetrical = false;
break;
}
rightIndex--;
}
return symetrical;
}
}
}
Edit: Turns out it's not BigInteger, it's my shitty implementation.
You could use ulong:
Size: Unsigned 64-bit integer
Range: 0 to 18,446,744,073,709,551,615
But I would guess that BigInteger is not a problem here. I think you should create algorithm for palindrome creation instead of brute-force increment+check solution.
Bonus
Here is a palyndrome generator I wrote in 5 minutes. I think it will be much faster than your approach. Could you test it and tell how much faster it is? I'm curious about that.
public class PalyndromeGenerator
{
private List<string> _results;
private bool _isGenerated;
private int _length;
private char[] _characters;
private int _middle;
private char[] _currentItem;
public PalyndromeGenerator(int length, params char[] characters)
{
if (length <= 0)
throw new ArgumentException("length");
if (characters == null)
throw new ArgumentNullException("characters");
if (characters.Length == 0)
throw new ArgumentException("characters");
_length = length;
_characters = characters;
}
public List<string> Results
{
get
{
if (!_isGenerated)
throw new InvalidOperationException();
return _results.ToList();
}
}
public void Generate()
{
_middle = (int)Math.Ceiling(_length / 2.0) - 1;
_results = new List<string>((int)Math.Pow(_characters.Length, _middle + 1));
_currentItem = new char[_length];
GeneratePosition(0);
_isGenerated = true;
}
private void GeneratePosition(int position)
{
if(position == _middle)
{
for (int i = 0; i < _characters.Length; i++)
{
_currentItem[position] = _characters[i];
_currentItem[_length - position - 1] = _characters[i];
_results.Add(new string(_currentItem));
}
}
else
{
for(int i = 0; i < _characters.Length; i++)
{
_currentItem[position] = _characters[i];
_currentItem[_length - position - 1] = _characters[i];
GeneratePosition(position + 1);
}
}
}
}
Usage:
var generator = new PalyndromeGenerator(6, '3', '5');
generator.Generate();
var items = generator.Results.Select(x => ulong.Parse(x)).ToList();
Strange riddle, but can be simplified if I understand the requirement.
I would first map these numbers to binary as there is only two possible
"lucky" digits, then generate the numbers by counting in binary until
I have completed nine bits. Reflect it for the full number, then
convert 0 to 3 and 1 to 5.
Example 1101
Reflect it = 10111101 --> 53555535
Do this from 0 all the way to 111111111
Declare start and finish to be static inside the class.
Change the method Lucky to:
static bool Lucky(string number)
{
return !(number.Contains("1") || number.Contains("2") || number.Contains("4") || number.Contains("6") || number.Contains("7") || number.Contains("8") || number.Contains("9") || number.Contains("0"));
}
Also, you can use Parallel library to parallelize the computation.
Instead of using a regular for loop, you could use a Parallel.For.
Look at the problem a different way - how many strings of up to 9 characters (using only '3' and '5') can you make? for each string you have 2 palindromes (one repeating the last character, one not) that you can make.
e.g.
3 -> 33
5 ->, 55
33 -> 333, 3333
35 -> 353, 3553
53 -> 535, 5335
...
The only suggestion I have is to use a 3rd party library like intx, or some unmanaged code. The intx author reports that it can work faster than BigInteger in some situations: "System.Numerics.BigInteger class was introduced in .NET 4.0 so I was interested in performance of this solution. I did some tests (grab test code from GitHub) and it appears that BigInteger has performance in general comparable with IntX on standard operations but starts losing when FHT comes into play (when multiplying really big integers, for example)."
Since the number has to be symmetrical, you only need to check the first half of the number. You don't need to check 18 digits, you only have to check to 9 digits and then swap the order of the characters and add them to the back as a string.
One thing I can think of is if you are only going to count integers that are containing 3 or 5 you don't need to traverse the entire list of numbers between your beginning & ending range.
Instead, look at your character set as either '3' or '5'. Then you can simply go through the allowed permutations of half of the number itself, leaving the other half to be completed to successfully create a polyndrome.
There are some rules to this method which would help, such as :
if the starting number's left-most digit was greater than 5 there is no need to attempt for that specific number of digits.
if both numbers fall on the same amount of digits but left-most digits do not traverse / include 5 or 3, no need to process.
Developing some set of rules such as this may help other than attempting to check every possible permutation.
So, for example, your Lucky function would become something more along the lines of :
static bool Lucky(string number)
{
if((number[0] != '3') && (number[0] != '5'))
{
return false;
} //and you could continue this for the entire string
...
}
I have a coding/maths problem that I need help translating into C#. It's a poker chip calculator that takes in the BuyIn, the number of players and the total amount of chips for each colour (there are x amount of colours) and their value.
It then shows you every possible combination of chips per person to equal the Buy In. The user can then pick the chipset distribution they would like to use. It's best illustrated with a simple example.
BuyIn: $10
Number of Players: 1
10 Red Chips, $1 value
10 Blue Chips, $2 value
10 Green Chips, $5 value
So, the possible combinations are:
R/B/G
10/0/0
8/1/0
6/2/0
5/0/1
4/3/0
2/4/0
1/2/1
etc.
I have spent a lot of time trying to come up with an algorithm in C#/.NET to work this out. I am stumbling on the variable factor - there's usually only 3 or 4 different chips colours in a set, but there could be any amount. If you have more than one player than you have to count up until TotalChips / NumberOfPlayers.
I started off with a loop through all the chips and then looping from 0 up to NumberOfChips for that colour. And this is pretty much where I have spent the last 4 hours... how do I write the code to loop through x amount of chips and check the value of the sum of the chips and add it to a collection if it equals the BuyIn? I need to change my approach radically methinks...
Can anyone put me on the right track on how to solve this please? Pseudo code would work - thank you for any advice!
The below is my attempt so far - it's hopeless (and wont compile, just an example to show you my thought process so far) - Might be better not to look at it as it might biased you on a solution...
private void SplitChips(List<ChipSuggestion> suggestions)
{
decimal valueRequired = (decimal)txtBuyIn.Value;
decimal checkTotal = 0;
ChipSuggestion suggestion;
//loop through each colour
foreach (Chip chip in (PagedCollectionView)gridChips.ItemsSource)
{
//for each value, loop through them all again
foreach (Chip currentChip in (PagedCollectionView)gridChips.ItemsSource)
{
//start at 0 and go all the way up
for (int i = 0; i < chip.TotalChipsInChipset; i++)
{
checkTotal = currentChip.ChipValue * i;
//if it is greater than than ignore and stop
if (checkTotal > valueRequired)
{
break;
}
else
{
//if it is equal to then this is a match
if (checkTotal == valueRequired)
{
suggestion = new ChipSuggestion();
suggestion.SuggestionName = "Suggestion";
chipRed.NumberPerPlayer = i;
suggestion.Chips.Add(chipRed);
chipBlue.NumberPerPlayer = y;
suggestion.Chips.Add(chipBlue);
chipGreen.NumberPerPlayer = 0;
suggestion.Chips.Add(chipGreen);
//add this to the Suggestion
suggestions.Add(suggestion);
break;
}
}
}
}
}
}
Here's an implementation that reads the number of chips, the chips (their worth and amount) and the buyin and displays the results in your example format. I have explained it through comments, let me know if you have any questions.
class Test
{
static int buyIn;
static int numChips;
static List<int> chips = new List<int>(); // chips[i] = value of chips of color i
static List<int> amountOfChips = new List<int>(); // amountOfChips[i] = number of chips of color i
static void generateSolutions(int sum, int[] solutions, int last)
{
if (sum > buyIn) // our sum is too big, return
return;
if (sum == buyIn) // our sum is just right, print the solution
{
for (int i = 0; i < chips.Count; ++i)
Console.Write("{0}/", solutions[i]);
Console.WriteLine();
return; // and return
}
for (int i = last; i < chips.Count; ++i) // try adding another chip with the same value as the one added at the last step.
// this ensures that no duplicate solutions will be generated, since we impose an order of generation
if (amountOfChips[i] != 0)
{
--amountOfChips[i]; // decrease the amount of chips
++solutions[i]; // increase the number of times chip i has been used
generateSolutions(sum + chips[i], solutions, i); // recursive call
++amountOfChips[i]; // (one of) chip i is no longer used
--solutions[i]; // so it's no longer part of the solution either
}
}
static void Main()
{
Console.WriteLine("Enter the buyin:");
buyIn = int.Parse(Console.ReadLine());
Console.WriteLine("Enter the number of chips types:");
numChips = int.Parse(Console.ReadLine());
Console.WriteLine("Enter {0} chips values:", numChips);
for (int i = 0; i < numChips; ++i)
chips.Add(int.Parse(Console.ReadLine()));
Console.WriteLine("Enter {0} chips amounts:", numChips);
for (int i = 0; i < numChips; ++i)
amountOfChips.Add(int.Parse(Console.ReadLine()));
int[] solutions = new int[numChips];
generateSolutions(0, solutions, 0);
}
}
Enter the buyin:
10
Enter the number of chips types:
3
Enter 3 chips values:
1
2
5
Enter 3 chips amounts:
10
10
10
10/0/0/
8/1/0/
6/2/0/
5/0/1/
4/3/0/
3/1/1/
2/4/0/
1/2/1/
0/5/0/
0/0/2/
Break the problem down recursively by the number of kinds of chips.
For the base case, how many ways are there to make an $X buy-in with zero chips? If X is zero, there is one way: no chips. If X is more than zero, there are no ways to do it.
Now we need to solve the problem for N kinds of chips, given the solution for N - 1. We can take one kind of chip, and consider every possible number of that chip up to the buy-in. For example, if the chip is $2, and the buy-in is $5, try using 0, 1, or 2 of them. For each of these tries, we have to use only the remaining N - 1 chips to make up the remaining value. We can solve that by doing a recursive call, and then adding our current chip to each solution it returns.
private static IEnumerable<IEnumerable<Tuple<Chip, int>>> GetAllChipSuggestions(List<Chip> chips, int players, int totalValue)
{
return GetAllChipSuggestions(chips, players, totalValue, 0);
}
private static IEnumerable<IEnumerable<Tuple<Chip, int>>> GetAllChipSuggestions(List<Chip> chips, int players, int totalValue, int firstChipIndex)
{
if (firstChipIndex == chips.Count)
{
// Base case: we have no chip types remaining
if (totalValue == 0)
{
// One way to make 0 with no chip types
return new[] { Enumerable.Empty<Tuple<Chip, int>>() };
}
else
{
// No ways to make more than 0 with no chip types
return Enumerable.Empty<IEnumerable<Tuple<Chip, int>>>();
}
}
else
{
// Recursive case: try each possible number of this chip type
var allSuggestions = new List<IEnumerable<Tuple<Chip, int>>>();
var currentChip = chips[firstChipIndex];
var maxChips = Math.Min(currentChip.TotalChipsInChipset / players, totalValue / currentChip.ChipValue);
for (var chipCount = 0; chipCount <= maxChips; chipCount++)
{
var currentChipSuggestion = new[] { Tuple.Create(currentChip, chipCount) };
var remainingValue = totalValue - currentChip.ChipValue * chipCount;
// Get all combinations of chips after this one that make up the rest of the value
foreach (var suggestion in GetAllChipSuggestions(chips, players, remainingValue, firstChipIndex + 1))
{
allSuggestions.Add(suggestion.Concat(currentChipSuggestion));
}
}
return allSuggestions;
}
}
For some large combinations this is propably not solvable in finite time.
(It is a NP problem)
http://en.wikipedia.org/wiki/Knapsack_problem
There are also links with Code? that could help you.
Hope this helps a bit.
I coded up a program in C# to find perfect numbers within a certain range as part of a programming challenge . However, I realized it is very slow when calculating perfect numbers upwards of 10000. Are there any methods of optimization that exist for finding perfect numbers? My code is as follows:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleTest
{
class Program
{
public static List<int> FindDivisors(int inputNo)
{
List<int> Divisors = new List<int>();
for (int i = 1; i<inputNo; i++)
{
if (inputNo%i==0)
Divisors.Add(i);
}
return Divisors;
}
public static void Main(string[] args)
{
const int limit = 100000;
List<int> PerfectNumbers = new List<int>();
List<int> Divisors=new List<int>();
for (int i=1; i<limit; i++)
{
Divisors = FindDivisors(i);
if (i==Divisors.Sum())
PerfectNumbers.Add(i);
}
Console.Write("Output =");
for (int i=0; i<PerfectNumbers.Count; i++)
{
Console.Write(" {0} ",PerfectNumbers[i]);
}
Console.Write("\n\n\nPress any key to continue . . . ");
Console.ReadKey(true);
}
}
}
Use the formula
testPerfect = 2n-1(2n - 1)
to generate possiblities then check wether the number is in fact perfect.
try this for some bedtime reading
Do perfect numbers change? No. Look here. Surely, they should be calculated once and then stored.
In your case, the only results will be
6
28
496
8128
The next one is 33550336. Outside your range.
Just the obvious one from me: you don't need to check every divisor. No point looking for divisors past inputNo/2. That cuts down half of the calculations, but this is not an order of magnitude faster.
One way to solve things like this involves building a huge array in memory of every number, and then crossing numbers out.
if your still looking for something to calculate perfect numbers.
this goes through the first ten thousand pretty quick, but the 33 million number is a little slower.
public class Perfect {
private static Perfect INSTANCE = new Perfect();
public static Perfect getInstance() {
return INSTANCE;
}
/**
* the method that determines if a number is perfect;
*
* #param n
* #return
*/
public boolean isPerfect(long n) {
long i = 0;
long value = 0;
while(++i<n){
value = (0 == n%i?value+i:value);
}
return n==value;
}
}
For anyone interested in a LINQ based approach, the following method worked quite well and efficiently for me in determining whether or not a caller supplied integer value is a perfect number.
bool IsPerfectNumber(int value)
{
var isPerfect = false;
int maxCheck = Convert.ToInt32(Math.Sqrt(value));
int[] possibleDivisors = Enumerable.Range(1, maxCheck).ToArray();
int[] properDivisors = possibleDivisors.Where(d => (value % d == 0)).Select(d => d).ToArray();
int divisorsSum = properDivisors.Sum();
if (IsPrime(divisorsSum))
{
int lastDivisor = properDivisors.Last();
isPerfect = (value == (lastDivisor * divisorsSum));
}
return isPerfect;
}
For simplicity and clarity, my implementation for IsPrime(), which is used within IsPerfectNumber(), is omitted.
To continue from Charles Gargent's answer there is a very quick way to check if a Mersenne Number a.k.a. 2^n - 1 is prime. It is called the Lucas-Lehmer test
The basic pseudocode though (taken from the Wikipedia page) is:
// Determine if Mp = 2p − 1 is prime for p > 2
Lucas–Lehmer(p)
var s = 4
var M = 2p − 1
repeat p − 2 times:
s = ((s × s) − 2) mod M
if s == 0 return PRIME else return COMPOSITE