Hier noch einmal die Methode vom letzten mal:
Sofort ist mir aufgefallen, dass die erste Bedingung der for-Schleife überflüssig ist. Generell kann ich ruhig jede Kugel prüfen und damit reicht die zweite Bedingung aus.
public bool IsInRow(List<Ball> balls) {
if (balls.Count <= 1) {
return true;
}
int minX = balls.Min(ball => ball.PosX);
int minY = balls.Min(ball => ball.PosY);
int dx = balls.Max(ball => ball.PosX) - minX;
int dy = balls.Max(ball => ball.PosY) - minY;
int xinc = Convert.ToInt32(dx > 0);
int yinc = Convert.ToInt32(dy > 0);
for (int i = 0, x = 0, y = 0; (x <= dx && y <= dy) ||
i < balls.Count; x += xinc, y += yinc, i++) {
if (balls.Find(ball => ball.PosX == (x + minX) &&
ball.PosY == (y + minY)) == null) {
return false;
}
}
return true;
}
...Als nächstes kann ich die Suche nach einer Kugel an der jeweiligen Position durch eine Methode austauschen.
for (int i = 0, x = 0, y = 0; i < balls.Count;
x += xinc, y += yinc, i++) {
...
}
...Da ich den Test so oft durchführen muss, wie Kugeln in der Liste sind kann ich die Iteration auch über eine foreach-Schleife machen. So brauche ich selbst nicht mehr auf das Ende der Liste prüfen.
for (int i = 0, x = 0, y = 0; i < balls.Count;
x += xinc, y += yinc, i++) {
if (ThereIsNoBall(balls, minX + x, minY + y)) {
return false;
}
}
...
private bool ThereIsNoBall(List<Ball> balls) {
return balls.Find(ball => ball.PosX == x && ball.PosY == y) == null;
}
...
int x = 0;
int y = 0;
foreach (Ball ball in balls) {
if (ThereIsNoBall(balls, x + minX, y + minY)) {
return false;
}
x += xinc;
y += yinc;
}
...
Im nächsten Schritt werde ich zwei Dinge verändern. Zum einen werden die Variablen dx und dy nur an einer stelle verwendet. Also kann ich mir diese Variable auch komplett sparen. Als nächtes werde ich noch die index-Variablen x und y entfernen, da ich auch direkt minX und minY hochzählen kann. Sinnvollerweise benenne ich minX und minY gleich in posX und posY um.
...Da jetzt die Zeile um x-/y-inc zu ermitteln etwas zu kryptisch aussieht, werde ich das noch in eine Methode auslagern. Ausserdem passiert im wesentlichen in beiden Zeilen das Gleiche, was ebenfalls für eine neue Methode sprechen würde.
int posX = balls.Min(ball => ball.PosX);
int posY = balls.Min(ball => ball.PosY);
int xinc = Convert.ToInt32((balls.Max(ball => ball.PosX) - posX) > 0);
int yinc = Convert.ToInt32((balls.Max(ball => ball.PosY) - posY) > 0);
foreach (Ball ball in balls) {
if (ThereIsNoBall(balls, posX, posY)) {
return false;
}
posX += xinc;
posY += yinc;
}
...
...Als letztes werde ich noch überall das List
int xinc = GetStepSize(balls, posX, ball => ball.PosX);
int yinc = GetStepSize(balls, posY, ball => ball.PosY);
...
private int GetStepSize(IEnumerable<Ball> balls, int pos,
Func<Ball, bool> positionProjection) {
return Convert.ToInt32((balls.Max(positionProjection) - pos) > 0);
}
public bool IsInRow(IEnumerable<Ball> balls) {
if (balls.Count() <= 1) {
return true;
}
int posX = balls.Min(ball => ball.PosX);
int posY = balls.Min(ball => ball.PosY);
int xinc = GetStepSize(balls, posX, ball => ball.PosX);
int yinc = GetStepSize(balls, posY, ball => ball.PosY);
foreach (Ball ball in balls) {
if (ThereIsNoBall(balls, posX, posY)) {
return false;
}
posX += xinc;
posY += yinc;
}
return true;
}
private int GetStepSize(IEnumerable<ball> balls, int pos,
Func<Ball, bool> positionProjection) {
return Convert.ToInt32((balls.Max(positionProjection) - pos) > 0);
}
private bool ThereIsNoBall(IEnumerable<Ball> balls, int x, int y) {
return balls.FirstOrDefault(ball => ball.PosX == x && ball.PosY == y) == null;
}
Ansich finde ich, dass der Code so wensentlich besser zu lesen ist als vorher. Was man vielleicht noch machen könnte wäre, die x/y Werte zu einem Point zusammen zu fassen. Ich denke aber, dass es fürs Erste reichen sollte.
Wie gut, dass ich vorher Tests geschrieben habe und nun überprüfen kann, ob mein Refactoring irgend welche Auswirkungen auf das Verhalten hat :-)