DEV Community

Cover image for Advent of Code 2019 Solution Megathread - Day 8: Space Image Format
Jon Bristow
Jon Bristow

Posted on • Edited on

Advent of Code 2019 Solution Megathread - Day 8: Space Image Format

Hooray! A reason to make cool gifs^TM.

Day 8 - The Problem

In order to finish booting a Elvish Mars Rover, they sent us a picture of the password. Credit where credit is due, the elves managed to at least encrypt the image file, so they have some security sensibility at least. Unfortunately, we have to build our own decryptor.

Humblebrag: I was out partying all day (playing board games!), and I want people to have a place to post. I'll update with a snappy summary when I finish the problem (probably tomorrow).

Part 1 was a simple counting problem. The main hurdle seemed to be correctly chunking the input data into the proper layers.

Part 2 also seemed strangely straightforward compared to Day 07, but maybe the list comprehension sugar of my chosen language simplified more than I realized.

Phew! I'm catching back up to where I wanted to be faster than I expected.

Ongoing Meta

Dev.to List of Leaderboards

If you were part of Ryan Palo's leaderboard last year, you're still a member of that!

If you want me to add your leaderboard code to this page, reply to one of these posts and/or send me a DM containing your code and any theming or notes you’d like me to add. (You can find your private leaderboard code on your "Private Leaderboard" page.)

I'll edit in any leaderboards that people want to post, along with any description for the kinds of people you want to have on it. (My leaderboard is being used as my office's leaderboard.) And if I get something wrong, please call me out or message me and I’ll fix it ASAP.

There's no limit to the number of leaderboards you can join, so there's no problem belonging to a "Beginner" and a language specific one if you want.

Neat Statistics

I'm planning on adding some statistics, but other than "what languages did we see yesterday" does anyone have any ideas?

Languages Seen On Day 07

  • javascript x 3
  • python x 2
  • c
  • clojure
  • haskell
  • kotlin
  • swift

Oldest comments (23)

Collapse
 
smh30 profile image
Stephanie Hope

This one was pretty straightforward for me, aside from a silly mistake at the end which was making my image a mess. I'm much happier about the puzzles now that I've given myself permission to skip any that involve the word 'intcode'!

$width = 25;
$height = 6;

$input = file_get_contents("input8.txt");
$num_layers = strlen($input) / ($width * $height);
$layer_size = strlen($input) / $num_layers;
$layers = str_split($input, $layer_size);

$min = get_fewest_zero($layers);
echo "Part 1: ".substr_count($layers[$min], "1")*substr_count($layers[$min], "2")."\n";

$image = decode_image($layers, $width, $height);
display_image($image);

function display_image($image){
    foreach ($image as $lines){
        foreach ($lines as $pixel){
            if ($pixel=="1"){
                echo "#";
            } else echo " ";
        }
        echo "\n";
    }
}

function decode_image($layers, $width, $height){
    $final_image = array_fill(0, $height, array_fill(0, $width, " "));
    foreach ($layers as $layer){
        $lines = str_split($layer, $width);
        foreach ($lines as $k=>$line){
            $chars = str_split($line);
            foreach ($chars as $m=>$char){
                if ($char != "2"){
                    if ($final_image[$k][$m] == " "){
                        $final_image[$k][$m] = $char;
                    }
                }
            }
        }
    }
    return $final_image;
}

function get_fewest_zero($layers){
$zeros_per_layer = array();
    foreach ($layers as $num=>$layer){
        $zeros_per_layer[$num] = substr_count($layer, "0");
    }
    return min(array_keys($zeros_per_layer, min($zeros_per_layer)));
}
Collapse
 
jbristow profile image
Jon Bristow

This was a nice and simple solution as long as you remembered how to pivot a list of lists. Kotlin's builtin minBy and count functions also simplify things a lot.

import arrow.core.firstOrNone
import arrow.core.getOrElse
import java.nio.file.Files
import java.nio.file.Paths

object Day08 {

    private const val FILENAME = "src/main/resources/day08.txt"
    val fileData: String = Files.readAllLines(Paths.get(FILENAME)).first()

    private fun List<String>.countAllEqualTo(match: Char): Int {
        return sumBy { row -> row.count { pixel -> pixel == match } }
    }

    fun part1(input: String): Int {
        val layerWithLeast0s = input.chunked(25).chunked(6).minBy { layer ->
            layer.countAllEqualTo('0')
        }.orEmpty()

        val num1s = layerWithLeast0s.countAllEqualTo('1')
        val num2s = layerWithLeast0s.countAllEqualTo('2')
        return num1s * num2s
    }

    private fun List<String>.findPixelColors() =
        (this[0].indices).map { i -> map { it[i] } }
            .map(this@Day08::findPixelColor)

    private fun findPixelColor(it: List<Char>) =
        it.dropWhile { c -> c == '2' }
            .firstOrNone()
            .getOrElse { '2' }

    private fun List<List<Char>>.renderImage() =
        joinToString("\n") { row ->
            row.joinToString("") { pixel ->
                when (pixel) {
                    '0' -> '.'
                    '1' -> '#'
                    '2' -> ' '
                    else -> '?'
                }.toString()
            }
        }

    fun part2(input: String) =
        input.chunked(25 * 6)
            .findPixelColors()
            .chunked(25)
            .renderImage()

}

fun main() {
    println(Day08.part1(Day08.fileData))
    println(Day08.part2(Day08.fileData))
}
Collapse
 
jbristow profile image
Jon Bristow • Edited

Here's a version of part1 that does everything in two passes of the list instead of four.

    fun part1(input: String) =
        input.chunked(25 * 6)
            .asSequence()
            .map { layer ->
                layer.groupBy { it }
                    .mapValues { (_, v) -> v.size }
            }.minBy { it.getOrDefault('0', 0) }?.let {
                it.getOrDefault('1', 0) * it.getOrDefault('2', 0)
            } ?: 0
Collapse
 
aspittel profile image
Ali Spittel • Edited

This felt like a big step down in difficulty from graph traversals and CPU madness!

def get_layer(x, y, data):
    start = 0
    while start < len(data):
        yield data[start:start + (x * y)]
        start += x * y


LAYER_HEIGHT = 6
LAYER_WIDTH = 25

with open('input.txt') as _file:
    data = _file.read()
    layers = [layer for layer in get_layer(LAYER_HEIGHT, LAYER_WIDTH, data)]
    least_zeroes = sorted(layers, key=lambda x: x.count("0"))[0]
    print(f'Part 1: {least_zeroes.count("1") * least_zeroes.count("2")}')

    stacked_pixels = ""
    for coordinate in range(LAYER_HEIGHT * LAYER_WIDTH):
        done = False
        starting_idx = 0
        while not done:
            if layers[starting_idx][coordinate] == "2":
                starting_idx += 1
            else:
                done = True
                stacked_pixels += " " if layers[starting_idx][coordinate] == "0" else "1"

    start = 0
    print("Part 2: ")
    while start < len(stacked_pixels):
        print(stacked_pixels[start:start + LAYER_WIDTH])
        start += 25
Collapse
 
rizzu26 profile image
Rizwan

Finally somthing which doesn't involve with opcode :P

Solution in swift

import Cocoa

let width = 25
let tall = 6

let digits = input.compactMap{ Int(String($0))}
let numberOfLayer = digits.count/(width * tall)

var startIndex = 0
let layers = (1...numberOfLayer).map{ layerNumber -> Array<Int> in
    let count = (width * tall) * layerNumber
    let layer = digits[startIndex..<count]
    startIndex = count
    return Array(layer)
}

func partTwo() {
    var result: [Int] = Array.init(repeating: 0, count: width * tall)

    let first = layers.first!

    for (index,value) in first.enumerated() {
        if value == 0 || value == 1 {
            result[index] = value
        }
        else {
            for layer in layers.dropFirst() {
                if (layer[index] == 0 || layer[index] == 1) {
                    result[index] = layer[index]
                    break
                }
            }
        }
    }

    startIndex = 0
    while true {
        let count = width + startIndex
        let value = result[startIndex..<count]
        startIndex = count
        print(value)
        if startIndex >= result.count {
            break
        }
    }
}

func partOne() {
    var low:[Int] = []
    var lowestCount: Int = Int.max
    for layer in layers {
        let dict = layer.reduce(into: [:]) { counts, number in
            counts[number, default: 0] += 1
        }
        let count = dict[0]
        if let count = count {
            if count < lowestCount {
                lowestCount = count
                low = layer
            }
        }
    }
    let dict = low.reduce(into: [:]) { counts, number in
        counts[number, default: 0] += 1
    }
    let result = (dict[1] ?? 0) * (dict[2] ?? 0)
    print("Result is : \(result)")
}

partOne()
partTwo()
Collapse
 
maxart2501 profile image
Massimo Artizzu • Edited

Aw yeah something simple again! Maybe even a little bit too simple? 😄

Anyway, in JavaScript:

const WIDTH = 25;
const HEIGHT = 6;
const layerSize = WIDTH * HEIGHT;

const layers = input.match(new RegExp(`[012]{${layerSize}}`, 'g'));

// Part One
function count(string, needle) {
  return string.split(needle).length - 1;
}
const digitCounts = [0, 1, 2].map(digit => layers.map(layer => count(layer, digit)));
const minZeros = Math.min(...digitCounts[0]);
const minZeroLayerIndex = digitCounts[0].indexOf(minZeros);

console.log(digitCounts[1][minZeroLayerIndex] * digitCounts[2][minZeroLayerIndex]);

// Part Two
const composed = Array.from({ length: layerSize }, (_, index) => {
  return layers.find(layer => layer[index] !== '2')[index];
});

// Pretty print the output 🤪
console.log(
  composed.join('')
    .replace(/0/g, ' ').replace(/1/g, '#')
    .match(new RegExp(`[012]{${WIDTH}}`, 'g'))
    .join('\n')
);

Check my repo for my input.

By the way, here's the language count for day 7 (it could be subject to change as it was a kind of complex challenge):

JavaScript × 3
Python × 2
C × 1
Clojure × 1
Java × 1
Swift × 1

Collapse
 
neilgall profile image
Neil Gall • Edited

Felt like a Kotlin day today. Pretty simple compared to yesterday.

import java.io.File

typealias Pixel = Char

const val BLACK: Char = '0'
const val WHITE: Char = '1'
const val TRANSPARENT: Char = '2'

data class Size(val width: Int, val height: Int) {
    val pixels: Int = width * height
}

data class Layer(val pixels: List<Pixel>)

data class SpaceImageFormat(val size: Size, val layers: List<Layer>)

fun String.decode(size: Size): SpaceImageFormat =
    SpaceImageFormat(
        size = size,
        layers = trim().asIterable().chunked(size.pixels).map(::Layer)
    )

fun transparentLayer(size: Size): Layer =
    Layer(List<Pixel>(size.pixels) { TRANSPARENT })

fun Layer.countPixels(pixel: Pixel): Int =
    pixels.count { p -> p == pixel }

fun Pair<Pixel, Pixel>.merge() =
    if (first == TRANSPARENT) second else first

fun Layer.merge(next: Layer): Layer =
    Layer(pixels.zip(next.pixels).map { p -> p.merge() })

fun Layer.render(size: Size): String =
    pixels.map { p -> if (p == WHITE) '#' else ' ' }
        .chunked(size.width)
        .map { row -> row.joinToString("") }
        .joinToString("\n")

fun part1(image: SpaceImageFormat): Int {
    val layerWithLeastZeros = image.layers.minBy { layer -> layer.countPixels('0') }!!
    val ones = layerWithLeastZeros.countPixels('1')
    val twos = layerWithLeastZeros.countPixels('2')
    return ones * twos
}

fun part2(image: SpaceImageFormat): String =
    image.layers.fold(transparentLayer(image.size), Layer::merge)
        .render(image.size)

fun main() {
    val image = File("input.txt").readText().decode(Size(25, 6))
    println("Part 1... ${part1(image)}")
    println("Part 2...\n${part2(image)}")
}
Collapse
 
mellen profile image
Matt Ellen-Tsivintzeli • Edited

SO much less difficult than yesterday. It took me a while to read the instructions correctly (need to sleep), but then I was away :D


function Pic(width, height, pixels)
{
    this.layers = [];
    let layerPixelCount = width * height;
    for(let pi = 0; pi < pixels.length; pi += layerPixelCount)
    {        
        let layerpixels = pixels.slice(pi, layerPixelCount+pi);
        let layer = new Layer(width, height, layerpixels)
        this.layers.push(layer);
    }
    this.width = width;
    this.height = height;
    this.pixels = pixels;
}

Pic.prototype.crc = function()
{
    let zerocount = Number.MAX_SAFE_INTEGER;
    let result = 0;
    for(let li = 0; li < this.layers.length; li++)
    {
        let layer = this.layers[li];
        let zc = layer.nCount(0);
        if(zc < zerocount)
        {
            zerocount = zc;
            let ones = layer.nCount(1);
            let twos = layer.nCount(2);
            result = ones*twos;
        }
    }
    return result;
};

Pic.prototype.render = function()
{
    let renderedLayer = new Layer(this.width, this.height, this.layers[0].pixels.slice(0, this.layers[0].pixels.length));
    for(let li = 1; li < this.layers.length; li++)
    {
        renderedLayer.replaceTransparent(this.layers[li].pixels);
    }

    console.log(renderedLayer.toString());
}

function Layer(width, height, pixels)
{
    this.width = width;
    this.height = height;
    this.pixels = pixels;
}

Layer.prototype.getRow = function(y)
{
    let start = y * this.width;
    let end = (y+1) * this.width;
    return this.pixels.slice(start, end);
};

Layer.prototype.nCount = function(n)
{
    return this.pixels.reduce((acc, p) => p === n ? (acc+1) : acc, 0);
}

Layer.prototype.replaceTransparent = function(newPixels)
{
    for(let pi = 0; pi < this.pixels.length; pi++)
    {
        if(this.pixels[pi] == 2)
        {
            this.pixels[pi] = newPixels[pi];
        }
    }
}

Layer.prototype.toString = function()
{
    let str = '';
    for(let y = 0; y < this.height; y++)
    {
        str += this.getRow(y).map(p =>
                                  {
                                      let result = '';
                                      switch(p)
                                      {
                                          case 0: result = '\u25af'; break;
                                          case 1: result = '\u25ae'; break;
                                      }
                                      return result;
                                  }).join('');
        str += '\n';
    }
    return str;
};

function day8()
{
    let pixels = document.querySelector('pre').innerHTML.trim().split('').map(s => parseInt(s));
    let pic = new Pic(25, 6, pixels);
    console.log('crc', pic.crc());
    pic.render();
}
Collapse
 
johnnyjayjay profile image
Johnny • Edited

Finally, a problem made for FP again.
First, a small utility function to partition an encoded image into its layers:

; Returns a sequence of the layers in an encoded image starting from the bottom layer. Not lazy
(defn layers [image dimensions]
  (reverse (partition (apply * dimensions) image)))

Part 1:

; For an encoded image (sequence of chars or string) and dimensions in the format [x y],
; finds the layer that has the least '0' pixels and calculates the value required to solve part 1.
(defn corruption-test [image dimensions]
  (let [least-zeros-layer (apply min-key #(% \0) (map frequencies (layers image dimensions)))]
    (* (least-zeros-layer \1) (least-zeros-layer \2))))

Part 2:

; Layers a pixel onto a base pixel. If the new pixel is transparent, returns the base pixel, else the new pixel.
(defn layer [base-pixel new-pixel]
  (if (= \2 new-pixel) base-pixel new-pixel))

; Returns a suited string representation for an encoded pixel.
(defn pixel-to-str [pixel]
  (if (= pixel \0) " " "█"))

; Decodes an encoded image (raw string or sequence of chars).
; Splits the input into layers, reduces them by layering them on top of each other,
; maps each pixel to an according string representation, partitions the result into rows
; (depicted by the provided dimensions) and returns them as a string joined with new lines.
(defn decode [image dimensions]
  (apply str (flatten (interpose "\n" (partition (dimensions 0) (map pixel-to-str (reduce (partial map layer) (layers image dimensions))))))))

(Full code: github.com/jkoenig134/AdventOfCode...)

Collapse
 
choroba profile image
E. Choroba

Using my native programming language, Perl:

Part 1

#! /usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

my $SIZE = 25 * 6;

open my $in, '<', shift or die $!;

my $min = $SIZE;
my $result;
while ($SIZE == read $in, my $layer, $SIZE) {
    my $zeros = $layer =~ tr/0//;
    if ($zeros < $min) {
        my $ones = $layer =~ tr/1//;
        my $twos = $layer =~ tr/2//;
        $result = $ones * $twos;
        $min = $zeros;
    }
}
say $result;

Part 2

#! /usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

my $WIDTH = 25;
my $SIZE = $WIDTH * 6;

open my $in, '<', shift or die $!;

my $result = '2' x $SIZE;
while ($SIZE == read $in, my $layer, $SIZE) {
    for my $i (0 .. $SIZE - 1) {
        my $front = substr $result, $i, 1;
        next if $front != 2;

        my $back  = substr $layer,  $i, 1;
        substr $result, $i, 1, $back;
    }
}
say $result =~ s/.{$WIDTH}\K/\n/gr =~ tr/01/ #/r;

Both solutions use the transliteration operator tr/// a lot. It's because it not only replaces characters one for one, but also returns the number of given characters in a string.

Collapse
 
nordfjord profile image
Einar Norðfjörð

Functional JS style today

const input = require('fs')
  .readFileSync(0)
  .toString()

const WIDTH = 25
const HEIGHT = 6
const layerSize = WIDTH * HEIGHT

const Id = x => ({
  value: x,
  map(fn) {
    return Id(fn(x))
  }
})

const splitEvery = n => list => {
  const result = []
  let idx = 0
  while (idx < list.length) {
    result.push(list.slice(idx, (idx += n)))
  }
  return result
}
const replace = regx => repl => str => str.replace(regx, repl)
const join = w => list => list.join(w)
const map = fn => list => list.map(fn)
const reduce = reducer => initial => list => list.reduce(reducer, initial)
const count = (x, str) => str.match(new RegExp(x, 'g')).length

function part1(input) {
  return Id(input)
    .map(splitEvery(layerSize))
    .map(
      map(x => ({
        0: count(0, x),
        1: count(1, x),
        2: count(2, x),
        x
      }))
    )
    .map(reduce((p, v) => (p[0] < v[0] ? p : v))({ 0: Infinity }))
    .map(l => l[1] * l[2]).value
}

function part2(input) {
  return Id(input)
    .map(splitEvery(layerSize))
    .map(layers =>
      Array.from({ length: layerSize }).map(
        (_, i) => layers.find(l => l[i] !== '2')[i]
      )
    )
    .map(join(''))
    .map(replace(/0/g)(' '))
    .map(replace(/1/g)('#'))
    .map(splitEvery(WIDTH))
    .map(join('\n')).value
}

console.log(part1(input))
console.log(part2(input))