Implémentation de type de données pour plusieurs histogrammes dynamiques
C'est presque l'exercice 3.2.14. extrait du livre Computer Science An Interdisciplinary Approach de Sedgewick & Wayne (puisque je suis auto-étudiant, je l'ai un peu changé):
Développez une version d' Histogramme qui utilise StdDraw , afin qu'un client puisse créer plusieurs histogrammes. Utilisez un client de test qui crée des histogrammes pour retourner des pièces (essais de Bernoulli) avec une pièce biaisée qui est en tête avec une probabilité p, pour p = 0,2, 0,4, 0,6. et 0.8, en prenant le nombre d'essais à partir de la ligne de commande.
Voici mes programmes:
public class Histogram {
private final double[] data;
private final double max;
public Histogram(double[] data, double max) {
this.data = data;
this.max = max;
StdDraw.setXscale(0, data.length);
StdDraw.setYscale(0, max * 3);
}
public double[] getData() {
return data;
}
public int findMax() {
double max = 0;
int dataLength = data.length;
for (int i = 0; i < dataLength; i++) {
max = Math.max(max, data[i]);
}
return (int) max;
}
public void addData(int index) {
data[index]++;
}
public void draw(double xIncrement, double yIncrement) {
StdDraw.enableDoubleBuffering();
StdDraw.setPenColor(StdDraw.BOOK_BLUE);
for (int i = 0; i < data.length; i++) {
StdDraw.filledRectangle(i + 0.5 + xIncrement * data.length, yIncrement * data.length + data[i] / 2, 0.45, data[i] / 2);
StdDraw.show();
}
StdDraw.setPenColor(StdDraw.RED);
StdDraw.line(data.length + xIncrement * data.length + 0.005, 0,
data.length + xIncrement * data.length + 0.025, max * 3);
}
public static void main(String[] args) {
int trials = Integer.parseInt(args[0]);
double[] diceData = new double[6];
Histogram histogram = new Histogram(diceData, (trials / 6) * 2);
StdDraw.setPenColor(StdDraw.BOOK_BLUE);
for (int t = 1; t <= trials; t++) {
double r = Math.random();
if (r < 1.0 / 6.0) histogram.addData(0);
else if (r < 2.0 / 6.0) histogram.addData(1);
else if (r < 3.0 / 6.0) histogram.addData(2);
else if (r < 4.0 / 6.0) histogram.addData(3);
else if (r < 5.0 / 6.0) histogram.addData(4);
else if (r < 6.0 / 6.0) histogram.addData(5);
histogram.draw(0, 0);
}
}
}
public class Histograms {
private final Histogram[] histograms;
private final double max;
public Histograms(Histogram[] histograms, double max) {
this.histograms = histograms;
this.max = max;
StdDraw.setXscale(0, histograms[0].getData().length * histograms.length);
StdDraw.setYscale(0, max);
}
public void draw() {
int rows = histograms.length;
int columns = histograms.length;
for (int i = 0; i < columns; i++) {
if (rows % columns == 0) {
rows = rows / columns;
break;
} else {
rows++;
}
}
int m = 0;
for (int c = 0; c < columns; c++) {
for (int r = 0; r < rows; r++) {
histograms[m].draw(c, r);
m++;
}
}
}
public static void main(String[] args) {
int trials = Integer.parseInt(args[0]);
double max = trials;
double[] probabilities = {
0.2,
0.4,
0.6,
0.8
};
double[][] diceData = new double[4][2];
Histogram[] histograms = new Histogram[4];
for (int i = 0; i < 4; i++) {
histograms[i] = new Histogram(diceData[i], max);
}
for (int t = 1; t <= trials; t++) {
if (Math.random() < probabilities[0]) histograms[0].addData(0);
else histograms[0].addData(1);
if (Math.random() < probabilities[1]) histograms[1].addData(0);
else histograms[1].addData(1);
if (Math.random() < probabilities[2]) histograms[2].addData(0);
else histograms[2].addData(1);
if (Math.random() < probabilities[3]) histograms[3].addData(0);
else histograms[3].addData(1);
Histograms multipleHistograms = new Histograms(histograms, max);
multipleHistograms.draw();
StdDraw.pause(20);
}
}
}
Je voulais Histogramégalement travailler indépendamment de Histogramset j'ai donc été obligé d'injecter une certaine redondance dans ces deux programmes (par exemple, ils utilisent tous les deux la mise à l'échelle, mais la mise à l'échelle à l'intérieur Histogramsremplace la mise à l'échelle à l'intérieur Histogram).
Voici un exemple de Histogram:
Entrée: 100
Production:
Voici un exemple de Histograms:
Entrée: 100
Production:
Est-il possible d'améliorer mes programmes?
Merci de votre attention.
Réponses
Quelques remarques, principalement axées sur la Histogramclasse.
Séparation des préoccupations
Votre application contient différentes parties: des parties algorithmiques (collecte de données d'histogramme), des parties d'interface utilisateur ( draw()méthodes) et des main()méthodes.
Vous séparez déjà ces préoccupations en différentes méthodes, ce qui est une bonne chose. Mais je recommande d'aller plus loin, et d'avoir différentes classes, par exemple:
- Histogramme (pour la partie algorithmique),
- StdDrawHistogram (pour présenter un histogramme à l'aide de
StdDraw), - HistogramApp (contenant la
main()méthode, reliant les pièces ensemble).
API de classe d'histogramme
Je changerais un peu l'API publique d'Histogram:
Vous utilisez un double[]tableau pour compter. Mais vous ne faites jamais de comptages fractionnaires, vous ajoutez toujours simplement un entier 1aux compartiments. Donc, vous devriez changer cela en int[](ou long[]dans le cas peu probable où vous vous attendez à plus de deux milliards de comptes).
Votre constructeur public Histogram(double[] data, double max)force l'appelant à préparer un double[]tableau de la dimension appropriée. Cela surprend l'appelant. Il devrait suffire d'indiquer le Histogramnombre de compartiments, et la configuration de la structure de comptage (votre tableau) devrait être la responsabilité du constructeur Histogram.
Encapsulation
Vous souhaitez généralement masquer les éléments internes (que vous utilisez un tableau au lieu d'une autre structure de données sophistiquée) de vos appelants. De cette façon, vous êtes libre de modifier les éléments internes ultérieurement sans impact sur le code en utilisant votre classe. Et vous empêchez le code externe de manipuler les internes, en contournant l'API de votre classe. Par exemple, actuellement, quelque part dans votre main()méthode, vous pouvez écrire des choses comme diceData[2] = 7;, puis les données d'histogramme de ce compartiment sont écrasées. La seule façon de modifier les données de votre histogramme devrait être d'appeler ses méthodes.
Suite à cet argument, je remplacerais également la public double[] getData()méthode par deux méthodes:
public int getNumberOfBuckets() {
return data.length;
}
public int getCountInBucket(int i) {
return data[i];
}
Vous forcez également l'appelant du constructeur à fournir une maxvaleur qui ne peut pas être connue à ce moment-là, alors que l'histogramme est toujours vide. Peut-être que le nom est trompeur et que vous ne parlez pas du nombre maximum dans un seau, mais de la hauteur du dessin. Ensuite, il n'appartient pas à la partie algorithmique, mais à la partie de dessin.
Lisibilité
Le code est généralement lu beaucoup plus souvent qu'écrit, il est donc important que vous puissiez comprendre immédiatement ce qui se passe.
Avec l'exemple de ce maxchamp, je recommande fortement de documenter la signification de vos éléments de code. Au moins pour moi, je ne sais pas si max est censé être le nombre maximum trouvé dans l'histogramme, ou un espace à réserver lors du dessin de l'histogramme, ou autre chose. L'écriture de commentaires Javadoc pour les champs et les méthodes vous permet d'éviter de telles ambiguïtés. Surtout si vous lisez votre code quelques mois après la création initiale, vous serez heureux de l'avoir documenté. Le fait de devoir exprimer vos concepts par écrit vous oblige à avoir les concepts clairement établis dans votre esprit.
Un autre moyen d'améliorer la lisibilité est la dénomination des éléments de code. Vous avez déjà fait du bon travail là-bas, seul le nom maxest un peu flou, comme indiqué précédemment.
Le champ max
Vous avez un champ nommé max. Il n'est utilisé qu'à l'intérieur du constructeur, pour définir l'échelle du dessin, et n'est accessible nulle part ailleurs. Ainsi, le conserver comme information dans l'instance pendant toute la durée de vie de l'instance après la construction est inutile, et vous pouvez éliminer le private final double max;champ.
Dans la findMax()méthode utile , vous avez une variable locale maxcollectant les informations algorithmiques "le plus grand nombre dans n'importe quel compartiment". Le fait d'avoir le même nom que le champ maxdéroute le lecteur. Je n'ai d'abord pas remarqué que c'étaient deux choses différentes. Je recommande de ne pas réutiliser les noms de champ en tant que variables locales.