DEV Community

Cover image for AoC Day 2: Inventory Management System

AoC Day 2: Inventory Management System

Ryan Palo on December 02, 2018

OK, the first day was awesome, I'm super excited about all of the solutions people posted. And I'm learning a lot! We've got a solid 20 people on...
Collapse
 
aspittel profile image
Ali Spittel

Python solutions!

part 1

from collections import Counter

with open('input.txt', 'r') as f:
    twice = 0
    thrice = 0
    for line in f:
        counts = Counter(line).values()
        if 2 in counts:
            twice += 1
        if 3 in counts:
            thrice += 1
    print(twice * thrice)

part 2 -- this one feels clunky to me.

with open('input.txt', 'r') as f:
    boxes = [box.strip() for box in f]

def check_differences(box1, box2):
    difference_idx = -1
    for idx, letter in enumerate(box1):
        if letter != box2[idx]:
            if difference_idx < 0:
                difference_idx = idx
            else:
                return False
    return box1[0:difference_idx] + box1[difference_idx+1:]


def find_one_difference(boxes):
    for idx, box in enumerate(boxes):
        for box2 in boxes[idx+1:]:
            diff = check_differences(box, box2)
            if diff:
                return diff

print(find_one_difference(boxes))
Collapse
 
easyaspython profile image
Dane Hillard

My part one ended up looking super similar!

#!/usr/bin/env python

from collections import Counter


if __name__ == '__main__':
    box_ids_with_two_duplicate_letters = 0
    box_ids_with_three_duplicate_letters = 0
    with open('1-input.txt') as box_file:
        for box_id in box_file:
            fingerprint = Counter(box_id)
            if 2 in fingerprint.values():
                box_ids_with_two_duplicate_letters += 1
            if 3 in fingerprint.values():
                box_ids_with_three_duplicate_letters += 1

    print(box_ids_with_two_duplicate_letters * box_ids_with_three_duplicate_letters)

I ended up using zip to help with the comparison if the strings in part 2:

#!/usr/bin/env python

from collections import Counter


def find_similar_box_ids(all_box_ids):
    for box_id_1 in all_box_ids:
        for box_id_2 in all_box_ids:
            # This is kind of a naive Levenshtein distance!
            letter_pairs = zip(box_id_1, box_id_2)
            duplicates = list(filter(lambda pair: pair[0] == pair[1], letter_pairs))
            if len(duplicates) == len(box_id_1) - 1:
                return ''.join(pair[0] for pair in duplicates)


if __name__ == '__main__':
    with open('2-input.txt') as box_file:
        all_box_ids = box_file.read().splitlines()
    print(find_similar_box_ids(all_box_ids))
Collapse
 
aspittel profile image
Ali Spittel

Nice! Definitely think that's the easiest way to do number 1. Zip also makes sense for the second, though not using it allowed me to do the early return!

Thread Thread
 
easyaspython profile image
Dane Hillard

True!

Collapse
 
rpalo profile image
Ryan Palo

10 points for the variable name β€œthrice!”

Collapse
 
jbristow profile image
Jon Bristow

As a lispy thinker, my brain wants to tell you to make those for loops into comprehensions, but my work brain is telling me that my coworkers would have me drawn and quartered to make your "find-one-difference" a one-liner!

Kudos on how neat this looks. I find python and ruby to be difficult to make look "clean" when I write it.

Collapse
 
rpalo profile image
Ryan Palo

Part 1

You can probably see my Python shining through as I implemented a custom Counter struct to help me out.

use std::collections::HashMap;

// Part 1

/// A histogram of the characters in a string.
struct Counter {
    letters: HashMap<char, usize>,
}

impl Counter {
    pub fn new(word: &str) -> Self {
        let mut letters = HashMap::new();
        for letter in word.chars() {
            let current_reference = letters.entry(letter).or_insert(0);
            *current_reference += 1;
        }
        Self { letters }
    }

    pub fn count_value(&self, number: usize) -> usize {
        self.letters.values().filter(|count| **count == number).count()
    }
}

/// Calculates a checksum for id strings.
/// 
/// The checksum is the number of id's with at least one set of exactly
/// two of a letter times the number of id's with at least one set of
/// exactly three of a letter.  If it has more than one
pub fn checksum(text: &str) -> usize {
    let mut twos = 0;
    let mut threes = 0;
    text.lines()
        .map(|id| Counter::new(id))
        .for_each(|counter| {
            if counter.count_value(2) != 0 {
                twos += 1;
            }
            if counter.count_value(3) != 0 {
                threes += 1;
            }
        });
    twos * threes
}

Part 2

Double for loop boooooooo! But it works and it's fast enough for now. I'm pretty happy with the mileage I got out of Iterators for this part.

// Part 2

/// Finds the letters that are shared between the two prototype fabric
/// box ids.
/// 
/// These ids are the only two that differ from each other by exactly
/// one letter.
pub fn prototype_ids_common_letters(text: &str) -> String {
    let ids: Vec<&str> = text.lines().collect();
    for (i, s1) in ids.iter().enumerate() {
        for s2 in ids.iter().skip(i) {
            if hamming_distance(s1, s2) == 1 {
                return common_letters(s1, s2);
            }
        }
    }
    String::new()
}

/// Calculates the "Hamming Distance" between two strings
/// 
/// Hamming distance is the number of characters who are different
/// between the two strings when the corresponding indices are compared
/// in each string
fn hamming_distance(s1: &str, s2: &str) -> usize {
    s1.chars().zip(s2.chars())
        .filter(|(c1, c2)| c1 != c2)
        .count()
}

/// Returns the letters that are the same (and in the same place)
/// between the two strings
fn common_letters(s1: &str, s2: &str) -> String {
    s1.chars().zip(s2.chars())
        .filter(|(c1, c2)| c1 == c2)
        .map(|(c1, _c2)| c1)
        .collect()
}
Collapse
 
ballpointcarrot profile image
Christopher Kruse

This is very well documented and clear, easy-to-read code. This also makes me want to jump into Rust again (I've only hobbied around with it here and there).

Collapse
 
rpalo profile image
Ryan Palo

Thanks! That really encouraging!

Collapse
 
shritesh profile image
Shritesh Bhattarai

I love how you've used enumerate and skip together in your nested for loop. I struggled to find a clean solution like this.

Collapse
 
rpalo profile image
Ryan Palo

Thanks! Yeah, I originally had a very manual nested for-loop set up, but after I got the tests passing, I decided to make an effort to do everything I could with iterators instead :) I've decided that the iterator module in Rust is where most of the magic that I'm missing from Python and Ruby lives.

Thread Thread
 
shritesh profile image
Shritesh Bhattarai

This was still bothering me, but I found the Itertools crate and the tuple_combinations method. Check out my updated solution in the thread. Itertools makes iterators even more powerful.

Collapse
 
carlymho profile image
Carly Ho 🌈

PHP

Ended up learning about a bunch of useful array functions like array_values, array_count_values and array_diff_assoc for this one!

Part 1:

$list = file_get_contents($argv[1]);
$boxes = explode("\n", trim($list));
$twos = 0;
$threes = 0;
foreach ($boxes as $box) {
    $letters = str_split($box);
    $values = array_values(array_count_values($letters));
    if (in_array(2, $values)) {
        $twos++;
    }
    if (in_array(3, $values)) {
        $threes++;
    }
}
echo $twos*$threes;
die(1);

Part 2:

<?php
$list = file_get_contents($argv[1]);
$boxes = explode("\n", rtrim($list));
foreach ($boxes as $i=>$box) {
    if ($i != count($boxes)-1) {
        for ($j = $i+1; $j < count($boxes); $j++) {
            $one = str_split($box);
            $two = str_split($boxes[$j]);
            if (count(array_diff_assoc($one, $two)) == 1) {
                echo join("", array_intersect_assoc($one, $two));
                die(1);
            }
        }
    }
}
Collapse
 
ikirker profile image
Ian Kirker

I’m trying to use a broader range of languages than I do usually, so I figured I’d try not to use one I’d already used before through the days. I use bash all the time, so I thought I’d get it out of the way early.

This was not one of my better decisions, but worked fine!

Part 1 uses regular expressions; I could have used an associative array like some other people in the thread, but for some reason I went here first. The sort function wasn’t necessary, but helped with debugging.

#!/bin/bash

twos=0
threes=0

function sort_str () {
    # Bubble Sort ^_^
    local sl="$1"
    changed=1
    while [[ "$changed" -eq 1 ]]; do
        changed=0
        for i in $(seq 1 $(( ${#sl} - 1 ))); do
            if [[ ${sl:$(( i - 1 )):1} > ${sl:$i:1} ]]; then
                echo "${sl:$(( i - 1 )):1} > ${sl:$i:1}" >>sort_progress
                changed=1
                if [[ $i -eq 1 ]]; then
                    sl=${sl:1:1}${sl:0:1}${sl:2}
                else
                    sl=${sl:0:$(( i - 1 ))}${sl:$i:1}${sl:$((i-1)):1}${sl:$i+1}
                fi
            fi
        done
    done
    echo $sl
}

re_2_str=""
re_3_str=""
for a in a b c d e f g h i j k l m n o p q r s t u v w x y z; do
    re_2_str+="^[^$a]*$a[^$a]*$a[^$a]*$|"
    re_3_str+="^[^$a]*$a[^$a]*$a[^$a]*$a[^$a]*$|"
done
re_2_str="(${re_2_str%|})"
re_3_str="(${re_3_str%|})"


while read line
do
    echo -n "$line: $(sort_str $line): "
    if [[ "$line" =~ $re_2_str ]]; then
        twos=$(( twos + 1 ))
        echo -n "2 "
    fi
    if [[ "$line" =~ $re_3_str ]]; then
        threes=$(( threes + 1 ))
        echo -n "3 "
    fi
    echo
done <2.1.input

echo "Twos: $twos"
echo "Threes: $threes"
echo "x: $(( twos * threes ))"

Part 2 uses the same double for loop lamented elsewhere, but it gets the job done.

#!/bin/bash

while read line
do
    while read comp_line
    do
        same_string=""
        ndiffs=0
        for i in $(seq 0 $(( ${#line} - 1 )) )
        do
            if [[ "${line:$i:1}" == "${comp_line:$i:1}" ]]; then
                same_string+=${line:$i:1}
            else
                ndiffs=$(( ndiffs+1 ))
            fi
        done
        if [[ "$ndiffs" -eq 1 ]]; then
            echo "$line: $comp_line: $same_string"
            exit
        fi
    done <2.2.input
done <2.2.input

Neither of these is what I’d call β€œfast”.

Collapse
 
rpalo profile image
Ryan Palo

Woah, nice! It's always really cool to see bigger things done with Bash :)

P.S. Depending on your Bash version, you can save yourself some typing with {a..z}.

Collapse
 
ikirker profile image
Ian Kirker

Oh yes, good call, I missed that compaction.

Collapse
 
trueneu profile image
Pavel Gurkov • Edited

Clojure (inefficient part 2)

Part 1:

(->>
    (utils/read-file (str utils/resources-path "day2.txt"))
    (map frequencies)
    (map vals)
    (map (juxt (fn [coll] (some #(= 2 %) coll))
               (fn [coll] (some #(= 3 %) coll))))
    (reduce
      (fn [acc [_2 _3]]
        (-> acc
            (update :2 #(+ (if _2 1 0) %))
            (update :3 #(+ (if _3 1 0) %))))
      {:2 0 :3 0})
    ((fn [coll] (* (:2 coll) (:3 coll)))))

Part 2:

(->>
    (utils/read-file (str utils/resources-path "day2.txt"))
    (map #(map-indexed (fn [idx itm] [idx itm]) %))
    (map set)
    ((fn [coll] (combinatorics/combinations coll 2)))
    (map (fn [[f s]] {:diff (clj-set/difference f s) :orig [f s]}))
    (filter #(= (count (:diff %)) 1))
    ((fn [[{:keys [diff orig]}]]
       (apply str (map second (sort-by first (clj-set/difference (first orig) diff)))))))
Collapse
 
ballpointcarrot profile image
Christopher Kruse

I like the threaded use of update here in part 1 - my method used a transient map and returned a persistent copy at the end:

(ns aoc.aoc2)

(defn reduce-twos-threes
  "check the given frequency map n for twos or threes matches, and update
   the memo map to indicate if the string has a match. Used for a reducer."
  [memo n]
  (let [t-memo (transient memo)]
    (if (some (fn [[k v]] (= v 2)) n) 
      (assoc! t-memo :twos (inc (:twos t-memo))))
    (if (some (fn [[k v]] (= v 3)) n) 
      (assoc! t-memo :threes (inc (:threes t-memo))))
    (persistent! t-memo)))

(defn checksum [input]
  (let [sum-maps (map frequencies input)
        twos-threes (reduce reduce-twos-threes {:twos 0 :threes 0} sum-maps)]
    (* (:twos twos-threes) (:threes twos-threes))))
Collapse
 
trueneu profile image
Pavel Gurkov

Nice one. Is definitely faster than mine.

Collapse
 
andersonjoseph profile image
Anderson. J • Edited

Javascript lazy solution
I don't have much time to solve the challenges :( So I'm just trying to get the stars.

part 1


let twoAppears = 0;
let threeAppears = 0;

for(ID of input) {
  const letters = new Set(ID.split(''));
  let twoAppearing = false;
  let threeAppearing = false;

  for(letter of letters) {
    const length = ID.match(new RegExp(letter, 'g')).length;

    switch (length) {
      case 2:
        if(!twoAppearing) {
          twoAppears++;
          twoAppearing = true;
        }
        break;
      case 3:
      if(!threeAppearing) {
        threeAppears++;
        threeAppearing = true;
      }
      break;
    }
  }
}

console.log(twoAppears * threeAppears);

Part 2


function getCommonLetters(string1, string2) {
  let commonLetters = [];

  for(let i=0; i<string1.length; i++) {
    if(string1[i] === string2[i] && string1[i] !== '\r') {
      commonLetters.push(string1[i]);
    }
  }
  return commonLetters;
}

let mostOcurrencies = [0, 0];
for(let i=0; i<input.length; i+=1) {
  for(let j=i+1; j<input.length; j+=1) {
    commonLetters = getCommonLetters(input[i], input[j]);
    if(commonLetters.length >= 1 && commonLetters.length > mostOcurrencies[0]) {
      mostOcurrencies[0] = commonLetters.length;
      mostOcurrencies[1] = commonLetters;
    }
  }
}

console.log(mostOcurrencies[1].join().replace(/,/g,''))
Collapse
 
lindakatcodes profile image
Linda Thompson • Edited

Thanks for hosting the private leaderboard! Never been on a leaderboard before lol so that'll be fun. :)

I am curious - how is everyone posting their code? Is there a code tag on here, like there is on Slack? Is everyone sharing screenshots? I haven't posted a whole lot on here yet, so I'm not sure of the best way to share code.

I'm using JS this year, so here's my day 2 solutions: not the prettiest / most succinct, but they work!

Part 1:

let countDouble = 0;
let countTriple = 0;

for (let i = 0; i < input.length; i++) {
    let label = input[i].split('');
    let letterCount = {};

    label.reduce((letters, letter) => {
        if (letter in letterCount) {
            letterCount[letter]++;
        } else {
            letterCount[letter] = 1;
        }
        return letters;
    }, 0);

    let checkCounts = Object.values(letterCount);
    if (checkCounts.includes(2)) {
        countDouble++;
    }
    if (checkCounts.includes(3)) {
        countTriple++;
    }
}

let checksum = countDouble * countTriple;
console.log(checksum);

Part 2:

for (let i = 0; i < input.length; i++) {
    let root = input[i];

    for (let j = i+1; j < input.length; j++) {
        let currName = input[j];

        if (compareNames(root, currName)) {
            return;
        }
    }
}

function compareNames(first, second) {
    let differences = 0;
    let locations = [];
    for (let k = 0; k < first.length; k++) {
        if (first[k] === second[k]) {
            continue;
        } else {
            differences++;
            locations.push(k);
        }
    }

    if (differences === 1) {
        const letterArray = first.split('');
        const removeLetter = letterArray.splice(locations[0], 1);
        const matchingLetters = letterArray.join('');
        console.log(`same letters: ${matchingLetters}`);
        return true;
    } else {
        return false;
    }

    differences = 0;
    locations = [];
}
Collapse
 
neilgall profile image
Neil Gall

There's an enhanced form of markdown for code blocks: triple backticks for start and end, and if you immediately follow the opening backticks with the language you get syntax highlighting. Difficult to show raw markdown in markdown unfortunately.

Collapse
 
lindakatcodes profile image
Linda Thompson

Excellent, thank you! Much better than screenshots. :)

Collapse
 
neilgall profile image
Neil Gall

Enjoyed this one. My Kotlin solution:

package adventofcode2018.day2

import java.io.File

fun repeatCounts(s: String): Set<Int> =
    s.groupBy { it }.values.map { it.size }.toSet()

fun difference(s1: String, s2: String): Int = 
    s1.zip(s2, { x, y -> if (x == y) 0 else 1 }).sum()

fun common(s1: String, s2: String): String =
    s1.zip(s2, { x, y -> if (x == y) x.toString() else "" }).joinToString(separator="")

fun <T> pairs(xs: Collection<T>): List<Pair<T, T>> = when {
    xs.isEmpty() -> listOf() 
    else -> {
        val head = xs.first()
        val tail = xs.drop(1)
        (tail.map { head to it }) + pairs(tail)
    }
}

fun main(args: Array<String>) {
    val file = if (args.isEmpty()) "input.txt" else args[0]
    val input = File(file).readLines().map(String::trim)

    // Part 1
    val counts = input.map(::repeatCounts)
    val numPairs = counts.filter { s -> s.contains(2) }.size
    val numTriples = counts.filter { s -> s.contains(3) }.size
    println("Part 1 checksum: ${numPairs * numTriples}")

    // Part 2
    val differentByOnePairs = pairs(input).filter { (x, y) -> difference(x, y) == 1 }
    println(differentByOnePairs.map { (x, y) -> common(x, y) })
}
Collapse
 
jbristow profile image
Jon Bristow

Were you also annoyed that Kotlin has .groupBy but not .frequencies?

Have you thought about looking into Sequence? You could make your pairs function lazily? Using List means you're materializing the entirety of your double for-loop right away.

Collapse
 
neilgall profile image
Neil Gall • Edited

The lack of frequencies didn't bother me - it's easy to add. And yes, I've been thinking for the rest of the day that I should use lazy sequences. In this case the execution time remains O(NΒ²) but as you say the memory footprint becomes more like constant. Definitely a good practice when you can't find a better algorithm.

Collapse
 
ryanwilldev profile image
Ryan Will

A little late, to the party. I tried really hard to think of a solution to part 2 that only involved iterating the list once, but no luck. Here is my solution in Elixir.

Part one:

 def part_1() do
  %{"3" => total_3, "2" => total_2} =
    AOC.read_input("2018/day_2"      
    |> Enum.reduce(%{"2" => 0, "3" => 0}, fn id, acc ->
        counts =
          String.split(id, "", trim: true)
          |> Enum.reduce(%{}, &Map.update(&2, &1, 1, fn val -> val + 1 end))
          |> Enum.reduce(%{"2" => 0, "3" => 0}, fn
            {_, 2}, count ->
              Map.update!(count, "2", fn _ -> 1 end)

            {_, 3}, count ->
              Map.update!(count, "3", fn _ -> 1 end)

            _, count ->
              count
          end)

        %{acc | "2" => acc["2"] + counts["2"], "3" => acc["3"] + counts["3"]}
      end)

    total_2 * total_3
end

Part 2:

def part_2() do
  AOC.read_input("2018/day_2")
  |> traverse_list()
end

def traverse_list([head | tail]) do
  traverse_list(head, tail, tail)
end

def traverse_list(_, [], [head | tail]) do
  traverse_list(head, tail, tail)
end

def traverse_list(compare, [current | tail] = remaining, rest_list)
    when length(remaining) > 0 do
  case compare_strings(compare, current) do
    :notfound ->
      traverse_list(compare, tail, rest_list)

    common_chars ->
      common_chars
  end
end

def compare_strings(s1, s2) do
  s1 = String.split(s1, "", trim: true)
  s2 = String.split(s2, "", trim: true)
  zipped = Enum.zip(s1, s2)

  case Enum.reduce(zipped, %{misses: 0, common: ""}, fn
    {s, s}, acc ->
      %{acc | common: acc.common <> s}

     _, acc ->
       %{acc | misses: acc.misses + 1}
  end) do
    %{misses: 1, common: common} ->
      common

    _ ->
      :notfound
  end
end
Collapse
 
philnash profile image
Phil Nash • Edited

So this was a pain. I also ended up with a double loop (O(n2)) and couldn't think of anything better.

One thing I discovered during part two was that Crystal has Levenshtein distance as part of the standard library. It might have been a bit heavy going for what I needed, but it did the trick!

require "levenshtein"

class FabricBox
  getter id

  @letter_map : Hash(String, Int32)

  def initialize(@id : String)
    @letter_map = @id.split("").reduce(Hash(String, Int32).new(default_value: 0)) do |acc, letter|
      acc[letter] = acc[letter] + 1
      acc
    end
  end

  def has_exactly?(letters : Int32) : Bool
    @letter_map.values.any? { |count| count == letters }
  end
end

class FabricBoxCollection
  getter boxes
  @boxes : Array(FabricBox)

  def initialize(ids : Array(String))
    @boxes = ids.map { |id| FabricBox.new(id) }
  end

  def checksum
    @boxes.count { |box| box.has_exactly? 2 } * @boxes.count { |box| box.has_exactly? 3 }
  end

  def find_close_id
    @boxes.each do |box_i|
      @boxes.each do |box_j|
        if Levenshtein.distance(box_i.id, box_j.id) == 1
          characters_i = box_i.id.split("")
          characters_j = box_j.id.split("")
          char_pairs = characters_i.zip(characters_j)
          return char_pairs.reduce("") do |acc, (char_i, char_j)|
            acc += char_i if char_i == char_j
            acc
          end
        end
      end
    end
  end
end

puts "--- Day 2: Inventory Management System ---"
input = File.read_lines("./02/input.txt")
collection = FabricBoxCollection.new(input)
puts "Checksum: #{collection.checksum}"
puts "Common letters: #{collection.find_close_id}"

Bring on day 3!

Collapse
 
rpalo profile image
Ryan Palo

High five for a beefy standard library! That’s awesome 😎

Collapse
 
lschultebraucks profile image
Lasse Schultebraucks
from collections import defaultdict
from difflib import SequenceMatcher

from operator import itemgetter


def similar(a, b):
    return SequenceMatcher(None, a, b).ratio()


def main():
    part_one()
    part_two()


def part_one():
    with open('input.txt', 'r') as input_file:
        lines = [line for line in input_file]

    word_twice_count = 0
    word_three_times_count = 0

    for line in lines:
        word_count_dic = defaultdict(int)
        has_char_twice = False
        has_char_three_times = False
        for char in line:
            word_count_dic[char] += 1
        for key in word_count_dic.keys():
            if word_count_dic[key] == 2:
                has_char_twice = True
            elif word_count_dic[key] == 3:
                has_char_three_times = True

        if has_char_twice:
            word_twice_count += 1
        if has_char_three_times:
            word_three_times_count += 1

    checksum = word_twice_count * word_three_times_count

    print 'checksum is ' + str(checksum)


def part_two():
    with open('input.txt', 'r') as input_file:
        lines = [line for line in input_file]

    similarity_strings = [(line, comparing_line, similar(line, comparing_line))
                          for line in lines for comparing_line in lines
                          if line is not comparing_line]

    most_similair_strings = max(similarity_strings, key=itemgetter(2))

    result_word = ''.join([char_word_one
                           for char_word_one, char_word_two in zip(most_similair_strings[0], most_similair_strings[1])
                           if char_word_one == char_word_two])

    print result_word


if __name__ == '__main__':
    main()

My solution with Python, not sure about the second part, not the most fastest solution I think regarding of performance.

Collapse
 
rpalo profile image
Ryan Palo

Nice! Don’t forget about collections.Counter for part 1! I didn’t know about difflib though. Cool!

Collapse
 
lschultebraucks profile image
Lasse Schultebraucks

Thanks for the hint! Will do that later!

Thread Thread
 
lschultebraucks profile image
Lasse Schultebraucks
def part_one():
    word_twice_count = 0
    word_three_times_count = 0

    with open('input.txt', 'r') as input_file:
        for line in input_file:
            line_counter = Counter(line)
            if 2 in line_counter.values():
                word_twice_count += 1
            if 3 in line_counter.values():
                word_three_times_count += 1

    checksum = word_twice_count * word_three_times_count

    print 'checksum is ' + str(checksum)

My edited solution for part one with collections Counter

Collapse
 
roeekl profile image
roeekl

Part 1: C# + LINQ = one-liner

return (input.Count(id => id.GroupBy(c => c).Any(group => group.Count() == 2)) *
                   input.Count(id => id.GroupBy(c => c).Any(group => group.Count() == 3)))

Part 2

for (int i = 0; i < input[0].Length; i++)
            {
                var commonIds = input.Select(id => id.Remove(i, 1)).GroupBy(id => id).FirstOrDefault(group => group.Count() > 1);
                if (commonIds != null)
                {
                    return commonIds.First();
                }
            }
Collapse
 
r0f1 profile image
Florian Rohrer

Part 1

from collections import Counter

with open("input.txt") as f:
    ids = [Counter(l.strip()) for l in f]

count2 = 0
count3 = 0
for c in ids:
    if 2 in c.values(): count2 += 1
    if 3 in c.values(): count3 += 1

print(count2 * count3)

Part 2

from editdistance import eval as dist
from itertools import product

with open("input.txt") as f:
    ids = [l.strip() for l in f]

for a, b in product(ids, ids):
    d = dist(a, b)
    if d == 1:
        print(a, b)
        break

And from the output, I just copied and pasted the necessary characters that matched. That was faster than comming up with a custom method to do so.

Collapse
 
rpalo profile image
Ryan Palo

Nice! Did you implement editdistance yourself, or is that an external library?

Collapse
 
r0f1 profile image
Florian Rohrer

It is external. I found it via a quick google search. The edit distance measures how many operations - insertion, deletion or substitution - it takes to get from one string to the other. Since all the strings in the puzzle input have the same length, insertion and deletion do not come into play and it works out perfectly.

Thread Thread
 
rpalo profile image
Ryan Palo

Ok that’s cool, just checking. Neat!

Collapse
 
ganderzz profile image
Dylan Paulus • Edited

Terrible C++ solution for part 1 !

#include <iostream>
#include <fstream>
#include <list>
#include <map>

std::list<std::string> read_file(std::string filename)
{
  auto lines = std::list<std::string>();
  std::ifstream file(filename);

  for (std::string line; std::getline(file, line);)
  {
    lines.push_back(line);
  }

  file.close();

  return lines;
}

class WordCounter
{
public:
  static std::map<char, int> CalculateLine(const std::string line)
  {
    auto map = std::map<char, int>();

    if (line.length() == 0)
    {
      return map;
    }

    for (int i = 0; i < line.length(); i++)
    {
      std::map<char, int>::iterator it = map.find(line[i]);

      if (it != map.end())
      {
        it->second = it->second + 1;
      }
      else
      {
        map.insert(std::pair<char, int>(line[i], 1));
      }
    }

    return map;
  }

  static const std::pair<int, int> Sum(std::map<char, int> map)
  {
    int twos = 0;
    int threes = 0;

    for (auto line = map.begin(); line != map.end(); line++)
    {
      if (line->second == 2)
      {
        twos = 1;
      }
      else if (line->second == 3)
      {
        threes = 1;
      }
    }

    return std::pair<int, int>(twos, threes);
  }
};

int main()
{
  auto input = read_file("./input.txt");
  int two = 0;
  int three = 0;

  for (auto i = input.begin(); i != input.end(); i++)
  {
    auto letterMap = WordCounter::CalculateLine(*i);
    auto calculated = WordCounter::Sum(letterMap);

    two += calculated.first;
    three += calculated.second;
  }

  std::cout << two * three << std::endl;

  return 0;
}
Collapse
 
rpalo profile image
Ryan Palo

Terrible is better than never finished! And this looks pretty good to me, not knowing C++ if that makes you feel better πŸ˜„

Collapse
 
quoll profile image
Paula Gearon

I did my solutions at midnight last night, but I was surviving on very little sleep at the time, so the resulting code was below standard. I tried again this morning and felt better about it.

For anyone reading this, I'm still using the simple lines function from Day 1 which reads a file into a sequence of strings.

Part 1

This was my solution last night:

(defn nums [s]
  (let [cs (->> s
                (group-by identity)
                vals
                (map count))]
    [(some #(= 2 %) cs) (some #(= 3 %) cs)]))

(defn star
  [input-file]
  (let [tt (->> (lines input-file)
                (map nums))]
    (* (count (filter first tt)) (count (filter second tt)))))

This is embarrassing code. I totally forgot about the frequencies function, which is why I used group-by followed by count. But the 2 filter operations in the final calculation meant that the results of the map get processed twice.

My morning attempt fixed these:

(defn counts [[two-count three-count] s]
  (let [cs (-> s frequencies vals)]
    [(if (some #(= 2 %) cs) (inc two-count) two-count)
     (if (some #(= 3 %) cs) (inc three-count) three-count)]))

(defn star
  [input-file]
  (let [[two-count three-count] (->> (lines input-file) (reduce counts [0 0]))]
    (* two-count three-count)))

This time I accumulated the 2/3 count values while processing the list, so it only goes through it once.

Part 2

Since each element needs to be compared to every other element, I can't see any way around the O(n2 ) complexity. Of course, each element should only be compared to the ones after it, so as to avoid comparing each one twice (since comparing A/B has the same result as comparing B/A).

When doing list processing, the only ways I know to refer to everything following an element are by using indexes (yuck) or with a loop. Unfortunately, I got fixated on the loop construct, and nested it:

(defn close [a b]
  (let [sim (filter identity (map #(when (= %1 %2) %1) a b))]
    (when (= (count sim) (dec (count a))) sim)))

(defn cmp-close [ll]
  (loop [[f & fr] ll]
    (when (seq fr)
      (or
        (loop [[s & sr] fr]
          (when s
            (or (close f s)
                (recur sr))))
        (recur fr)))))

(defn star2
  [input-file]
  (let [ll (lines input-file)]
    (apply str (cmp-close ll))))

The other way you can tell that I wrote this on very little sleep was the use of ridiculously terse var names.

On reflection this morning, I realized that the inner loop should have been a some operation. This does a predicate test and terminates processing early, which is what I was doing manually with the inner loop.

Also, my close function has several issues. First is the name! I was thinking of "A is close to B", but when I saw it again this morning I realized that it's the same function name for closing a resource. Something else that bothers me is that it processes the entirety of each string before returning, when a false result can usuall terminate early. Finally, a minor issue is that the anonymous function #(when (= %1 %2) %1) would be more idiomatic to use a singleton set on %1 to compare to %2.

(defn nearly= [left right]
  (let [same (filter identity (map #(#{%1} %2) left right))]
    (when (= (count same) (dec (count left))) (apply str same))))

(defn compare-lines [ll]
  (loop [[line & xlines] ll]
    (when (seq line)
      (or
       (some (partial nearly= line) xlines)
       (recur xlines)))))

(defn star2
  [input-file]
  (compare-lines (lines input-file)))

The nearly= function now returns a string, rather than the array of characters, but hasn't changed much. I was still unsatisfied with it not terminating the test early, so I rewrote it to be faster. However, the resulting code lacks any kind of elegance, and I'm not convinced that it's worth the effort:

(defn nearly= [left right]
  (loop [[l & xl] left [r & xr] right diffs 0]
    (if (nil? l)
      (apply str (filter identity (map #(#{%1} %2) left right)))
      (if (not= l r)
        (when (zero? diffs)
          (recur xl xr 1))
        (recur xl xr diffs)))))

Hopefully I'll get some sleep before attempting day 3. 😊

Collapse
 
pabloxcl profile image
Pablo Olmos de Aguilera Corradini

A bit late to the party, here are my solutions on Ruby:

Part 1:

twos = 0
threes = 0

File.open('day-02_input.txt').each_line do |line|
  counter = {}

  line.each_char do |char|
    next if line.count(char) <= 1

    counter[char] = line.count(char)
  end

  twos += 1 if counter.value? 2
  threes += 1 if counter.value? 3
end

puts twos * threes

Part 2:

boxes = []

File.open('day-02_input.txt').each_line { |box| boxes << box.chomp! }

def find_distance pair, distance = 0
  pair[0].each_char.with_index do |char, index|
    distance += 1 if char != pair[1][index]
  end
  distance
end

boxes.combination(2).each do |pair|
  next unless find_distance(pair) == 1

  str = ''

  pair[0].chars.each_with_index do |_, i|
    str << pair[0][i] if pair[0][i] == pair[1][i]
  end
  puts str
end

I learnt about the combination method :)

Collapse
 
themindfuldev profile image
Tiago Romero • Edited

My solution in JavaScript / Node 11, using the readline interface:

readLines.js

const fs = require('fs');
const readline = require('readline');

const readLines = (file, onLine) => {
    const reader = readline.createInterface({
        input: fs.createReadStream(file),
        crlfDelay: Infinity
    });

    reader.on('line', onLine);

    return new Promise(resolve => reader.on('close', resolve));
};

const readFile = async file => {
    const lines = [];
    await readLines(file, line => lines.push(line));  
    return lines;
}

module.exports = {
    readLines,
    readFile
};

02a.js

const { readFile } = require('./readLines');

(async () => {
    const lines = await readFile('02-input.txt');

    let twoLettersCount = 0;
    let threeLettersCount = 0;
    for (let line of lines) {
        const frequencyMap = {};
        for (const char of line.split('')) {
            frequencyMap[char] = (frequencyMap[char] || 0) + 1;
        }
        const hasTwoLetters = Object.values(frequencyMap).some(frequency => frequency === 2);
        const hasThreeLetters = Object.values(frequencyMap).some(frequency => frequency === 3);

        twoLettersCount += +hasTwoLetters;
        threeLettersCount += +hasThreeLetters;
    };

    const checksum = twoLettersCount * threeLettersCount;
    console.log(`The checksum is ${checksum}`);
})();

02b.js

const { readFile } = require('./readLines');

// Compares two strings to see if they differ by one char and which one 
function compare(string1, string2) {
    const length = string1.length;
    let differentChars = 0;
    let differIndex;
    for (let i = 0; i < length; i++) {
        if (string1.charAt(i) !== string2.charAt(i)) {
            differentChars++;            
            differIndex = differentChars === 1 ? i : undefined;
        }
    }

    return {
        differByOneChar: differentChars === 1,
        differIndex
    };
}

// Compare each strings to every other string 
// and get the common letters in case the differByOneChar is true
function getCommonLetters(ids) {
    const idsCount = ids.length;
    for (let i = 0; i < idsCount; i++) {
        const id = ids[i];
        for (let j = i + 1; j < idsCount; j++) {
            const { differByOneChar, differIndex } = compare(id, ids[j]);
            if (differByOneChar) {
                return id.slice(0, differIndex) + id.slice(differIndex + 1);
            }
        }
    }
}

(async () => {
    const lines = await readFile('02-input.txt');

    const commonLetters = getCommonLetters(lines);
    console.log(`The common letters are ${commonLetters}`);
})();
Collapse
 
thejessleigh profile image
jess unrein

My solutions for part 1

Python

def checksum(inputs):
    ids = inputs.read().splitlines()
    twos = 0
    threes = 0
    for row in ids:
        seen = {}
        for letter in row:
            seen[letter] = seen.get(letter, 0) + 1
        if 2 in seen.values():
            twos += 1
        if 3 in seen.values():
            threes += 1

    return twos * threes

checksum(open('input.txt', 'r'))

Go

package main

import (
    "strings"
    "io/ioutil"
)

func containsVal(m map[string]int, v int)(bool) {
    for _, x := range m{
        if x == v {
            return true
        }
    }
    return false
}

func checksum(x string)(int) {
    two := 0
    three := 0
    ids := strings.Split(x, "\n")
    for i := 0; i < len(ids); i++ {
        counter := make( map[string]int )
        s := strings.Split(ids[i], "")
        for _, l := range s {
            counter[l]++
        }
        if containsVal(counter, 2) {
            two++
        }
        if containsVal(counter, 3) {
            three++
        }
    }
    return two * three
}

func main() {
    f, err := ioutil.ReadFile("input.txt")
    if err != nil {
        panic(err)
    }
    checksum(string(f))
}

Benchmark difference

`python3 2_1.py` 100 times
real    0m4.744s
user    0m3.129s
sys 0m0.967s

`go run 2_1.go` 100 times
real    0m37.649s
user    0m28.807s
sys 0m14.105s

go build

`./2_1` 100 times
real    0m0.709s
user    0m0.327s
sys 0m0.280s
Collapse
 
yordiverkroost profile image
Yordi Verkroost

My solution to day 2, in Elixir. The double for-loop in part 2 is certainly not optimal, but it works. The available time was short today. :)

Part one:

defmodule AoC.DayTwo.PartOne do
  alias AoC.DayTwo.Common

  def main() do
    "lib/day2/input.txt"
    |> Common.read_input()
    |> Enum.map(&count_characters/1)
    |> Enum.map(&Map.values/1)
    |> get_multiplicants()
    |> Enum.reduce(1, fn x, acc -> acc * x end)
  end

  defp count_characters(box_id) do
    box_id
    |> String.graphemes()
    |> Enum.reduce(%{}, fn x, acc -> Map.put(acc, x, (acc[x] || 0) + 1) end)
  end

  defp get_multiplicants(occurrences_lists) do
    twos = get_multiplicant(occurrences_lists, 2)
    threes = get_multiplicant(occurrences_lists, 3)
    [twos, threes]
  end

  defp get_multiplicant(occurrences_lists, count) do
    multiplicant =
      Enum.reduce(occurrences_lists, 0, fn x, acc ->
        if Enum.member?(x, count), do: acc + 1, else: acc
      end)
  end
end

Part two:

defmodule AoC.DayTwo.PartTwo do
  alias AoC.DayTwo.Common

  def main() do
    "lib/day2/input.txt"
    |> Common.read_input()
    |> find_correct_boxes()

    receive do
      {x, y} -> get_common_characters(x, y)
    end
  end

  defp find_correct_boxes(box_ids) do
    for x <- box_ids do
      for y <- box_ids do
        zipped = Enum.zip(String.graphemes(x), String.graphemes(y))

        differences =
          Enum.reduce(zipped, 0, fn {z1, z2}, acc -> if z1 == z2, do: acc, else: acc + 1 end)

        if(differences == 1) do
          send(self(), {x, y})
        end
      end
    end
  end

  defp get_common_characters(x, y) do
    Enum.zip(String.graphemes(x), String.graphemes(y))
    |> Enum.reduce("", fn {x1, x2}, acc -> if x1 == x2, do: acc <> x1, else: acc end)
  end
end

Common:

defmodule AoC.DayTwo.Common do
  def read_input(path) do
    path
    |> File.stream!()
    |> Stream.map(&String.trim_trailing/1)
    |> Enum.to_list()
  end
end
Collapse
 
jnschrag profile image
Jacque Schrag

Giving it a go in good ol' JavaScript.

Part 1
First creates an Object to track the # of times a letter appears in a string. That gets converted to a Set to filter out any duplicate values to account for situations where 2 or more letters appeared twice (as the string only gets counted once for the check sum).

const input = text.split("\n").map(d => d.split(''))
let x2 = 0
let x3 = 0

input.forEach(line => {
  let counted = [...new Set(Object.values(line.reduce((allLetters, letter) => {
    if (letter in allLetters) {
      allLetters[letter]++
    } else {
      allLetters[letter] = 1
    }
    return allLetters
  }, {})))]
  x2 += counted.filter(d => d === 2).length
  x3 += counted.filter(d => d === 3).length
})

let checksum = x2 * x3

Part 2
I struggled with this one, so I'm sure there's a cleaner/more efficient way to do this. This takes each line of the input and compares it to the other lines, checking the characters and tracking the # of differences and the position of the last accounted for difference. The loops are set to break when a result with only 1 difference is found to prevent unnecessary looping.

const input = text.split("\n")

let check = true

for (let line of input) {
  const comparisonLines = input.filter(d => d !== line)

  for (let entry of comparisonLines) {
    let result = findDifferences(line, entry)
    if (result.numDiff === 1) {
      let string = line.slice(0, result.posDiff) + line.slice(result.posDiff + 1)
      console.log(string)
      check = false
      break
    }
  }
  if (!check) break
}

function findDifferences(string1, string2) {
  if (string1 === string2) return

  const length = Math.max(string1.length, string2.length)
  let numDiff = 0
  let posDiff = 0

  for (let i = 0; i <= length; i++) {
    if (string1[i] !== string2[i]) {
      numDiff++
      posDiff = i
    }
  }
  return {numDiff: numDiff, posDiff: posDiff}
}
Collapse
 
harri_etty profile image
Harriet

Part 1 in Ruby:

have_two_letters = 0
have_three_letters = 0

File.open('./input.txt', 'r').each do |str|
  freq = str.split('').group_by(&:itself).map {|k,v| [v.size, k] }.to_h
  have_two_letters += 1 if freq[2]
  have_three_letters += 1 if freq[3]
end

puts have_three_letters * have_two_letters

I enjoyed playing around with the one liner

str.split('').group_by(&:itself).map {|k,v| [v.size, k] }.to_h

which produces a frequency chart something like this:

{1=>"j", 4=>"f", 2=>"s"}

i.e. there are exactly 2 occurrences of the letter s in str

Part 2 in Ruby

lines = File.read('./input.txt').split("\n");

def get_shared_letters(str1, str2)
  differences = 0
  same_letters = nil
  str1.split('').each_with_index do |letter, i|
    if letter != str2[i]
      differences += 1 
      same_letters = str1.dup
      same_letters.slice!(i)
    end
  end

  differences === 1 ? same_letters : nil
end

lines.each_with_index do |str1, i1|
  lines.each_with_index do |str2, i2|
    next if i2 === i1

    shared = get_shared_letters str1, str2
    puts shared if shared
  end
end

Not happy about the double loop through the input file... but also can't think of a way to avoid it! It occurred to me that sorting the list first might improve the chances of finding a match earlier in the loop since all similar strings would be together, but thinking about it, perhaps there's equal chance that the similar strings would end up being sorted to the bottom of the list and it wouldn't help at all :/

Another thought - many of the input strings are v. similar, maybe they could be grouped into sets early on (e.g. based on the first 4 chars or something) and then you only look for similar strings in the same set?

Going to read into hamming distances now!

Collapse
 
jbristow profile image
Jon Bristow

I lost about 10 minutes to my son stalling getting into bed.

Kotlin Solution

Answer 1

Once again a fairly straightforward opener. Just run through, do some simple frequency checking and spit back the results. I think this is technically O(n2) but it moved fast enough for me. (And in a more lazy language, it ends up being closer to O(n) anyway)

fun String.frequencies(): Map<Char, Int> =
        groupBy { it }.mapValues { (_, v) -> v.count() }

fun answer1(input: List<String>) =
    input.map {
        val freqs = it.frequencies()
        freqs.anyValue(2::equals) to freqs.anyValue(3::equals)
    }
        .unzip().toList()
        .map { bs -> bs.count { it } }
        .fold(1, Int::times)

Answer 2

As Ryan said, this is just Hamming distance with the added wrinkle that you need to throw away the differences while counting them. Lots of optimization room here, but once again, we shave off just under half the possibilities by only doing unique combinations and going from a raw n^2 to (n*(n+1))/2.

At around 10 ms (calculated by shuffling my input 1000 times), I don't think there's an easy way to make this noticeably faster at this point without a more esoteric method.

fun <A, B> List<A>.cartesian(transform: (A, A) -> B): Sequence<B> {
    return init.asSequence().mapIndexed { i, a ->
        drop(i + 1).asSequence().map { b ->
            transform(a, b)
        }
    }.flatten()
}

fun <A> Pair<A, A>.same() = first == second
fun <A> Pair<A, A>.different() = first != second

fun answer2(input: List<String>) =
    input.cartesian { s1, s2 ->
        s1.zip(s2)
    }.find { // find short circuits on Sequence
        it.count(Pair<Char, Char>::different) == 1
    }?.filter {
        it.same()
    }?.joinToString("") { it.first.toString() }
Collapse
 
mattmorgis profile image
Matt Morgis • Edited

Node.js

Common async generator. Read the file in chunk by chunk and yield each product ID based on new lines.

// Generator
// "abcdef\n+bababc\n" yields -> abcdef -> bababc
async function* streamToProdctId(stream) {
  let previous = "";
  for await (const chunk of stream) {
    previous += chunk;
    let eolIndex;
    while ((eolIndex = previous.indexOf("\n")) >= 0) {
      // productId excludes the EOL
      const productId = previous.slice(0, eolIndex);
      yield productId;
      previous = previous.slice(eolIndex + 1);
    }
  }
  if (previous.length > 0) {
    yield previous;
  }
}

Part 1 was fun with some ES6 array -> Set -> Map to get the value counts

// ['b', 'a', 'b', 'a', 'b', 'c'] returns Map {3 => 'b', 2 => 'a', 1 => 'c}
const count = array => {
  return new Map(
    [...new Set(array)].map(x => [array.filter(y => y === x).length, x])
  );
};

const checksum = async stream => {
  let idsWith2MatchingLetters = 0;
  let idsWith3MatchingLetters = 0;
  for await (const productId of streamToProductId(stream)) {
    const valueCounts = count([...productId]);
    if (valueCounts.has(2)) {
      idsWith2MatchingLetters++;
    }
    if (valueCounts.has(3)) {
      idsWith3MatchingLetters++;
    }
  }
  return idsWith2MatchingLetters * idsWith3MatchingLetters;
};

Part 2 got interesting. I needed to generate all pairs for every product ID. I made my Hamming Distance function also return the common letters. Then tied it all together by running each pair through the Hamming Distance function and getting the lowest.

const generatePairs = array => {
  return array.reduce(
    (acc, _, i1) => [
      ...acc,
      ...new Array(array.length - 1 - i1)
        .fill(0)
        .map((v, i2) => [array[i1], array[i1 + 1 + i2]])
    ],
    []
  );
};

const hammingDistance = (stringOne, stringTwo) => {
  let distance = 0;
  let commonLetters = "";
  for (let i = 0; i < stringOne.length; i++) {
    if (stringOne[i] !== stringTwo[i]) {
      distance += 1;
    } else {
      commonLetters = commonLetters.concat(stringOne[i]);
    }
  }
  return [distance, commonLetters];
};

const findLowestPairAndRemoveDifferences = pairs => {
  let lowestDistance = Infinity;
  let lowestPair;
  pairs.forEach(pair => {
    const [distance, commonLetters] = hammingDistance(...pair);
    if (distance < lowestDistance) {
      lowestDistance = distance;
      lowestPair = commonLetters;
    }
  });
  return lowestPair;
};

const productIds = async stream => {
  const ids = [];
  for await (const productId of streamToProductId(stream)) {
    ids.push(productId);
  }
  return ids;
};

const findCommon = async stream => {
  return findLowestPairAndRemoveDifferences(
    generatePairs(await productIds(stream))
  );
};

Putting it all together:

const productIdStream = () => {
  return fs.createReadStream(__dirname + "/input.txt", {
    encoding: "utf-8",
    highWaterMark: 256
  });
};

const main = async () => {
  try {
    const part1 = await checksum(productIdStream());
    console.log({part1});
    const part2 = await findCommon(productIdStream());
    console.log({part2});
  } catch (e) {
    console.log(e.message);
    process.exit(-1);
  }
};

Full code here: github.com/MattMorgis/Advent-Of-Co...

Collapse
 
rpalo profile image
Ryan Palo

Making hamming distance return common letters is a slick way to go. I was looking at the duplication between the hamming distance and common letters functionality in my solution and was a little bummed about it, but I couldn't figure out a good way to do it.

I like this!

Collapse
 
shritesh profile image
Shritesh Bhattarai • Edited

Rust
Part 1

use std::collections::HashMap;

fn contains_repeats(input: &str) -> (bool, bool) {
    let mut count = HashMap::new();

    input.chars().for_each(|c| {
        let n = count.entry(c).or_insert(0);
        *n += 1;
    });

    let two = count.iter().any(|(_, &v)| v == 2);
    let three = count.iter().any(|(_, &v)| v == 3);

    (two, three)
}

pub fn checksum(input: &[&str]) -> usize {
    let (twos, threes): (Vec<_>, Vec<_>) = input.iter().map(|&s| contains_repeats(s)).unzip();
    let two = twos.iter().filter(|&x| *x).count();
    let three = threes.iter().filter(|&x| *x).count();

    two * three
}

Part 2
I'm still searching for a nice iterator adapter to replace the nested loop. I finally used the itertools crate and it's AMAZING!!!

use itertools::Itertools;

fn hamming(input1: &str, input2: &str) -> usize {
    input1
        .chars()
        .zip(input2.chars())
        .filter(|(a, b)| a != b)
        .count()
}

fn common(input1: &str, input2: &str) -> String {
    input1
        .chars()
        .zip(input2.chars())
        .filter_map(|(a, b)| match a == b {
            true => Some(a),
            false => None,
        })
        .collect()
}

pub fn find_common(input: &[&str]) -> String {
    input
        .iter()
        .tuple_combinations()
        .find_map(|(first, second)| match hamming(first, second) {
            1 => Some(common(first, second)),
            _ => None,
        })
        .unwrap()
}
Collapse
 
bjarnemagnussen profile image
Bjarne Magnussen

I am using Advent of Code to learn Golang, and here is the solution I came up with. Suggestions for improvements are always welcome!

Part 1:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(path string) ([]string, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var lines []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
    }
    return lines, scanner.Err()
}

func main() {
    boxes, err := readLines("input")
    if err != nil {
        panic(err)
    }

    var twos, threes int
    for _, box := range boxes {
        // Iterate through each box letter-by-letter and check if letters appear
        // two or three times:
        two, three := false, false
        for _, letter := range box {
            if !two && strings.Count(box, string(letter)) == 2 {
                twos++
                two = true
            } else if !three && strings.Count(box, string(letter)) == 3 {
                threes++
                three = true
            }
            if two && three {
                // We already found the maximum number of appearing letters we count
                break
            }
        }
    }
    checksum := twos * threes
    fmt.Printf("Checksum is: %d\n", checksum)
}

Part 2:

package main

import (
    "bufio"
    "fmt"
    "os"
)

// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(path string) ([]string, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var lines []string
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
    }
    return lines, scanner.Err()
}

func findSimilar(boxes []string) string {
    // For each box name we remove the i'th element and store the rest of the
    // name inside a map:
    visited := make(map[int]map[string]bool)
    for _, box := range boxes {
        // Go through each  box:
        for i := range box {
            // Remove i'th character from box name:
            subname := box[:i] + box[i+1:]
            // Check if we have already visited a similar box name:
            _, ok := visited[i][subname]
            if !ok {
                // If we have never encountered a similar box name, add it:
                _, ok := visited[i]
                if !ok {
                    sub := make(map[string]bool)
                    visited[i] = sub
                }
                visited[i][subname] = true
            } else {
                // We have encounter a similar box name, return it:
                return subname
            }
        }
    }
    return ""
}

func main() {
    boxes, err := readLines("input")
    if err != nil {
        panic(err)
    }
    subname := findSimilar(boxes)
    fmt.Println(subname)
}

My idea was to use a dictionary and store the subnames and see if we encounter one we have already visited. Since there should only be one match we can immediately return it. I learned a lot about using maps in Golang!

I am also using Python that I have more experience with to cross check solutions. I have tried to implement it with readability in mind, not performance.

Part 1:

with open('input') as f:
    boxes = f.readlines()

twos, threes = 0, 0
for box in boxes:
    twos += any([box.count(letter) == 2 for letter in set(box)])
    threes += any([box.count(letter) == 3 for letter in set(box)])
checksum = twos * threes

print('Checksum {}'.format(checksum))

Part 2:

with open('input') as f:
    boxes = f.readlines()


def closest_boxes():
    visited = {}
    for box in boxes:
        for i in range(len(box)):
            if i not in visited:
                visited[i] = set()
            subname = box[0:i] + box[i+1:]
            if subname in visited[i]:
                return subname
            else:
                visited[i].add(subname)


print(closest_boxes())
Collapse
 
deciduously profile image
Ben Lovy

Late to the party!

Still digging F#, but I'm definitely hindered by my lack of familiarity with what's available in .NET

namespace Day2
open Microsoft.FSharp.Collections

module util =
    type FreqMap = Map<char, int>

    let getBoxIds fileName =
      let lines = System.IO.File.ReadLines(fileName)
      lines |> Seq.toList

    let tickFreq c (freqs: FreqMap) =
      match (freqs.TryFind c) with
      | Some x -> Map.add c (x + 1) freqs
      | None -> Map.add c 1 freqs

    let rec goGetFreqs (freqs: FreqMap) boxId =
      let len = String.length boxId
      if len = 1 then
        tickFreq boxId.[0] freqs
      else
        goGetFreqs (tickFreq boxId.[0] freqs) boxId.[1..len - 1]

    let getFreqs boxId = goGetFreqs (new Map<char, int> (Seq.empty)) boxId

    let countOfFreq num (freqMap: FreqMap) =
      freqMap
        |> Map.filter (fun _ v -> v = num)
        |> Map.toList
        |> List.length

module part1 =
  let getTwosAndThrees boxId =
    let freqs = util.getFreqs boxId
    let twos = util.countOfFreq 2 freqs
    let threes = util.countOfFreq 3 freqs
    (twos, threes)

  let rec getTotals twos threes l =
    match l with
    | [] -> (twos, threes)
    | (two, three)::tail ->
      let newTwos = if two > 0 then twos + 1 else twos
      let newThrees = if three > 0 then threes + 1 else threes
      getTotals newTwos newThrees tail

  let execute fileName =
    let boxIds = util.getBoxIds fileName
    let twosAndThrees = List.map (fun bid -> getTwosAndThrees bid) boxIds
    let (twos, threes) = getTotals 0 0 twosAndThrees
    twos * threes

module part2 =
  let checkTwo id id' =
    let s = (Seq.map2 (fun c c' -> 
      if c <> c' then
        '!'
      else
        c) id id')
          |> Seq.filter (fun c -> c <> '!')
          |> Seq.toArray
          |> System.String

    if (String.length id) - 1 = String.length s then
      Some s
    else
      None

  let findWinner boxIds =
    List.map (fun id ->
      List.map (fun id' ->
        if id = id' then
          None
        else
          checkTwo id id') boxIds) boxIds
            |> List.map (List.filter (fun el -> el.IsSome))
            |> List.filter (fun el -> List.length el > 0)
            |> List.concat
            |> List.distinct

  let execute fileName =
    let boxIds = util.getBoxIds fileName
    findWinner boxIds

Collapse
 
macshome profile image
Josh Wisenbaker

I'm not super thrilled with my Day 2 code, but I haven't really had the time to tweak it with everything going on at work currently.

Swift Solutions
Part 1

This one was fairly simple. Just count how many times each letter appears in each String and act appropriately. I do like the fact that a Swift String type is really an Array of Characters.

// Part 1: Find the checksums
func calculateChecksum(_ idCodes: [String]) -> Int {
    var doubles = 0
    var triples = 0

    idCodes.map { (boxID) in
        var doubleTrue = false
        var tripleTrue = false
        for char in boxID {
            let count = boxID.filter { $0 == char }.count
            if count == 2 {
                doubleTrue = true
            }
            if count == 3 {
                tripleTrue = true
            }
        }
        doubles += doubleTrue ? 1 : 0
        triples += tripleTrue ? 1 : 0
    }
    return doubles * triples
}

let checksum = calculateChecksum(boxIDs)
print("Boxes checksum is: \(checksum)")

Part 2
This one feels clunky, if I get a chance I'll revisit it.

I break on the first hit for a solution to short circuit everything and return the answer, this can help a lot with so many Characters to test.

I use zip(_:_:) with a filter and count to quickly test how many differences there are in the same positions. When I see two strings that differ by one character in the same position I move to the next step.

In the second part I cast the Arrays into Set types so that I can use the Collection Algebra features to quickly find the actual character to remove by subtracting one collection from the other. With that done I can just remove it from the source Array and return what's left.

// Part 2: Box finder
func findTheBoxes(_ idCodes: [String]) -> String {
    var result = ""
    var differceCount = 0

    for boxID in idCodes {
        if differceCount == 1 {
            break
        }
        for code in idCodes {
            differceCount = zip(boxID, code).filter{$0 != $1}.count
            if differceCount == 1 {
                let diff = Set(boxID).subtracting(code)
                if let charToRemove = diff.first {
                    result = boxID
                    if let foo = result.index(of: charToRemove) {
                        result.remove(at: foo)
                        break
                    }
                }
            }
        }
    }
    return result
}

let theBoxes = findTheBoxes(boxIDs)
print("Matching box ID is: \(theBoxes)")

Normally I would import Foundation here so that I could just use NSOrderedSet and skip a few steps. I wanted to try and keep it all in the Swift Standard Library though, so I didn't!

Collapse
 
thedevbc profile image
Ben Mulford

Here's my C# .Net solution:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace Day2Part1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] inputTxt = File.ReadAllLines(@"C:\Users\Ben\Desktop\adventOfCode2018\Day 2\input.txt");
            List<int> TwoMatchList = new List<int>();// List of id's to find box in the input array
            List<int> ThreeMatchList = new List<int>();// list of id's to find box in the input array
            foreach (string boxID in inputTxt)
            {
                int id = Array.IndexOf(inputTxt, boxID);
                var distinctLetters = boxID.Select(x => x).Distinct().ToList();
                foreach (char letter in distinctLetters)
                {
                    var matches = boxID.Where(x => x == letter).ToList();
                    switch (matches.Count)
                    {
                        case 2:
                            //if there's exactly 2, and it's not already in the list then add it to the TwoMatchList
                            if (!TwoMatchList.Contains(id))
                            {
                                TwoMatchList.Add(id);
                            }
                            break;
                        case 3:
                            //if there's exactly 3, and it's not already in the list then add it to the ThreeMatchList
                            if (!ThreeMatchList.Contains(id))
                            {
                                ThreeMatchList.Add(id);
                            }
                            break;
                        default:
                            //don't add to any list
                            break;
                    }//end switch
                }//end foreach letter
            }//end foreach boxID
            int checksum = TwoMatchList.Count * ThreeMatchList.Count;
            Console.WriteLine("Number of boxes with exactly two matching letters {0}", TwoMatchList.Count);
            Console.WriteLine("Number of boxes with exactly three matching letters {0}", ThreeMatchList.Count);
            Console.WriteLine("Checksum: {0}", checksum);
            Console.WriteLine();


            Console.WriteLine("Now for part two!");

            //empty Dictionary to hold any boxes we find and the index of the letter that's different
            Dictionary<string, int> savedIDsAndIndexes = new Dictionary<string, int>();

            //empty string to hold the answer
            string answer = "";

            //Loop through to test and find correct boxes ==>> this could be a foreach loop
            for (int i = 0; i < inputTxt.Length; i++)
            {
                string boxID = inputTxt[i];
                for (int j = inputTxt.Length - 1; j >= 0; j--)
                {
                    List<int> mismatchIndexes = new List<int>();
                    string testBoxID = inputTxt[j];
                    for (int k = 0; k < boxID.Length; k++)
                    {
                        if (boxID[k] != testBoxID[k])
                        {
                            mismatchIndexes.Add(k);
                        }
                    }
                    if (mismatchIndexes.Count == 1)
                    {
                        savedIDsAndIndexes.Add(boxID, mismatchIndexes.First());
                    }
                }
            }

            Console.WriteLine("number of correct boxes found: {0}", savedIDsAndIndexes.Count);

            answer = savedIDsAndIndexes.Keys.First().Substring(0, savedIDsAndIndexes.Values.First())
                + savedIDsAndIndexes.Keys.First().Substring(savedIDsAndIndexes.Values.First() + 1);

            Console.WriteLine("here are the ids:");
            foreach (var id in savedIDsAndIndexes)
            {
                Console.WriteLine(id.Key);
            }

            Console.WriteLine("The common characters are:\n{0}", answer);

            Console.WriteLine("saved index: {0}", savedIDsAndIndexes.Values.First());
        }
    }
}
Collapse
 
kritner profile image
Russ Hammett • Edited

I didn't see any C# glancing through:

public class InventoryManagement
    {
        public int GetCheckSum(IEnumerable<string> inventory)
        {
            return GetCheckSum(GetCheckSumCandidates(inventory));
        }

        public int GetCheckSum(IEnumerable<InventoryChecksumCandidates> candidates)
        {
            var checksumPieces = candidates
                .GroupBy(gb => gb.DuplicateCount)
                .Select(s => new
                {
                    s.Key,
                    Count = s.Count()
                })
                .ToList();

            Debug.Assert(checksumPieces.Count() == 2);

            return checksumPieces[0].Count * checksumPieces[1].Count;
        }

        public IEnumerable<InventoryChecksumCandidates> GetCheckSumCandidates(IEnumerable<string> inventory)
        {
            List<InventoryChecksumCandidates> list = new List<InventoryChecksumCandidates>();

            foreach (var item in inventory)
            {
                var checksumCandidate = item
                    // make everything same case (just in case)
                    .ToLower()
                    // where it's a letter
                    .Where(char.IsLetter)
                    // Group by the default (letter)
                    .GroupBy(gb => gb)
                    // Project the found values into their new type
                    .Select(s => new InventoryChecksumCandidates()
                    {
                        DuplicateCharacter = s.Key.ToString(),
                        DuplicateCount = s.Count()
                    });

                if (checksumCandidate != null)
                {
                    list.AddIfNotNull(
                        checksumCandidate.FirstOrDefault(f => f.DuplicateCount == 2)
                    );
                    list.AddIfNotNull(
                        checksumCandidate.FirstOrDefault(f => f.DuplicateCount == 3)
                    );
                }
            }

            return list;
        }
    }

(Note AddIfNotNull is from my extension methods package nuget.org/packages/Kritner.Extensi...

Part 2:

public class PrototypeFabricFinder
    {
        public string GetProtoTypeFabric(IEnumerable<string> inventory)
        {
            var inventoryPermutations = SwapCharForEachInventoryPermutation(inventory);
            var foundDuplicateish = inventoryPermutations
                // Group by default
                .GroupBy(gb => gb)
                // We only want the instance where there's more than
                // one in the group by (the prototype fabric)
                .Where(w => w.Count() > 1)
                .Select(s => new
                {
                    s.Key
                })
                .FirstOrDefault();

            // Return the prototype fabric, minus the "different" single character
            return foundDuplicateish.Key.Replace("*", "");
        }

        /// <summary>
        /// Builds and returns a <see cref="IEnumerable{string}"/>
        /// containing every permutation of iventory items, with
        /// one character (*) swapped in at a differing index.
        /// 
        /// I have no idea if that makes sense.
        /// </summary>
        /// <param name="inventory">each inventory id.</param>
        /// <returns></returns>
        private IEnumerable<string> SwapCharForEachInventoryPermutation(
            IEnumerable<string> inventory
        )
        {
            List<string> list = new List<string>();

            foreach (var item in inventory)
            {
                // Create new strings and add them to the list.
                // The new strings will be a copy of the original,
                // with a single character (the current index) swapped in with "*"
                for (var index = 0; index < item.Length; index++)
                {
                    var charArrayOfItem = item.ToCharArray();
                    charArrayOfItem[index] = '*';

                    list.Add(new string(charArrayOfItem));
                }
            }

            return list;
        }
    }

I dunno how I'm gonna keep up during the week, but putting my solutions in the repo github.com/Kritner/Kritner.AdventO...

Collapse
 
stevieoberg profile image
Stevie Oberg

This one was difficult for me, but I eventually got it! I'm also not happy about that double for loop in part 2, but I think I sped it up by removing the elements as I compared them?

github.com/stevieoberg/advent-of-c...

Collapse
 
mustafahaddara profile image
Mustafa Haddara • Edited

Hosting my solutions on my github...

MustafaHaddara / advent-of-code-2018

Code for https://adventofcode.com/2018/


I'm pretty sure part 2 has to be O(n2) but you can cut down on the total number of iterations by only looking forward...here's my solution (implemented in Julia)

function inventory_management_diff(input)
    word_idx = 0
    while word_idx < length(input)
        word_idx += 1
        word1 = input[word_idx]

        for word2 in input[word_idx+1:end]
            ch_diff = one_char_diff(word1, word2)
            if ch_diff != nothing
                return string(word1[1:ch_diff-1], word1[ch_diff+1:end])
            end
        end
    end
end

function one_char_diff(word1, word2)
    found_diff = nothing
    ch_idx = 0
    while ch_idx < length(word1)
        ch_idx += 1
        if word1[ch_idx] != word2[ch_idx]
            if found_diff == nothing
                found_diff = ch_idx
            else
                return nothing
            end
        end
    end
    return found_diff
end

function main()
    filename = ARGS[1]  # julia arrays are 1-indexed!
    input = split(read(filename, String))
    test_input = ["abcde","fghij","klmno","pqrst","fguij","axcye","wvxyz"]

    println(inventory_management_diff(input))
end

main()

Julia's a weird language...it's so close to python that I forget it does weird things like "Arrays are 1-indexed", and it spells None/null as nothing

Collapse
 
rpalo profile image
Ryan Palo

I agree that Julia is weird, but I actually love it because it adds some functional stuff that I miss in python like the pipe operator!

Collapse
 
ben profile image
Ben Halpern

We need better cover images πŸ˜‚

Collapse
 
rpalo profile image
Ryan Palo

I was kind of hoping that the magical resizing was an automated resizing thing that happens and not one of you guys fixing it for me. I can actually put a background on it and scale it. What are the optimal dimensions? Is the white text on black ok or should I come up with something more fancy? I have very little aesthetic skill, so I’d appreciate any suggestions you or anyone else has.

Collapse
 
aspittel profile image
Ali Spittel

Ryan, do you want me to design something? The only hard part is that I probably won't be up at midnight most nights to add specifics. Do you have any design software? If so, I can transfer you a file! We could also use Canva.

Thread Thread
 
rpalo profile image
Ryan Palo

That would be amazing! I have a Figma account, but that’s it. I’ve never heard of Canva and pretty sure I don’t have any design software, but if you tell me the best way to handle it, I’ll happily do whatever you suggest. I’m happy to learn.

Thread Thread
 
aspittel profile image
Ali Spittel

Cool -- sending you a DM via /connect!