Garciat
2/17/2015 - 10:20 PM

pixelated.d

import std.conv;
import std.stdio;
import std.range;
import std.random;
import std.algorithm;

struct Pair(T, U)
{
  T fst;
  U snd;
  
  auto opAdd(const ref Pair!(T, U) p) const
  {
    return Pair!(T, U)(fst + p.fst, snd + p.snd);
  }
}

alias Coord = Pair!(int, int);

struct Matrix(T)
{
  int m, n;
  T data[];
  
  this(int m, int n)
  {
    this.m = m;
    this.n = n;
    this.data = new T[m * n];
  }
  
  bool inBounds(int i) const
  {
    return i >= 0 && i < m * n;
  }
  
  bool inBounds(int i, int j) const
  {
    return i >= 0 && j >= 0 && i < m && j < n;
  }
  
  bool inBounds(Coord p) const
  {
    return inBounds(p.fst, p.snd);
  }
  
  ref auto opIndex(int i)
  {
    return data[i];
  }
  
  ref auto opIndex(int i) const
  {
    return data[i];
  }
  
  ref auto opIndex(int i, int j)
  {
    return this[i * n + j];
  }
  
  ref auto opIndex(int i, int j) const
  {
    return this[i * n + j];
  }
  
  ref auto opIndex(Coord p)
  {
    return this[p.fst, p.snd];
  }
  
  ref auto opIndex(Coord p) const
  {
    return this[p.fst, p.snd];
  }
}

struct Board
{
  int colors;
  Matrix!int data;
  
  this(int colors, int width, int height)
  {
    this.colors = colors;
    this.data = Matrix!int(height, width);
  }
  
  auto size() const
  {
    return data.m * data.n;
  }
  
  auto idxToCoord(int i) const
  {
    return Coord(i / data.n, i % data.n);
  }
  
  auto coordToIdx(Coord c) const
  {
    return c.fst * data.n + c.snd;
  }
  
  auto neighbors(Coord cur) const
  {
    return
      [Coord(-1, 0), Coord(0, -1), Coord(0, 1), Coord(1, 0)]
      .map!(x => x + cur)
      .filter!(x => this.data.inBounds(x))
      .array;
  }
  
  auto neighbors(int i) const
  {
    return neighbors(idxToCoord(i)).map!(x => this.coordToIdx(x)).array;
  }
  
  void randomize()
  {
    foreach (i; 0..data.m * data.n)
    {
      data.data[i] = uniform(0, colors);
    }
  }
  
  void inflate(string path)
  {
    auto f = File(path);
    
    int i = 0;
    foreach (line; f.byLine)
    {
      foreach (int j, c; line)
      {
        data[i, j] = cast(int)(c - '0');
      }
      i += 1;
    }
  }
  
  auto toString() const
  {
    return
      iota(0, data.m)
      .map!(i => iota(0, data.n).map!(j => data[i, j]).map!(to!string).joiner)
      .joiner("\n");
  }
}

auto shift(T)(ref T[] arr)
{
  auto x = arr[0];
  arr = arr[1..$];
  return x;
}

struct Island
{
  int color;
  int[] elements;
  
  alias elements this;
}

auto findIslands(Board board)
{
  auto visited = new bool[board.size];
  Island[] islands = [];
  
  foreach (int i; 0..board.size)
  {
    if (visited[i]) continue;
    
    auto queue = [i];
    
    auto color = board.data[i];
    
    int[] island = [];
    
    while (queue.length > 0)
    {
      auto nx = queue.shift;
      
      if (visited[nx]) continue;
      
      if (color == board.data[nx])
      {
        visited[nx] = true;
        island ~= nx;
        queue ~= board.neighbors(nx);
      }
    }
    
    island = island.sort.uniq.array;
    
    islands ~= Island(color, island);
  }
  
  return islands;
}

auto islandsAdjacency(Board board, Island[] islands)
{
  auto adjacency = new int[][islands.length];
  
  auto idxmap = new int[board.size];
  
  foreach (int i, island; islands)
  {
    foreach (elem; island)
    {
      idxmap[elem] = i;
    }
  }
  
  foreach (i, island; islands)
  {
    int[] friends = [];
    
    foreach (elem; island)
    {
      foreach (neigh; board.neighbors(elem))
      {
        if (island.color == board.data[neigh]) continue;
        
        friends ~= idxmap[neigh];
      }
    }
    
    adjacency[i] = friends.sort.uniq.array;
  }
  
  return adjacency;
}

auto zipWith(alias f, Ranges...)(Ranges ranges)
{
  return zip(ranges).map!f;
}

struct NatSet
{
  int count;
  bool[] table;
  
  this(const ref NatSet ns)
  {
    count = ns.count;
    table = ns.table.dup;
  }
  
  this(int n)
  {
    count = 0;
    table = new bool[n];
  }
  
  this(int n, int[] arr)
  {
    this(n);
    insert(arr);
  }
  
  private void recount()
  {
    count = cast(int)table.count!(x => x);
  }
  
  auto contains(int n)
  {
    return table[n];
  }
  
  void insert(int n)
  {
    if (!table[n])
    {
      count += 1;
    }
    
    table[n] = true;
  }
  
  void insert(int[] arr)
  {
    foreach (x; arr)
    {
      table[x] = true;
    }
    
    recount;
  }
  
  void insert(NatSet ns)
  {
    foreach (i, x; ns.table)
    {
      table[i] |= x;
    }
    
    recount;
  }
}

auto isSubsetOf(const ref NatSet lhs, const ref NatSet rhs)
{
  foreach (x, y; lockstep(lhs.table, rhs.table))
  {
    if (x && !y)
    {
      return false;
    }
  }
  
  return true;
}

struct SolveState
{
  NatSet domain;
  int[] border;
  int[] steps;
}

auto solve(Board board)
{
  auto islands = board.findIslands;
  auto adjacency = board.islandsAdjacency(islands);
  
  auto level = [SolveState(NatSet(board.size, [0]), adjacency[0], [])];
  
  while (true)
  {
    SolveState[] next = [];
    
    foreach (state; level)
    {
      foreach (color; 0..board.colors)
      {
        auto domain = NatSet(state.domain);
        int[] border = [];
        
        foreach (friend; state.border)
        {
          if (islands[friend].color == color)
          {
            domain.insert(friend);
            
            foreach (poop; adjacency[friend])
            {
              if (!domain.contains(poop))
              {
                border ~= poop;
              }
            }
          }
          else
          {
            border ~= friend;
          }
        }
        
        if (state.domain.count == domain.count)
        {
          continue;
        }
        
        auto steps = state.steps ~ color;
        
        if (domain.count == islands.length)
        {
          return steps;
        }
        
        border = border.sort.uniq.array;
        
        auto child = SolveState(domain, border, steps);
        
        next ~= child;
      }
    }
    
    assert(next.length > 0);
    
    SolveState[] reduced = [];
    
    next.sort!"a.domain.count < b.domain.count";
    
    foreach (i; 0..next.length)
    {
      bool skip = false;
      
      foreach (j; i+1..next.length)
      {
        if (next[i].domain.isSubsetOf(next[j].domain))
        {
          skip = true;
          break;
        }
      }
      
      if (!skip)
      {
        reduced ~= next[i];
      }
    }
    
    level = reduced;
  }
}

void main(string[] args)
{
  auto p = args[1..$].map!(x => x.parse!int).array;
  
  auto board = Board(p[0], p[1], p[2]);
  //board.randomize;
  board.inflate("board.txt");
  
  board.toString.writeln;
  
  board.solve.writeln;
}