DEV Community

Cover image for Advent of Code 2020 Solution Megathread - Day 6: Custom Customs
Ryan Palo
Ryan Palo

Posted on • Updated on

Advent of Code 2020 Solution Megathread - Day 6: Custom Customs

Yesterday, a lot of you finished quickly and were looking for more to keep you occupied on a Saturday. All of the golfed one-liners were really funny to read through.

The Puzzle

In today’s puzzle, we're helping our fellow passengers fill out customs forms. 26 questions represented by 'a'-'z', and the presence of a particular letter in our input means a "yes" for that question for that particular person. We're just helping tally up the results.

The Leaderboards

As always, this is the spot where I’ll plug any leaderboard codes shared from the community.

Ryan's Leaderboard: 224198-25048a19
Enter fullscreen mode Exit fullscreen mode

If you want to generate your own leaderboard and signal boost it a little bit, send it to me either in a DEV message or in a comment on one of these posts and I'll add it to the list above.

Yesterday’s Languages

Updated 03:07PM 12/12/2020 PST.

Language Count
Ruby 3
Go 2
Python 2
Haskell 2
JavaScript 2
C 2
Rust 2
COBOL 1
Elixir 1
D 1
TypeScript 1

Merry Coding!

Latest comments (28)

Collapse
 
erikbackman profile image
Erik Bäckman • Edited

Haskell:

module Main where

import Control.Arrow (Arrow((&&&)))
import Data.List.Split (splitOn)
import Control.Monad (join)
import Data.List (nub, intersect)

parseInput :: String -> [[String]]
parseInput = fmap lines . splitOn "\n\n"

solveP1 :: [[String]] -> Int
solveP1 = sum . fmap (length . nub . join)

solveP2 :: [[String]] -> Int
solveP2 = sum . fmap answers
  where
    answers [x]    = length x
    answers (x:xs) = length . foldr intersect x $ xs

main :: IO ()
main = print . (solveP1 &&& solveP2) . parseInput =<< readFile "./day6inp.txt"
Enter fullscreen mode Exit fullscreen mode
Collapse
 
harrygibson profile image
Harry Gibson

Python. A bit less elegant than some of the other python versions already posted but the same idea using set theory

n = 0
checkset = set()
with open('input.txt', 'r') as in_file:
    for l in in_file:
        if l.strip() == '':
            n += len(checkset)
            checkset.clear()
        else:
            checkset.update((c for c in l.strip()))
    if l.strip() != '': n += len(checkset)
print(f"Part 1: total is {n}")

checkset = set()
new_group = True
n = 0
with open('input.txt', 'r') as in_file:
    for l in in_file:
        if l.strip() == '':
            n += len(checkset)
            checkset.clear()
            new_group = True
        else:
            if new_group:
                checkset.update((c for c in l.strip()))
                new_group = False
            else:
                # must be in every person: equivalent to set intersection 
                checkset.intersection_update((c for c in l.strip()))
    if l.strip() != '': n += len(checkset)
print(f"Part 2: total is {n}")
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sleeplessbyte profile image
Derk-Jan Karrenbeld

Only took a few minutes today; here is what I got in Ruby:

require 'benchmark'

class GroupAnswers
  def self.from_lines(lines)
    GroupAnswers.new(lines.map(&:chomp))
  end

  def initialize(people)
    questions = people.join('').chars.uniq
    self.count = questions.count { |q| people.all? { |l| l.include?(q) } }
  end

  def to_i
    self.count
  end

  private

  attr_accessor :count
end

groups = File
  .read('input.txt')
  .split(/\n\n/)

Benchmark.bmbm do |b|
  b.report do
    puts groups.sum { |group| GroupAnswers.from_lines(group.split(/\n/)).to_i }
  end
end
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mellen profile image
Matt Ellen

I spent a long time trying to get a regex to work for part 2, but I have given up for the time being. Got a short couple of answers for this:

function testScoresp1()
{
  const input = document.getElementsByTagName('pre')[0].innerHTML;
  const groups = input.split('\n\n');
  return groups.map(group => new Set(group.replaceAll('\n', ''))).reduce((sum, group) => sum + group.size, 0);
}

function testScoresp2()
{
  const input = document.getElementsByTagName('pre')[0].innerHTML;
  const groups = input.split('\n\n');
  const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
  return alphabet.reduce((sum, letter) =>
  {
    return sum + groups.filter(group => group.trim().split('\n').every(line => line.indexOf(letter) > -1)).length;
  }, 0);
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
patryk profile image
Patryk Woziński

Yesterday I had no time to post my solution in Elixir, but there is!

defmodule AdventOfCode.Day6 do
  def part1(file_path) do
    file_path
    |> read_all_answers()
    |> Enum.map(&how_many_answers(&1, :step1))
    |> Enum.sum()
  end

  def part2(file_path) do
    file_path
    |> read_all_answers()
    |> Enum.map(&how_many_answers(&1, :step2))
    |> Enum.sum()
  end

  defp read_all_answers(file_path) do
    file_path
    |> File.read!()
    |> String.split("\n\n", trim: true)
    |> Enum.map(&String.split/1)
  end

  defp how_many_answers(list, :step1), do: how_many_answers(list, &MapSet.union/2)
  defp how_many_answers(list, :step2), do: how_many_answers(list, &MapSet.intersection/2)

  defp how_many_answers(list, f) do
    list
    |> Enum.map(&String.to_charlist/1)
    |> Enum.map(&MapSet.new/1)
    |> Enum.reduce(f)
    |> Enum.count()
  end
end
Enter fullscreen mode Exit fullscreen mode
Collapse
 
rpalo profile image
Ryan Palo

A little late to the party, but I got it done before bed time 😁

Day6.h:

#ifndef AOC2020_DAY6_H
#define AOC2020_DAY6_H

/// Day 6: Custom Customs
/// 
/// Search through lists of questionnaire answers.
/// Each questionnaire is 26 questions, so a yes to question 'b' would
/// be represented by the presence of 'b' in the output line.
/// People are one to a line, and parties are separated by a blank line.

#include <stdlib.h>
#include <stdio.h>

/// Return the number of letters that are present for any line in a group
int tally_anyone_yeses(FILE* fp);

/// Return the number of letters that are present in every line in a group
int tally_everyone_yeses(FILE* fp);

/// Run both 
int day6(void);
#endif
Enter fullscreen mode Exit fullscreen mode

Day6.c:

#include "Day6.h"

#include <stdio.h>
#include <string.h>

/// Number of letters in the alphabet
#define LETTERS 26

/// Max number of chars per line (yes to all plus newline plus NULL)
#define MAX_LINE_SIZE (LETTERS + 2)

int tally_anyone_yeses(FILE* fp) {
  int counts[LETTERS] = {0};
  char line[MAX_LINE_SIZE] = {0};

  // Log each occurrence of a character.
  while (!feof(fp)) {
    fgets(line, MAX_LINE_SIZE, fp);
    if (line[0] == '\n') break;

    for (int i = 0; line[i] != '\n' && line[i]; i++) {
      int index = line[i] - 'a';
      counts[index]++;
    }
    memset(line, 0, MAX_LINE_SIZE);
  }

  // Loop through and find the total count of those that appeared.
  int total = 0;
  for (int i = 0; i < LETTERS; i++) {
    if (counts[i] > 0) total++;
  }
  return total;
}

/// Run the part 1 code on the input file.
static int part1() {
  FILE* fp;
  fp = fopen("data/day6.txt", "r");
  if (fp == NULL) {
      printf("Couldn't open input file.\n");
      exit(EXIT_FAILURE);
  }

  // Loop through and add up the total for each group.
  int total = 0;
  while (!feof(fp)) {
      total += tally_anyone_yeses(fp);
  }

  fclose(fp);
  return total;
}

int tally_everyone_yeses(FILE* fp) {
  int counts[LETTERS] = {0};
  char line[MAX_LINE_SIZE] = {0};
  int people_count = 0;

  // Loop through the group and add up everybody's answers.
  while (!feof(fp)) {
    fgets(line, MAX_LINE_SIZE, fp);
    if (line[0] == '\n') break;

    people_count++;
    for (int i = 0; line[i] != '\n' && line[i]; i++) {
      int index = line[i] - 'a';
      counts[index]++;
    }
    memset(line, 0, MAX_LINE_SIZE);
  }

  // Count the number of letters where everyone answered it yes
  // i.e. number of yeses == number of people
  int total = 0;
  for (int i = 0; i < LETTERS; i++) {
    if (counts[i] == people_count) total++;
  }
  return total;
}

/// Run the part 2 code on the input file.
int part2() {
  FILE* fp;
  fp = fopen("data/day6.txt", "r");
  if (fp == NULL) {
      printf("Couldn't open input file.\n");
      exit(EXIT_FAILURE);
  }

  // Loop through and add up the total for each group.
  int total = 0;
  while (!feof(fp)) {
      total += tally_everyone_yeses(fp);
  }

  fclose(fp);
  return total;
}

int day6() {
  printf("====== Day 6 ======\n");
  printf("Part 1: %d\n", part1());
  printf("Part 2: %d\n", part2());
  return EXIT_SUCCESS;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
readyready15728 profile image
readyready15728

Ruby, part 2:

require 'set'

groups = File.read('06.txt').split /\n\n/

puts (groups.map do |group|
  responses = group.split(/\n/).map { |response| Set.new(response.each_char) }
  responses.inject { |product, current| product.intersection(current) }.length
end).sum
Enter fullscreen mode Exit fullscreen mode
Collapse
 
readyready15728 profile image
readyready15728 • Edited

Ruby, part 1:

require 'set'

groups = File.read('06.txt').split /\n\n/

puts (groups.map do |group|
  Set.new(group.split(/\n/).join.each_char).length
end).sum
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kudostoy0u profile image
Kudos Beluga • Edited

Javascript answer, Part 1 and 2 in one script, change the part2 boolean to false if you want a part 1 answer.
Looking at other people's responses, mine feels very inefficient :(

let fs = require("fs"), part2 = false, n = 0;
fs.readFile("input.txt","utf8",(err,data) => {
if (err) throw err;
let veryuppercount = 0;
data.split(/\n\s/gi).map(e => {
  if (part2) {
  let uppercount = 0;
 e = e.split(/\n/g);
 e[0].split("").map(f => {
   let counter = 0;
   e.slice(1,e.length).map(g => {
   g.split("").map(h => {
     if (h == f) counter++
   })})
   if (counter == e.length-1) uppercount++
 })
  veryuppercount += uppercount;
  } else {
  e = e.replace(/\n/g,"")
  if (e) {
 let noduplicate = [...new Set(e.split(""))];
 n += noduplicate.length
  }}})
if (part2) console.log(veryuppercount)
else console.log(n)
})
Enter fullscreen mode Exit fullscreen mode
Collapse
 
meseta profile image
Yuan Gao • Edited

Python one-liners, thanks to set theory and list comprehension, and map
Part 1:

sum([len(set(entry.replace("\n",""))) for entry in open("input.txt").read().split("\n\n")])
Enter fullscreen mode Exit fullscreen mode

Part 2:

sum(len(set.intersection(*map(set, entry.split()))) for entry in open("input.txt").read().split("\n\n"))
Enter fullscreen mode Exit fullscreen mode

I try to explain this more fully at dev.to/meseta/advent-of-code-2020-...

Collapse
 
katafrakt profile image
Paweł Świątkowski

Re yesterday: actually my solution was in D, but I don't see it listed in the table.

Collapse
 
rpalo profile image
Ryan Palo

OK cool, I wasn't 100% sure. I'll update that.

Collapse
 
katafrakt profile image
Paweł Świątkowski

Yeah, I should've mentioned that in the original. Those C-like languages look pretty much all the same if you don't know some particular details.

Thread Thread
 
rpalo profile image
Ryan Palo

Haha! Yeah, I was thinking, "I'm pretty sure that's not Go, but maybe it's C#? Or C++?" I just thought of inspecting the HTML classes yesterday after running into a similar issue. 😂

Collapse
 
vncz profile image
Vincenzo Chianese
(ns day6 (:require [clojure.string :refer [split]]
                   [clojure.set :refer [intersection]]))

(def input (-> "./day6input.txt"
               (clojure.core/slurp)
               (split #"\n\n")))

(def any-answers (->> input
                      (map #(re-seq #"\w" %))
                      (map set)
                      (reduce #(+ %1 (count %2)) 0))) ; Part 1

(def all-answers (->> input
                      (map #(split % #"\n"))
                      (map #(map (fn [x] (set (clojure.core/char-array x))) %))
                      (map #(apply intersection %))
                      (reduce #(+ %1 (count %2)) 0))) ; Part 2
Enter fullscreen mode Exit fullscreen mode
Collapse
 
vncz profile image
Vincenzo Chianese • Edited

I've solved this using Clojure for the sake of. It seems to be the shortest one so far.

(ns day6 (:require [clojure.string :refer [split]]
                   [clojure.set :refer [intersection]]))

(def input (-> "./day6input.txt"
               (clojure.core/slurp)
               (split #"\n\n")))

(def any-answers (->> input
                      (map #(re-seq #"\w" %))
                      (map set)
                      (reduce #(+ %1 (count %2)) 0))) ; Part 1

(def all-answers (->> input
                      (map #(split % #"\n"))
                      (map #(map (fn [x] (set (clojure.core/char-array x))) %))
                      (map #(apply intersection %))
                      (reduce #(+ %1 (count %2)) 0))) ; Part 2
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mgasparel profile image
Mike Gasparelli

I thought the most obvious way to do this would be to intersect a bunch of HashSets, but seemed to be more short & sweet to just check that every character in the first line was contained in every other line 🤷

Part 1

    public class Part1 : Puzzle<IEnumerable<PlaneGroup>, int>
    {
        public override int SampleAnswer => 11;

        public override IEnumerable<PlaneGroup> ParseInput(string rawInput)
            => rawInput
                .Split(Environment.NewLine + Environment.NewLine)
                .Where(line => line.Length > 0)
                .Select(group =>
                    new PlaneGroup(
                        group
                            .Split(Environment.NewLine)
                            .Where(x => x.Length > 0)));

        public override int Solve(IEnumerable<PlaneGroup> input)
            => input.Sum(x => x.CountDistinctAnswers());
    }
Enter fullscreen mode Exit fullscreen mode

Part 2

    public class Part2 : Part1
    {
        public override int SampleAnswer => 6;

        public override int Solve(IEnumerable<PlaneGroup> input)
            => input.Sum(x => x.CountIntersectingAnswers());
    }
Enter fullscreen mode Exit fullscreen mode

PlaneGroup

    public class PlaneGroup
    {
        List<string> answers;

        public PlaneGroup(IEnumerable<string> answers)
        {
            this.answers = answers.ToList();
        }

        public int CountDistinctAnswers()
            => new HashSet<char>(answers.SelectMany(a => a))
                .Count();

        public int CountIntersectingAnswers()
            => answers.First()
                .Count(c => answers.All(a => a.Contains(c)));
    }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
galoisgirl profile image
Anna

JavaScript - I did it in COBOL first, but still wanted to practice reduce a bit.

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

const A_INDEX = "a".charCodeAt(0);

let lines = [];

function getClearAnswers() {
  return Array(26).fill(0)
}

const readInterface = readline.createInterface({
    input: fs.createReadStream('./example.txt'),
});

readInterface.on('line', function(line) {
  lines.push(line)
});

readInterface.on('close', function() {
  const result = lines.reduce((res, line) => {
    if (line.length == 0) {
      if (res.groupSize == 0) {
        return res
      }
      return {
        total: res.total + Object.values(res.groupAnswers).reduce((acc, x) => acc + (x == res.groupSize ? 1 : 0), 0),
        groupSize: 0,
        groupAnswers: getClearAnswers()
      }
    }
    const indices = line.split('').map(l => l.charCodeAt(0) - A_INDEX)
    return {
      total: res.total,
      groupSize: res.groupSize + 1,
      groupAnswers: indices.reduce((acc, x) => acc.fill(acc[x] + 1, x, x + 1), res.groupAnswers)
    }
  }, {
    total: 0,
    groupSize: 0,
    groupAnswers: getClearAnswers()
  });

  console.log(result.total)
})
Enter fullscreen mode Exit fullscreen mode