
With LINQ on Life
John Conway ’s famous game, “Life,” due to its simplicity, amusement, and instructiveness, has been implemented by programmers so many times that it is probably inferior only to the notorious “bubble” sorting.
I will give, however, one more version of this wonderful game, based entirely on LINQ technology in the .NET environment - simple, compact, without loops and multidimensional arrays.
Immediately make a reservation that the task of achieving maximum efficiency was not posed .
I believe few people doubt that LINQ makes it easy to write compact, expressive, and efficient code. Let this simple example serve as further evidence of this.
UPD: If the goal is extremely compact, the code could look something like this:
I will give, however, one more version of this wonderful game, based entirely on LINQ technology in the .NET environment - simple, compact, without loops and multidimensional arrays.
Immediately make a reservation that the task of achieving maximum efficiency was not posed .
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace LinqLife
{
public struct Cell
{
public readonly int X, Y;
public Cell(int x, int y) { X = x; Y = y; }
}
public class Life : IEnumerable
{
private List _cells = new List();
private static readonly int[] Liveables = { 2, 3 };
public bool Next()
{
var died = _cells
.Where(cell => !Liveables.Contains(Count(cell))) // Условие смерти
.ToArray();
var born = _cells
.SelectMany(Ambit) // Все окружающие ячейки
.Distinct() // ...без дубликатов
.Except(_cells) // ...пустые
.Where(cell => Count(cell) == 3) // Условие рождения
.ToArray();
if (died.Length == 0 && born.Length == 0)
return false; // Нет изменений
_cells = _cells
.Except(died)
.Concat(born)
.ToList();
return _cells.Any(); // Не все еще умерли?
}
private int Count(Cell cell)
{
return Ambit(cell)
.Intersect(_cells)
.Count();
}
private static IEnumerable Ambit(Cell cell)
{
return Enumerable.Range(-1, 3)
.SelectMany(x => Enumerable.Range(-1, 3)
.Where(y => x != 0 || y != 0) // Кроме центральной клетки
.Select(y => new Cell(cell.X + x, cell.Y + y)));
}
public override string ToString()
{
if (_cells.Count == 0)
return string.Empty;
var xmin = _cells.Min(cell => cell.X);
var xmax = _cells.Max(cell => cell.X);
var ymin = _cells.Min(cell => cell.Y);
var ymax = _cells.Max(cell => cell.Y);
var matrix = Enumerable.Range(xmin, xmax - xmin + 1)
.Select(x => Enumerable.Range(ymin, ymax - ymin + 1)
.Select(y => _cells.Contains(new Cell(x, y))));
return string.Join(Environment.NewLine,
matrix.Select(row =>
string.Join("",
row.Select(b => b ? "X" : "."))));
}
public IEnumerator GetEnumerator()
{
return _cells.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(int x, int y)
{
_cells.Add(new Cell(x, y));
}
}
class Program
{
static void Main(string[] args)
{
var life = new Life { { 1, 1 }, { 2, 2 }, { 3, 3 }, { 1, 2 }, { 1, 3 } };
var i = 0;
do
{
Console.WriteLine("#{0}\r\n{1}", i++, life);
Console.ReadLine();
} while (life.Next());
Console.WriteLine(life.Any() ? "* Stagnation!" : "* Extinction!");
}
}
}
| | | | |
Code notes:
- The current state of the game is a list of coordinates of living cells, updated at each step. In other implementations, the representation of the playing field as a two-dimensional array is usually used.
- Using operations on sets: union, intersection, subtraction.
- Not a single explicit cycle (in the Life class ).
- Using struct for Cell allows you not to worry about comparing cells and provides efficient memory management (.NET natively supports ValueType in typed collections).
- Almost unlimited size of the playing field. Moreover, "for free."
- A simple optimization is possible: replacing the type _cell with List with a HashSet and calling Except () / Concat () in Next () with loops with Remove () / Add () will give a significant increase in speed (though to the detriment of the elegance of the code).
- Simple multi-threaded optimization is possible: AsParallel () .
- IEnumerable inheritance and the Add () method were added solely for the sake of initialization grace.
I believe few people doubt that LINQ makes it easy to write compact, expressive, and efficient code. Let this simple example serve as further evidence of this.
UPD: If the goal is extremely compact, the code could look something like this:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqLife
{
using Cell = Tuple;
public class Life
{
public static List Next(List cells)
{
Func> ambit =
cell => Enumerable.Range(-1, 3).SelectMany(x => Enumerable.Range(-1, 3)
.Where(y => x != 0 || y != 0)
.Select(y => new Cell(cell.Item1 + x, cell.Item2 + y)));
Func count = cell => ambit(cell).Intersect(cells).Count();
return cells.SelectMany(ambit)
.Where(cell => count(cell) == 3)
.Union(cells.Where(cell => count(cell) == 2))
.ToList();
}
}
class Program
{
static void Main(string[] args)
{
var world = new List { new Cell(1, 1), new Cell(2, 2), new Cell(3, 3), new Cell(1, 2), new Cell(1, 3) };
do
{
Console.WriteLine(Dump(world));
Console.ReadLine();
} while ((world = Life.Next(world)).Any());
}
public static string Dump(List cells)
{
int xmin = cells.Min(x => x.Item1), xmax = cells.Max(x => x.Item1),
ymin = cells.Min(x => x.Item2), ymax = cells.Max(x => x.Item2);
var matrix = Enumerable.Range(xmin, xmax - xmin + 1)
.Select(x => Enumerable.Range(ymin, ymax - ymin + 1)
.Select(y => cells.Contains(new Cell(x, y))));
return string.Join(Environment.NewLine, matrix.Select(row => string.Join("", row.Select(b => b ? "X" : "."))));
}
}
}
| | | | | |