Resilient Propagation (RProp) training slowly - c#

I am creating a library for Neural Networks; I have successfully implemented the Back Propagation algorithm, but I'm having trouble with Resilient Propagation.
I have been using the XOR scenario to test my implementation; the error seems to decrease after a few epochs, then it increases and eventually stops changing.
The network I am testing on has 3 layers - 2 input, 2 hidden and 1 output neuron/s. The synapse/dendrite weights are on the following layer's neurons, for example, the input-hidden weights would be stored on the hidden neurons and the hidden-output weights would be stored on the output neurons.
This is my gradient calculation (EDIT 4):
private void CalculateGradient()
{
for (int t = 0; t < TrainingInputs.Count; ++t) //loop through training data
{
Network.Run(TrainingInputs[t]);
for (int l = Network.Layers.Count - 1; l > 0; l--)
{
for (int i = 0; i < Network.Layers[l].Neurons.Count; i++)
{
Network.Layers[l].Neurons[i].Delta = l < Network.Layers.Count - 1
? CalculateNonLastGradient(l + 1, i, Network.Layers[l].Neurons[i].Value)
: CalculateLastGradient(TrainingOutputs[t][i], Network.Layers[l].Neurons[i].Value);
}
}
for (int l = Network.Layers.Count - 1; l > 0; l--)
{
for (int j = 0; j < Network.Layers[l].Neurons.Count; ++j)
{
double grad = Network.Layers[l].Neurons[j].Delta;
biasGrads[l][j] += grad;
for (int i = 0; i < Network.Layers[l - 1].Neurons.Count; ++i)
{
grad = Network.Layers[l].Neurons[j].Delta * Network.Layers[l - 1].Neurons[i].Value;
grads[l][j][i] += grad;
}
}
}
int o = 0;
Layer layer = Network.Layers[Network.Layers.Count - 1];
errors.Add(layer.Neurons.Sum(n => Math.Abs(TrainingOutputs[t][o++] - n.Value)));
}
Error = errors.Average();
}
private double CalculateLastGradient(double ideal, double nValue)
{
return Network.Activation.Derivitive(nValue) * (ideal - nValue);
}
private double CalculateNonLastGradient(int nextLayer, int j, double nValue)
{
double sum = 0.0;
for (int i = 0; i < Network.Layers[nextLayer].Neurons.Count; i++)
{
sum += Network.Layers[nextLayer].Neurons[i].Delta * Network.Layers[nextLayer].Neurons[i].Dendrites[j].Weight;
}
return Network.Activation.Derivitive(nValue) * sum;
}
My RProp implementation (EDIT 4):
public bool Algorithm()
{
//initialise matrices
if (!initializedMatrices)
{
InitializeMatrices();
}
ZeroOut();
//calculate gradients
CalculateGradient();
for (int l = 1; l < Network.Layers.Count; l++) //layers
{
for (int i = 0; i < Network.Layers[l - 1].Neurons.Count; ++i) //prev layer neurons
{
for (int j = 0; j < Network.Layers[l].Neurons.Count; ++j) //current layer neurons
{
double delta = prevDeltas[l][j][i];
int change = Math.Sign(prevGrads[l][j][i] * grads[l][j][i]);
if (change > 0)
{
delta = Math.Min(delta * etaPlus, deltaMax);
double deltaWeight = -Math.Sign(grads[l][j][i]) * delta;
Network.Layers[l].Neurons[j].Dendrites[i].Weight += deltaWeight;
}
else if (change < 0)
{
delta = Math.Max(delta * etaMinus, deltaMin);
Network.Layers[l].Neurons[j].Dendrites[i].Weight -= prevDeltas[l][j][i];
prevGrads[l][j][i] = 0;
}
else if (change == 0)
{
double deltaWeight = -Math.Sign(grads[l][j][i]) * delta;
Network.Layers[l].Neurons[j].Dendrites[i].Weight += deltaWeight;
}
prevGrads[l][j][i] = grads[l][j][i];
prevDeltas[l][j][i] = delta;
} //j
} //i
for (int i = 1; i < Network.Layers[l].Neurons.Count; ++i)
{
double delta = prevBiasDeltas[l][i];
int change = Math.Sign(prevBiasGrads[l][i] * biasGrads[l][i]);
if (change > 0)
{
delta = Math.Min(prevBiasDeltas[l][i] * etaPlus, deltaMax);
double biasDeltaWeight = -Math.Sign(biasGrads[l][i]) * delta;
Network.Layers[l].Neurons[i].Bias += biasDeltaWeight;
}
else if (change < 0)
{
delta = Math.Max(prevBiasDeltas[l][i] * etaMinus, deltaMin);
Network.Layers[l].Neurons[i].Bias -= prevBiasDeltas[l][i];
prevBiasGrads[l][i] = 0;
}
else if (change == 0)
{
double biasDeltaWeight = -Math.Sign(biasGrads[l][i]) * delta;
Network.Layers[l].Neurons[i].Bias += biasDeltaWeight;
}
prevBiasGrads[l][i] = biasGrads[l][i];
prevBiasDeltas[l][i] = delta;
}
}
return true;
}
Could anyone point me as to what could be going wrong?
EDIT:
The issue seems to be that the delta is not changing.
EDIT 2:
I have fixed the delta not changing by initializing the first previous deltas to 0.01 and Zero'ing the gradients before each call. The error increases very fast, then decreases very slowly with TANH; with Sigmoid it does the opposite.
EDIT 3:
The loop for the bias' was starting at 0, when it should have been 1. Fixing this fixed the original issue, but has brought up at new one - the error stops decreasing after a certain point.
EDIT 4: I realised that I had not accumulated the gradients for each neuron over the training set. The error now decreases, but very slowly.

Related

XOR Neural network always goes to 0.5

I've made a neural network in C# from scratch.
The problem is that when I try to train it for an XOR logic gate, it always converges to 0.5, instead of alternating between 0 and 1.
The neural network is made to work with a custumizable number of layers and nodes.
I've been over the theory countless of times, but I have no idea where the problem could be. I have excluded the bias in the backpropagation.
The backpropagation algorithm:
Neuralnetwork is constructed as follows:
nn.layerlist[x] = specific layer
nn.layerlist[x].network[y] = specific node
nn.layerlist[x].network[y].weight[z] = specific weight leading towards that node
public static NeuralNetwork Calculate(NeuralNetwork nn, List<double> Target, double LearningRate)
{
var OutputLayer = nn.LayerList.Count - 1;
var node = nn.LayerList[OutputLayer].Network[0];
List<List<double>> WeightList = new List<List<double>>();
List<double> Weights = new List<double>();
//Weightlist initialiseren
for (int i = 0; i < node.Weights.Count; i++)
Weights.Add(0);
// Door de laatste layer eerst gaan
for (int i = 0; i < Target.Count(); i++)
{
for (int j = 0; j < node.Weights.Count; j++)
{
var weight = nn.LayerList[OutputLayer].Network[i].Weights[j];
var outputcurrent = nn.LayerList[OutputLayer].Network[i].Value;
var OutputPrevious = nn.LayerList[OutputLayer - 1].Network[j].Value;
var Cost = (outputcurrent - Target[i]) * (outputcurrent * (1 - outputcurrent));
Weights[j] += Cost * weight;
weight = weight - (Cost * OutputPrevious * LearningRate);
nn.LayerList[OutputLayer].Network[i].Weights[j] = weight;
}
}
WeightList.Add(Weights);
int layercount = nn.LayerList.Count - 1;
//alle layers in reverse bijlangs gaan
for (int i = layercount; i > 0; i--)
{
var WeightsHidden = new List<double>();
for(int k = 0; k < 1000; k++) //TODO: Change this to something dynamic!
WeightsHidden.Add(0);
for (int j = 0; j < nn.LayerList[i].Network.Count; j++)
{
for (int k = 0; k < nn.LayerList[i].Network[j].Weights.Count; k++)
{
var weight = nn.LayerList[i].Network[j].Weights[k];
var outputcurrent = nn.LayerList[i].Network[j].Value;
var OutputPrevious = nn.LayerList[i - 1].Network[k].Value;
Console.WriteLine("Total Weights: {0}", WeightList[WeightList.Count - 1][k]);
var Cost = (outputcurrent * (1 - outputcurrent)) * WeightList[WeightList.Count - 1][k];
WeightsHidden[k] += Cost * weight;
weight = weight - (Cost * OutputPrevious * LearningRate);
nn.LayerList[i].Network[j].Weights[k] = weight;
}
}
WeightList.Add(WeightsHidden);
}
return nn;
}
}
The code is quite complex, but I have no idea how to format it in any other way.
I would like to neural network to put out the desired result like an XOR gate.

angle detection in fingerprint image with c#

I have project about fingerprint classification with c# and in one part compute angle.
in this part my project give no answer and hang and dont have any action .
to compute angle first compute sobelx and sobely filter and then compute angle with arctan
please help me
// calcute angle***************
public void computeAngle()
{
sobelx();
sobely();
angleIMG = new double[imgsx.Width, imgsx.Height];
try
{
for(int i=10 ; i<=200 ; i++)
{
for (int j = 10; j <= 150; j++)
{
if (Oy(i, j) != 0.0)
{
angleIMG[i, j] = Math.Atan(Ox(i, j) / Oy(i, j)) / 2 + Math.PI / 2;
textBox1.Text += Math.Abs(angleIMG[i, j]).ToString();
}
else
{
angleIMG[i, j] = 0.0;
}
}
textBox1.Text = "\n";
}
}
catch (Exception e1)
{
MessageBox.Show(e1.Message);
}
//get Ox(i,j)****************
public double Ox(int i, int j)
{
double wx=0.0;
for (int u = i - 1; u <= i + 1; u++)
{
for (int v = i - 1; v <= i + 1; v++)
{
Color cw = imgsx.GetPixel(u, v);
Color cw1 = imgsy.GetPixel(u, v);
wx =wx+ 2*( cw.R * cw1.R);
}
}
return wx;
}
//get Oy(i,j)****************
public double Oy(int i, int j)
{
double wy = 0.0;
for (int u = i - 1; u <= i + 1; u++)
{
for (int v = i - 1; v <= i + 1; v++)
{
Color cw=imgsx.GetPixel(u, v);
Color cw1=imgsy.GetPixel(u, v);
wy =wy+ Math.Pow (cw.R,2 )- Math.Pow(cw1.R,2);
}
}
return wy;
}
Because Red color can be up to 255, your Ox method can return values up to 1,170,450, and Oy is similar, is this what you intended?
An angle stored as a double will have up to 16 significant figures, and you are trying to concatenate 26,600 of them, this will take a while! To increase performance, I suggest you round off the angles, for instance to 2 decimal places, and then append them to a StringBuilder, which you can convert back to a string before putting it in your text box. Do something like
sb.Append(angleIMG[i, j].ToString("F2")).

Neural network [ocr]

I come looking for general tips about the program I'm writing now.
The goal is:
Use neural network program to recognize 3 letters [D,O,M] (or display "nothing is recognized" if i input anything other than those 3).
Here's what I have so far:
A class for my single neuron
public class neuron
{
double[] weights;
public neuron()
{
weights = null;
}
public neuron(int size)
{
weights = new double[size + 1];
Random r = new Random();
for (int i = 0; i <= size; i++)
{
weights[i] = r.NextDouble() / 5 - 0.1;
}
}
public double output(double[] wej)
{
double s = 0.0;
for (int i = 0; i < weights.Length; i++) s += weights[i] * wej[i];
s = 1 / (1 + Math.Exp(s));
return s;
}
}
A class for a layer:
public class layer
{
neuron[] tab;
public layer()
{
tab = null;
}
public layer(int numNeurons, int numInputs)
{
tab = new neuron[numNeurons];
for (int i = 0; i < numNeurons; i++)
{
tab[i] = new neuron(numInputs);
}
}
public double[] compute(double[] wejscia)
{
double[] output = new double[tab.Length + 1];
output[0] = 1;
for (int i = 1; i <= tab.Length; i++)
{
output[i] = tab[i - 1].output(wejscia);
}
return output;
}
}
And finally a class for a network
public class network
{
layer[] layers = null;
public network(int numLayers, int numInputs, int[] npl)
{
layers = new layer[numLayers];
for (int i = 0; i < numLayers; i++)
{
layers[i] = new layer(npl[i], (i == 0) ? numInputs : (npl[i - 1]));
}
}
double[] compute(double[] inputs)
{
double[] output = layers[0].compute(inputs);
for (int i = 1; i < layers.Length; i++)
{
output = layers[i].compute(output);
}
return output;
}
}
Now for the algorythm I chose:
I have a picture box, size 200x200, where you can draw a letter (or read one from jpg file).
I then convert it to my first array(get the whole picture) and 2nd one(cut the non relevant background around it) like so:
Bitmap bmp2 = new Bitmap(this.pictureBox1.Image);
int[,] binaryfrom = new int[bmp2.Width, bmp2.Height];
int minrow=0, maxrow=0, mincol=0, maxcol=0;
for (int i = 0; i < bmp2.Height; i++)
{
for (int j = 0; j < bmp2.Width; j++)
{
if (bmp2.GetPixel(j, i).R == 0)
{
binaryfrom[i, j] = 1;
if (minrow == 0) minrow = i;
if (maxrow < i) maxrow = i;
if (mincol == 0) mincol = j;
else if (mincol > j) mincol = j;
if (maxcol < j) maxcol = j;
}
else
{
binaryfrom[i, j] = 0;
}
}
}
int[,] boundaries = new int[binaryfrom.GetLength(0)-minrow-(binaryfrom.GetLength(0)-(maxrow+1)),binaryfrom.GetLength(1)-mincol-(binaryfrom.GetLength(1)-(maxcol+1))];
for(int i = 0; i < boundaries.GetLength(0); i++)
{
for(int j = 0; j < boundaries.GetLength(1); j++)
{
boundaries[i, j] = binaryfrom[i + minrow, j + mincol];
}
}
And convert it to my final array of 12x8 like so (i know I could shorten this a fair bit, but wanted to have every step in different loop so I can see what went wrong easier[if anything did]):
int[,] finalnet = new int[12, 8];
int k = 1;
int l = 1;
for (int i = 0; i < finalnet.GetLength(0); i++)
{
for (int j = 0; j < finalnet.GetLength(1); j++)
{
finalnet[i, j] = 0;
}
}
while (k <= finalnet.GetLength(0))
{
while (l <= finalnet.GetLength(1))
{
for (int i = (int)(boundaries.GetLength(0) / finalnet.GetLength(0)) * (k - 1); i < (int)(boundaries.GetLength(0) / finalnet.GetLength(0)) * k; i++)
{
for (int j = (int)(boundaries.GetLength(1) / finalnet.GetLength(1)) * (l - 1); j < (int)(boundaries.GetLength(1) / finalnet.GetLength(1)) * l; j++)
{
if (boundaries[i, j] == 1) finalnet[k-1, l-1] = 1;
}
}
l++;
}
l = 1;
k++;
}
int a = boundaries.GetLength(0);
int b = finalnet.GetLength(1);
if((a%b) != 0){
k = 1;
while (k <= finalnet.GetLength(1))
{
for (int i = (int)(boundaries.GetLength(0) / finalnet.GetLength(0)) * finalnet.GetLength(0); i < boundaries.GetLength(0); i++)
{
for (int j = (int)(boundaries.GetLength(1) / finalnet.GetLength(1)) * (k - 1); j < (int)(boundaries.GetLength(1) / finalnet.GetLength(1)) * k; j++)
{
if (boundaries[i, j] == 1) finalnet[finalnet.GetLength(0) - 1, k - 1] = 1;
}
}
k++;
}
}
if (boundaries.GetLength(1) % finalnet.GetLength(1) != 0)
{
k = 1;
while (k <= finalnet.GetLength(0))
{
for (int i = (int)(boundaries.GetLength(0) / finalnet.GetLength(0)) * (k - 1); i < (int)(boundaries.GetLength(0) / finalnet.GetLength(0)) * k; i++)
{
for (int j = (int)(boundaries.GetLength(1) / finalnet.GetLength(1)) * finalnet.GetLength(1); j < boundaries.GetLength(1); j++)
{
if (boundaries[i, j] == 1) finalnet[k - 1, finalnet.GetLength(1) - 1] = 1;
}
}
k++;
}
for (int i = (int)(boundaries.GetLength(0) / finalnet.GetLength(0)) * finalnet.GetLength(0); i < boundaries.GetLength(0); i++)
{
for (int j = (int)(boundaries.GetLength(1) / finalnet.GetLength(1)) * finalnet.GetLength(1); j < boundaries.GetLength(1); j++)
{
if (boundaries[i, j] == 1) finalnet[finalnet.GetLength(0) - 1, finalnet.GetLength(1) - 1] = 1;
}
}
}
The result is a 12x8 (I can change it in the code to get it from some form controls) array of 0 and 1, where 1 form the rough shape of a letter you drawn.
Now my questions are:
Is this a correct algorythm?
Is my function
1/(1+Math.Exp(x))
good one to use here?
What should be the topology? 2 or 3 layers, and if 3, how many neurons in hidden layer? I have 96 inputs (every field of the finalnet array), so should I also take 96 neurons in the first layer? Should I have 3 neurons in the final layer or 4(to take into account the "not recognized" case), or is it not necessary?
Thank you for your help.
EDIT: Oh, and I forgot to add, I'm gonna train my network using Backpropagation algorythm.
You may need 4 layers at least to get accurate results using back propagation method. 1 input, 2 middle layers, and an output layer.
12 * 8 matrix is too small(and you may end up in data loss which will result in total failure) - try some thing 16 * 16. If you want to reduce the size then you have to peel out the outer layers of black pixels further.
Think about training the network with your reference characters.
Remember that you have to feed back the output back to the input layer again and iterate it multiple times.
A while back I created a neural net to recognize digits 0-9 (python, sorry), so based on my (short) experience, 3 layers are ok and 96/50/3 topology will probably do a good job. As for the output layer, it's your choice; you can either backpropagate all 0s when the input image is not a D, O or M or use the fourth output neuron to indicate that the letter was not recognized. I think that the first option would be the best one because it's simpler (shorter training time, less problems debugging the net...), you just need to apply a threshold under which you classify the image as 'not recognized'.
I also used the sigmoid as activation function, I didn't try others but it worked :)

Simple DFT Lowpass

I'm having some trouble making a simple lowpass filter with a DFT. In the end, I hope to be able to pitch-shift audio in real time, but as it stands I can't even get this right. I have no training in this area, I only know that FFTs change waves to frequencies and iFFTs do that back, and a couple of other things I've read. To bo honest I'm surprised it works as well as it does so far. Anyway here's the code:
byte[] samples = new byte[20000000];
int spos = 0;
samples is filled here with 8Bit Unsigned PCM. spos <- number of samples
int samplesize = 128;
int sampleCount = spos / samplesize;
frequencies = new System.Numerics.Complex[sampleCount][];
for (int i = 0; i < sampleCount; i++)
{
Console.WriteLine("Sample " + i + " / " + sampleCount);
frequencies[i] = new System.Numerics.Complex[samplesize];
for (int j = 0; j < samplesize; j++)
{
frequencies[i][j] = (float)(samples[i * samplesize + j] - 128) / 128.0f;
}
dft.Radix2Forward(frequencies[i], MathNet.Numerics.IntegralTransforms.FourierOptions.Default);
}
int shiftUp = 1000; //1khz
int fade = 2; //8 sample fade.
int kick = frequencies[0].Length * shiftUp / rate;
So now I've calculated a bunch of DFTs for 128 sample portions of the input. kick is (I hope) the number of samples in the DFT that span 1000Hz. I.E since frequencies.Length / 2 contains frequency amplitude data up to rate/2 Hz, then frequencies[0].Length / 2 * shiftUp / (rate / 2) = frequencies[0].Length * shiftUp / rate should give me the right value
for (int i = 0; i < sampleCount; i++)
{
This is the part I have trouble with. Without it, the output sounds great! This skips both index 0 and index 64. Both of these have a complex component of 0, and I recall reading somewhere that the value at index 0 was important...
for (int j = 0; j < frequencies[i].Length; j++)
{
if (j == 0 || j == 64)
continue;
if (j < 64)
{
if (!(j < kick + 1))
{
frequencies[i][j] = 0;
}
}
else
{
if (!(j - 64 > 63 - kick))
{
frequencies[i][j] = 0;
}
}
}
Finally it undoes the transform
dft.Radix2Inverse(frequencies[i], MathNet.Numerics.IntegralTransforms.FourierOptions.Default);
...tosses it back in the samples array
for (int j=0; j<samplesize; j++)
samples[i * samplesize + j] = (byte)(frequencies[i][j].Real * 128.0f + 128.0f);
}
...chucks it into a file
BinaryWriter bw = new BinaryWriter(File.OpenWrite("sound"));
for (int i = 0; i < spos; i++)
{
bw.Write(samples[i]);
}
bw.Close();
...then I import it into Audacity to murder my ears with artifacts.
The spectral display shows that the code works, to an extent
However there's these annoying highpitched crackling sounds that occur throughout the entire song. I've heard something about the Gibbs phenomenon and a window function, but I don't really know how to apply that here. The fade variable is my best attempt at a window function: everything past the 1000hz mark fades to 0 in 2 samples.
Any ideas?
Thanks!
So it turns out that I was right (yay): Every 1024 samples I was getting a click sound which made the audio sound awful. To fix this, I faded inbetween many short overlapping chunks of filtered audio. It's not fast, but it works, and I'm pretty sure this is what they mean by "windowing"
public class OggDFT
{
int sample_length;
byte[] samples;
DragonOgg.MediaPlayer.OggFile f;
int rate = 0;
System.Numerics.Complex[][] frequencies;
DiscreteFourierTransform dft = new DiscreteFourierTransform();
int samplespacing = 128;
int samplesize = 1024;
int sampleCount;
public void ExampleLowpass()
{
int shiftUp = 1000; //1khz
int fade = 2; //8 sample fade.
int halfsize = samplesize / 2;
int kick = frequencies[0].Length * shiftUp / rate;
for (int i = 0; i < sampleCount; i++)
{
for (int j = 0; j < frequencies[i].Length; j++)
{
if (j == 0 || j == halfsize)
continue;
if (j < halfsize)
{
if (!(j < kick + 1))
{
frequencies[i][j] = 0;
}
}
else
{
if (!(j - halfsize > halfsize - 1 - kick))
{
frequencies[i][j] = 0;
}
}
}
dft.BluesteinInverse(frequencies[i], MathNet.Numerics.IntegralTransforms.FourierOptions.Default);
}
}
public OggDFT(DragonOgg.MediaPlayer.OggFile f)
{
Complex[] c = new Complex[10];
for (int i = 0; i < 10; i++)
c[i] = i;
ShiftComplex(-2, c, 5, 10);
this.f = f;
//Make a 20MB buffer.
samples = new byte[20000000];
int sample_length = 0;
//This block here simply loads the uncompressed data from the ogg file into a nice n' large 20MB buffer. If you want to use the same library as I've used, It's called DragonOgg (If you cant tell by the namespace)
while (sample_length < samples.Length)
{
var bs = f.GetBufferSegment(4096); //Get ~4096 bytes (does not gurantee that 4096 bytes will be returned.
if (bs.ReturnValue == 0)
break; //End of file
//Set the rate
rate = bs.RateHz;
//Display some loading info:
Console.WriteLine("seconds: " + sample_length / rate);
//It's stereo so we want half the data.
int max = bs.ReturnValue / 2;
//Buffer overflow care.
if (samples.Length - sample_length < max)
max = samples.Length - sample_length;
//The copier.
for (int j = 0; j < max; j++)
{
//I'm using j * 2 here because I know that the input audio is 8Bit Stereo, and we want just one mono channel. So we skip every second one.
samples[sample_length + j] = bs.Buffer[j * 2];
}
sample_length += max;
if (max == 0)
break;
}
sampleCount = (sample_length - 1) / samplespacing + 1;
frequencies = new System.Numerics.Complex[sampleCount][];
for (int i = 0; i < sample_length; i += samplespacing)
{
Console.WriteLine("Sample---" + i + " / " + sample_length);
System.Numerics.Complex[] sample;
if (i + samplesize > sample_length)
sample = new System.Numerics.Complex[sample_length - i];
else
sample = new System.Numerics.Complex[samplesize];
for (int j = 0; j < sample.Length; j++)
{
sample[j] = (float)(samples[i + j] - 128) / 128.0f;
}
dft.BluesteinForward(sample, MathNet.Numerics.IntegralTransforms.FourierOptions.Default);
frequencies[i / samplespacing] = sample;
}
//Perform the filters to the frequencies
ExampleLowpass();
//Make window kernel thingy
float[] kernel = new float[samplesize / samplespacing * 2];
for (int i=0; i<kernel.Length; i++)
{
kernel[i] = (float)((1-Math.Cos(2*Math.PI*i/(kernel.Length - 1)))/2);
}
//Apply window kernel thingy
for (int i = 0; i < sample_length; i++)
{
int jstart = i / samplespacing - samplesize / samplespacing + 1;
int jend = i / samplespacing;
if (jstart < 0) jstart = 0;
float ktotal = 0;
float stotal = 0;
for (int j = jstart; j <= jend; j++)
{
float kernelHere = 1.0f;
if (jstart != jend)
kernelHere = kernel[(j - jstart) * kernel.Length / (jend + 1 - jstart)];
int index = i - j * samplespacing;
stotal += (float)frequencies[j][index].Real * kernelHere;
ktotal += kernelHere;
}
if (ktotal != 0)
{
stotal /= ktotal;
samples[i] = (byte)(stotal * 128 * 0.9f + 128);
}
else
{
Console.WriteLine("BAD " + jstart + " " + jend + " sec: " + ((float)i / rate));
samples[i] = (byte)(stotal * 128 * 0.9f + 128);
}
}
BinaryWriter bw = new BinaryWriter(File.OpenWrite("sound"));
for (int i = 0; i < sample_length; i++)
{
bw.Write(samples[i]);
}
bw.Close();
}
}
If you want to compile this, you'll need DragonOgg (http://sourceforge.net/projects/dragonogg/) and MathNet.Numerics (http://mathnetnumerics.codeplex.com/)
I hope it helps someone - I don't know about how StackOverflow licences by default, but this code as it stands is public domain.
On further thought, I decided I could achieve an approximated effect much easier by simply "blurring" the samples to get a basic low pass filter. A high pass filter can be made by subtracting the result of the low pass.

Programming Contest Question: Counting Polyominos

Please see my own answer, I think I did it!
Hi,
An example question for a programming contest was to write a program that finds out how much polyominos are possible with a given number of stones.
So for two stones (n = 2) there is only one polyominos:
XX
You might think this is a second solution:
X
X
But it isn't. The polyominos are not unique if you can rotate them.
So, for 4 stones (n = 4), there are 7 solutions:
X
X XX X X X X
X X XX X XX XX XX
X X X XX X X XX
The application has to be able to find the solution for 1 <= n <=10
PS: Using the list of polyominos on Wikipedia isn't allowed ;)
EDIT: Of course the question is: How to do this in Java, C/C++, C#
I started this project in Java. But then I had to admit I didn't know how to build polyominos using an efficient algorithm.
This is what I had so far:
import java.util.ArrayList;
import java.util.List;
public class Main
{
private int countPolyminos(int n)
{
hashes.clear();
count = 0;
boolean[][] matrix = new boolean[n][n];
createPolyominos(matrix, n);
return count;
}
private List<Integer> hashes = new ArrayList<Integer>();
private int count;
private void createPolyominos(boolean[][] matrix, int n)
{
if (n == 0)
{
boolean[][] cropped = cropMatrix(matrix);
int hash = hashMatrixOrientationIndependent(matrix);
if (!hashes.contains(hash))
{
count++;
hashes.add(hash);
}
return;
}
// Here is the real trouble!!
// Then here something like; createPolyominos(matrix, n-1);
// But, we need to keep in mind that the polyominos can have ramifications
}
public boolean[][] copy(boolean[][] matrix)
{
boolean[][] b = new boolean[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; ++i)
{
System.arraycopy(matrix[i], 0, b, 0, matrix[i].length);
}
return b;
}
public boolean[][] cropMatrix(boolean[][] matrix)
{
int l = 0, t = 0, r = 0, b = 0;
// Left
left: for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
break left;
}
}
l++;
}
// Right
right: for (int x = matrix.length - 1; x >= 0; --x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
break right;
}
}
r++;
}
// Top
top: for (int y = 0; y < matrix[0].length; ++y)
{
for (int x = 0; x < matrix.length; ++x)
{
if (matrix[x][y])
{
break top;
}
}
t++;
}
// Bottom
bottom: for (int y = matrix[0].length; y >= 0; --y)
{
for (int x = 0; x < matrix.length; ++x)
{
if (matrix[x][y])
{
break bottom;
}
}
b++;
}
// Perform the real crop
boolean[][] cropped = new boolean[matrix.length - l - r][matrix[0].length - t - b];
for (int x = l; x < matrix.length - r; ++x)
{
System.arraycopy(matrix[x - l], t, cropped, 0, matrix[x].length - t - b);
}
return cropped;
}
public int hashMatrix(boolean[][] matrix)
{
int hash = 0;
for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
hash += matrix[x][y] ? (((x + 7) << 4) * ((y + 3) << 6) * 31) : ((((x+5) << 9) * (((y + x) + 18) << 7) * 53));
}
}
return hash;
}
public int hashMatrixOrientationIndependent(boolean[][] matrix)
{
int hash = 0;
hash += hashMatrix(matrix);
for (int i = 0; i < 3; ++i)
{
matrix = rotateMatrixLeft(matrix);
hash += hashMatrix(matrix);
}
return hash;
}
public boolean[][] rotateMatrixRight(boolean[][] matrix)
{
/* W and H are already swapped */
int w = matrix.length;
int h = matrix[0].length;
boolean[][] ret = new boolean[h][w];
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
ret[i][j] = matrix[w - j - 1][i];
}
}
return ret;
}
public boolean[][] rotateMatrixLeft(boolean[][] matrix)
{
/* W and H are already swapped */
int w = matrix.length;
int h = matrix[0].length;
boolean[][] ret = new boolean[h][w];
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
ret[i][j] = matrix[j][h - i - 1];
}
}
return ret;
}
}
There are only 4,461 polynominoes of size 10, so we can just enumerate them all.
Start with a single stone. To expand it by one stone, try add the new stone in at all empty cells that neighbour an existing stone. Do this recursively until reaching the desired size.
To avoid duplicates, keep a hash table of all polynominoes of each size we've already enumerated. When we put together a new polynomino, we check that its not already in the hash table. We also need to check its 3 rotations (and possibly its mirror image). While duplicate checking at the final size is the only strictly necessary check, checking at each step prunes recursive branches that will yield a new polynomino.
Here's some pseudo-code:
polynomino = array of n hashtables
function find_polynominoes(n, base):
if base.size == n:
return
for stone in base:
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
new_stone.x = stone.x + dx
new_stone.y = stone.y + dy
if new_stone not in base:
new_polynomino = base + new_stone
is_new = true
for rotation in [0, 90, 180, 270]:
if new_polynomino.rotate(rotation) in polynomino[new_polynomino.size]:
is_new = false
break
if is_new:
polynomino[new_polynomino.size].add(new_polynomino)
Just solved this as well in java. Since all here appear to have performance issues. I give you mine as well.
Board reprsentation:
2 arrays of integers. 1 for the rows and 1 for the columns.
Rotation: column[i]=row[size-(i+1)], row[i] = reverse(column[i]) where reverse is the bits reversed according to the size (for size = 4 and first 2 bits are taken: rev(1100) = 0011)
Shifting block: row[i-1] = row[i], col[i]<<=1
Check if bit is set: (row[r] & (1<<c)) > 0
Board uniqueness: The board is unique when the array row is unique.
Board hash: Hashcode of the array row
..
So this makes all operations fast. Many of them would have been O(sizeĀ²) in the 2D array representation instead of now O(size).
Algorithm:
Start with the block of size 1
For each size start from the blocks with 1 stone less.
If it's possible to add the stone. Check if it was already added to the set.
If it's not yet added. Add it to the solution of this size.
add the block to the set and all its rotations. (3 rotations, 4 in total)
Important, after each rotation shift the block as left/top as possible.
+Special cases: do the same logic for the next 2 cases
shift block one to the right and add stone in first column
shift block one to the bottom and add stone in first row
Performance:
N=5 , time: 3ms
N=10, time: 58ms
N=11, time: 166ms
N=12, time: 538ms
N=13, time: 2893ms
N=14, time:17266ms
N=15, NA (out of heapspace)
Code:
https://github.com/Samjayyy/logicpuzzles/tree/master/polyominos
The most naive solution is to start with a single X, and for each iteration, build the list of unique possible next-states. From that list, build the list of unique states by adding another X. Continue this until the iteration you desire.
I'm not sure if this runs in reasonable time for N=10, however. It might, depending on your requirements.
I think I did it!
EDIT: I'm using the SHA-256 algorithm to hash them, now it works correct.
Here are the results:
numberOfStones -> numberOfPolyominos
1 -> 1
2 -> 1
3 -> 2
4 -> 7
5 -> 18
6 -> 60
7 -> 196
8 -> 704
9 -> 2500
10 -> terminated
Here is the code (Java):
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/* VPW Template */
public class Main
{
/**
* #param args
*/
public static void main(String[] args) throws IOException
{
new Main().start();
}
public void start() throws IOException
{
/* Read the stuff */
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] input = new String[Integer.parseInt(br.readLine())];
for (int i = 0; i < input.length; ++i)
{
input[i] = br.readLine();
}
/* Process each line */
for (int i = 0; i < input.length; ++i)
{
processLine(input[i]);
}
}
public void processLine(String line)
{
int n = Integer.parseInt(line);
System.out.println(countPolyminos(n));
}
private int countPolyminos(int n)
{
hashes.clear();
count = 0;
boolean[][] matrix = new boolean[n][n];
matrix[n / 2][n / 2] = true;
createPolyominos(matrix, n - 1);
return count;
}
private List<BigInteger> hashes = new ArrayList<BigInteger>();
private int count;
private void createPolyominos(boolean[][] matrix, int n)
{
if (n == 0)
{
boolean[][] cropped = cropMatrix(matrix);
BigInteger hash = hashMatrixOrientationIndependent(cropped);
if (!hashes.contains(hash))
{
// System.out.println(count + " Found!");
// printMatrix(cropped);
// System.out.println();
count++;
hashes.add(hash);
}
return;
}
for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
if (x > 0 && !matrix[x - 1][y])
{
boolean[][] clone = copy(matrix);
clone[x - 1][y] = true;
createPolyominos(clone, n - 1);
}
if (x < matrix.length - 1 && !matrix[x + 1][y])
{
boolean[][] clone = copy(matrix);
clone[x + 1][y] = true;
createPolyominos(clone, n - 1);
}
if (y > 0 && !matrix[x][y - 1])
{
boolean[][] clone = copy(matrix);
clone[x][y - 1] = true;
createPolyominos(clone, n - 1);
}
if (y < matrix[x].length - 1 && !matrix[x][y + 1])
{
boolean[][] clone = copy(matrix);
clone[x][y + 1] = true;
createPolyominos(clone, n - 1);
}
}
}
}
}
public boolean[][] copy(boolean[][] matrix)
{
boolean[][] b = new boolean[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; ++i)
{
System.arraycopy(matrix[i], 0, b[i], 0, matrix[i].length);
}
return b;
}
public void printMatrix(boolean[][] matrix)
{
for (int y = 0; y < matrix.length; ++y)
{
for (int x = 0; x < matrix[y].length; ++x)
{
System.out.print((matrix[y][x] ? 'X' : ' '));
}
System.out.println();
}
}
public boolean[][] cropMatrix(boolean[][] matrix)
{
int l = 0, t = 0, r = 0, b = 0;
// Left
left: for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
break left;
}
}
l++;
}
// Right
right: for (int x = matrix.length - 1; x >= 0; --x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
break right;
}
}
r++;
}
// Top
top: for (int y = 0; y < matrix[0].length; ++y)
{
for (int x = 0; x < matrix.length; ++x)
{
if (matrix[x][y])
{
break top;
}
}
t++;
}
// Bottom
bottom: for (int y = matrix[0].length - 1; y >= 0; --y)
{
for (int x = 0; x < matrix.length; ++x)
{
if (matrix[x][y])
{
break bottom;
}
}
b++;
}
// Perform the real crop
boolean[][] cropped = new boolean[matrix.length - l - r][matrix[0].length - t - b];
for (int x = l; x < matrix.length - r; ++x)
{
System.arraycopy(matrix[x], t, cropped[x - l], 0, matrix[x].length - t - b);
}
return cropped;
}
public BigInteger hashMatrix(boolean[][] matrix)
{
try
{
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update((byte) matrix.length);
md.update((byte) matrix[0].length);
for (int x = 0; x < matrix.length; ++x)
{
for (int y = 0; y < matrix[x].length; ++y)
{
if (matrix[x][y])
{
md.update((byte) x);
} else
{
md.update((byte) y);
}
}
}
return new BigInteger(1, md.digest());
} catch (NoSuchAlgorithmException e)
{
System.exit(1);
return null;
}
}
public BigInteger hashMatrixOrientationIndependent(boolean[][] matrix)
{
BigInteger hash = hashMatrix(matrix);
for (int i = 0; i < 3; ++i)
{
matrix = rotateMatrixLeft(matrix);
hash = hash.add(hashMatrix(matrix));
}
return hash;
}
public boolean[][] rotateMatrixRight(boolean[][] matrix)
{
/* W and H are already swapped */
int w = matrix.length;
int h = matrix[0].length;
boolean[][] ret = new boolean[h][w];
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
ret[i][j] = matrix[w - j - 1][i];
}
}
return ret;
}
public boolean[][] rotateMatrixLeft(boolean[][] matrix)
{
/* W and H are already swapped */
int w = matrix.length;
int h = matrix[0].length;
boolean[][] ret = new boolean[h][w];
for (int i = 0; i < h; ++i)
{
for (int j = 0; j < w; ++j)
{
ret[i][j] = matrix[j][h - i - 1];
}
}
return ret;
}
Here's my solution in Java to the same problem. I can confirm Martijn's numbers (see below). I've also added in the rough time it takes to compute the results (mid-2012 Macbook Retina Core i7). I suppose substantial performance improvements could be achieved via parallelization.
numberOfStones -> numberOfPolyominos
1 -> 1
2 -> 1
3 -> 2
4 -> 7
5 -> 18
6 -> 60
7 -> 196
8 -> 704 (3 seconds)
9 -> 2500 (46 seconds)
10 -> 9189 (~14 minutes)
.
/*
* This class is a solution to the Tetris unique shapes problem.
* That is, the game of Tetris has 7 unique shapes. These 7 shapes
* are all the possible unique combinations of any 4 adjoining blocks
* (i.e. ignoring rotations).
*
* How many unique shapes are possible with, say, 7 or n blocks?
*
* The solution uses recursive back-tracking to construct all the possible
* shapes. It uses a HashMap to store unique shapes and to ignore rotations.
* It also uses a temporary HashMap so that the program does not needlessly
* waste time checking the same path multiple times.
*
* Even so, this is an exponential run-time solution, with n=10 taking a few
* minutes to complete.
*/
package com.glugabytes.gbjutils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class TetrisBlocks {
private HashMap uShapes;
private HashMap tempShapes;
/* Get a map of unique shapes for n squares. The keys are string-representations
* of each shape, and values are corresponding boolean[][] arrays.
* #param squares - number of blocks to use for shapes, e.g. n=4 has 7 unique shapes
*/
public Map getUniqueShapes(int squares) {
uShapes = new HashMap();
tempShapes = new HashMap();
boolean[][] data = new boolean[squares*2+1][squares*2+1];
data[squares][squares] = true;
make(squares, data, 1); //start the process with a single square in the center of a boolean[][] matrix
return uShapes;
}
/* Recursivelly keep adding blocks to the data array until number of blocks(squares) = required size (e.g. n=4)
* Make sure to eliminate rotations. Also make sure not to enter infinite backtracking loops, and also not
* needlessly recompute the same path multiple times.
*/
private void make(int squares, boolean[][] data, int size) {
if(size == squares) { //used the required number of squares
//get a trimmed version of the array
boolean[][] trimmed = trimArray(data);
if(!isRotation(trimmed)) { //if a unique piece, add it to unique map
uShapes.put(arrayToString(trimmed), trimmed);
}
} else {
//go through the grid 1 element at a time and add a block next to an existing block
//do this for all possible combinations
for(int iX = 0; iX < data.length; iX++) {
for(int iY = 0; iY < data.length; iY++) {
if(data[iX][iY] == true) { //only add a block next to an existing block
if(data[iX+1][iY] != true) { //if no existing block to the right, add one and recuse
data[iX+1][iY] = true;
if(!isTempRotation(data)) { //only recurse if we haven't already been on this path before
make(squares, data, size+1);
tempShapes.put(arrayToString(data), data); //store this path so we don't repeat it later
}
data[iX+1][iY] = false;
}
if(data[iX-1][iY] != true) { //repeat by adding a block on the left
data[iX-1][iY] = true;
if(!isTempRotation(data)) {
make(squares, data, size+1);
tempShapes.put(arrayToString(data), data);
}
data[iX-1][iY] = false;
}
if(data[iX][iY+1] != true) { //repeat by adding a block down
data[iX][iY+1] = true;
if(!isTempRotation(data)) {
make(squares, data, size+1);
tempShapes.put(arrayToString(data), data);
}
data[iX][iY+1] = false;
}
if(data[iX][iY-1] != true) { //repeat by adding a block up
data[iX][iY-1] = true;
if(!isTempRotation(data)) {
make(squares, data, size+1);
tempShapes.put(arrayToString(data), data);
}
data[iX][iY-1] = false;
}
}
}
}
}
}
/**
* This function basically removes all rows and columns that have no 'true' flags,
* leaving only the portion of the array that contains useful data.
*
* #param data
* #return
*/
private boolean[][] trimArray(boolean[][] data) {
int maxX = 0;
int maxY = 0;
int firstX = data.length;
int firstY = data.length;
for(int iX = 0; iX < data.length; iX++) {
for (int iY = 0; iY < data.length; iY++) {
if(data[iX][iY]) {
if(iY < firstY) firstY = iY;
if(iY > maxY) maxY = iY;
}
}
}
for(int iY = 0; iY < data.length; iY++) {
for (int iX = 0; iX < data.length; iX++) {
if(data[iX][iY]) {
if(iX < firstX) firstX = iX;
if(iX > maxX) maxX = iX;
}
}
}
boolean[][] trimmed = new boolean[maxX-firstX+1][maxY-firstY+1];
for(int iX = firstX; iX <= maxX; iX++) {
for(int iY = firstY; iY <= maxY; iY++) {
trimmed[iX-firstX][iY-firstY] = data[iX][iY];
}
}
return trimmed;
}
/**
* Return a string representation of the 2D array.
*
* #param data
* #return
*/
private String arrayToString(boolean[][] data) {
StringBuilder sb = new StringBuilder();
for(int iX = 0; iX < data.length; iX++) {
for(int iY = 0; iY < data[0].length; iY++) {
sb.append(data[iX][iY] ? '#' : ' ');
}
sb.append('\n');
}
return sb.toString();
}
/**
* Rotate an array clockwise by 90 degrees.
* #param data
* #return
*/
public boolean[][] rotate90(boolean[][] data) {
boolean[][] rotated = new boolean[data[0].length][data.length];
for(int iX = 0; iX < data.length; iX++) {
for(int iY = 0; iY < data[0].length; iY++) {
rotated[iY][iX] = data[data.length - iX - 1][iY];
}
}
return rotated;
}
/**
* Checks to see if two 2d boolean arrays are the same
* #param a
* #param b
* #return
*/
public boolean equal(boolean[][] a, boolean[][] b) {
if(a.length != b.length || a[0].length != b[0].length) {
return false;
} else {
for(int iX = 0; iX < a.length; iX++) {
for(int iY = 0; iY < a[0].length; iY++) {
if(a[iX][iY] != b[iX][iY]) {
return false;
}
}
}
}
return true;
}
public boolean isRotation(boolean[][] data) {
//check to see if it's a rotation of a shape that we already have
data = rotate90(data); //+90*
String str = arrayToString(data);
if(!uShapes.containsKey(str)) {
data = rotate90(data); //180*
str = arrayToString(data);
if(!uShapes.containsKey(str)) {
data = rotate90(data); //270*
str = arrayToString(data);
if(!uShapes.containsKey(str)) {
return false;
}
}
}
return true;
}
public boolean isTempRotation(boolean[][] data) {
//check to see if it's a rotation of a shape that we already have
data = rotate90(data); //+90*
String str = arrayToString(data);
if(!tempShapes.containsKey(str)) {
data = rotate90(data); //180*
str = arrayToString(data);
if(!tempShapes.containsKey(str)) {
data = rotate90(data); //270*
str = arrayToString(data);
if(!tempShapes.containsKey(str)) {
return false;
}
}
}
return true;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
TetrisBlocks tetris = new TetrisBlocks();
long start = System.currentTimeMillis();
Map shapes = tetris.getUniqueShapes(8);
long end = System.currentTimeMillis();
Iterator it = shapes.keySet().iterator();
while(it.hasNext()) {
String shape = (String)it.next();
System.out.println(shape);
}
System.out.println("Unique Shapes: " + shapes.size());
System.out.println("Time: " + (end-start));
}
}
Here's some python that computes the answer. Seems to agree with Wikipedia. It isn't terribly fast because it uses lots of array searches instead of hash tables, but it still takes only a minute or so to complete.
#!/usr/bin/python
# compute the canonical representation of polyomino p.
# (minimum x and y coordinate is zero, sorted)
def canonical(p):
mx = min(map(lambda v: v[0], p))
my = min(map(lambda v: v[1], p))
return sorted(map(lambda v: (v[0]-mx, v[1]-my), p))
# rotate p 90 degrees
def rotate(p):
return canonical(map(lambda v: (v[1], -v[0]), p))
# add one tile to p
def expand(p):
result = []
for (x,y) in p:
for (dx,dy) in ((-1,0),(1,0),(0,-1),(0,1)):
if p.count((x+dx,y+dy)) == 0:
result.append(canonical(p + [(x+dx,y+dy)]))
return result
polyominos = [[(0,0)]]
for i in xrange(1,10):
new_polyominos = []
for p in polyominos:
for q in expand(p):
dup = 0
for r in xrange(4):
if new_polyominos.count(q) != 0:
dup = 1
break
q = rotate(q)
if not dup: new_polyominos.append(q)
polyominos = new_polyominos
print i+1, len(polyominos)
Here is my full Python solution inspired by #marcog's answer. It prints the number of polyominos of sizes 2..10 in about 2s on my laptop.
The algorithm is straightforward:
Size 1: start with one square
Size n + 1: take all pieces of size n and try adding a single square to all possible adjacent positions. This way you find all possible new pieces of size n + 1. Skip duplicates.
The main speedup came from hashing pieces to quickly check if we've already seen a piece.
import itertools
from collections import defaultdict
n = 10
print("Number of Tetris pieces up to size", n)
# Times:
# n is number of blocks
# - Python O(exp(n)^2): 10 blocks 2.5m
# - Python O(exp(n)): 10 blocks 2.5s, 11 blocks 10.9s, 12 block 33s, 13 blocks 141s (800MB memory)
smallest_piece = [(0, 0)] # We represent a piece as a list of block positions
pieces_of_size = {
1: [smallest_piece],
}
# Returns a list of all possible pieces made by adding one block to given piece
def possible_expansions(piece):
# No flatMap in Python 2/3:
# https://stackoverflow.com/questions/21418764/flatmap-or-bind-in-python-3
positions = set(itertools.chain.from_iterable(
[(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)] for (x, y) in piece
))
# Time complexity O(n^2) can be improved
# For each valid position, append to piece
expansions = []
for p in positions:
if not p in piece:
expansions.append(piece + [p])
return expansions
def rotate_90_cw(piece):
return [(y, -x) for (x, y) in piece]
def canonical(piece):
min_x = min(x for (x, y) in piece)
min_y = min(y for (x, y) in piece)
res = sorted((x - min_x, y - min_y) for (x, y) in piece)
return res
def hash_piece(piece):
return hash(tuple(piece))
def expand_pieces(pieces):
expanded = []
#[
# 332322396: [[(1,0), (0,-1)], [...]],
# 323200700000: [[(1,0), (0,-2)]]
#]
# Multimap because two different pieces can happen to have the same hash
expanded_hashes = defaultdict(list)
for piece in pieces:
for e in possible_expansions(piece):
exp = canonical(e)
is_new = True
if exp in expanded_hashes[hash_piece(exp)]:
is_new = False
for rotation in range(3):
exp = canonical(rotate_90_cw(exp))
if exp in expanded_hashes[hash_piece(exp)]:
is_new = False
if is_new:
expanded.append(exp)
expanded_hashes[hash_piece(exp)].append(exp)
return expanded
for i in range(2, n + 1):
pieces_of_size[i] = expand_pieces(pieces_of_size[i - 1])
print("Pieces with {} blocks: {}".format(i, len(pieces_of_size[i])))

Categories

Resources