📜 ⬆️ ⬇️

With LINQ on "Life"

The famous game of John Conway “Life” , due to its simplicity, entertaining and instructive, was implemented by programmers so many times that it is inferior only to the notorious sorting of “bubble”.

I will cite, however, another version of the execution of this wonderful game, entirely based on LINQ technology in the .NET environment - simple, compact, without cycles and multidimensional arrays.

At once I will make a reservation that the task of achieving maximum efficiency was not set .

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<Cell> { private List<Cell> _cells = new List<Cell>(); 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<Cell> 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<Cell> 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:


  1. The current state of the game - 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.
  2. Using set operations: union, intersection, subtraction.
  3. Not a single explicit cycle (in the Life class).
  4. Using the Cell struct allows you not to worry about comparing cells and provides efficient memory management (.NET natively supports ValueType in typed collections).
  5. Almost unlimited size of the playing field. And "free."
  6. A simple optimization is possible: replacing the _cell type from List with HashSet and calling Except () / Concat () in Next () for cycles with Remove () / Add () will give a significant speed increase (albeit to the detriment of the elegance of the code).
  7. Simple multi-threaded optimization is possible: AsParallel () .
  8. The inheritance from IEnumerable and the Add () method are added solely for the sake of initialization grace.

')
I suppose there is little doubt that LINQ makes it easy to write compact, expressive and efficient code. Let this simple example serve as another evidence of this.

UPD: If the goal is to be extremely compact, the code would look something like this:

 using System; using System.Collections.Generic; using System.Linq; namespace LinqLife { using Cell = Tuple<int, int>; public class Life { public static List<Cell> Next(List<Cell> cells) { Func<Cell, IEnumerable<Cell>> 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<Cell, int> 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<Cell> { 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<Cell> 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" : ".")))); } } } 

Source: https://habr.com/ru/post/263569/


All Articles