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!

Top comments (28)

Collapse
 
bgaster profile image
Benedict Gaster

OK, today seemed pretty straightforward, although it took me to reach task 2 to have the insight that it was union and then intersect... oh well it is a Sunday :-)

Anyway Haskell was a nice fit and simply used set operations on lists to keep with my somewhat weak goal of using lists.

main  = do xs <- IOT.readFile "day6_input" <&> fmap (map unpack . T.lines) . T.splitOn "\n\n"
           print (sum $ fmap (length . foldr1 union) xs)
           print (sum $ fmap (length . foldr1 intersect) xs)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kais_blog profile image
Kai • Edited

I've created a step-by-step tutorial (TypeScript) again:

and I did something with bits, just for fun:

Collapse
 
mgasparel profile image
Mike Gasparelli

Clever solution. I had an instinct that I could use binary to solve Day5, although couldn't quite flesh out a working solution (ended up solving it in a different way). It didn't even dawn on me that you could use binary for this challenge, although it seems obvious now. 👍

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
 
willsmart profile image
willsmart

Python impl today. Prob not great python but works.

print(reduce(
    lambda acc, v: acc + len(
        set(v.replace('\n', ''))
    ),
    open("6.txt").read().split('\n\n'),
    0
))

print(reduce(
    lambda acc, v: acc + len(reduce(
        lambda acc, v: acc & v,
        map(
            lambda v: set(v), 
            v.split('\n')
        )
    )),
    open("6.txt").read().split('\n\n'),
    0
))
Enter fullscreen mode Exit fullscreen mode

Tomorrow will try something like prolog?!
Failing that, maybe Haskell.

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
 
benwtrent profile image
Benjamin Trent

Rust!

use std::collections::HashSet;

#[aoc_generator(day6)]
fn to_vec(input: &str) -> Vec<Vec<Vec<char>>> {
    input
        .split("\n\n")
        .map(|i| {
            i.lines()
                .map(|s| s.chars().collect::<Vec<char>>())
                .collect()
        })
        .collect()
}

#[aoc(day6, part1)]
fn answer_count(input: &Vec<Vec<Vec<char>>>) -> usize {
    input
        .iter()
        .map(|group| {
            group
                .iter()
                .flat_map(|v| v.iter().map(|c| *c).collect::<HashSet<char>>())
                .collect::<HashSet<char>>()
                .len()
        })
        .sum()
}

#[aoc(day6, part2)]
fn abs_answer_count(input: &Vec<Vec<Vec<char>>>) -> usize {
    input
        .iter()
        .map(|group| {
            group
                .iter()
                .map(|v| v.iter().map(|c| *c).collect::<HashSet<char>>())
                .fold(Option::None, |l, g| {
                    if l.is_none() {
                        Some(g.clone())
                    } else {
                        Some(
                            l.unwrap_or(HashSet::new())
                                .intersection(&g)
                                .map(|c| *c)
                                .collect(),
                        )
                    }
                })
                .unwrap_or(HashSet::new())
                .len()
        })
        .sum()
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ballpointcarrot profile image
Christopher Kruse

Ugh, made it too late for this day (but I had it done in time for the problem, honest!).

Used HashSet to my benefit here, as I could apply set theory to the problem and get it done easily.

As always, in Github.

use aoc_runner_derive::{aoc, aoc_generator};
use std::collections::HashSet;

#[aoc_generator(day6)]
fn parse_input_day6(input: &str) -> Vec<String> {
    input
        .split("\n\n")
        .map(|group| String::from(group))
        .collect()
}

#[aoc(day6, part1)]
fn sum_group_questions(input: &Vec<String>) -> usize {
    let answers: Vec<HashSet<char>> = input
        .iter()
        .map(|group| group.chars().filter(|x| *x != '\n').collect())
        .collect();
    answers.iter().map(|group| group.len()).sum()
}

#[aoc(day6, part2)]
fn sum_group_common_questions(input: &Vec<String>) -> usize {
    input
        .iter()
        .map(|group| {
            let mut first_run = true;
            group
                .lines()
                .map(|answers| answers.chars().collect::<HashSet<char>>())
                .fold(HashSet::new(), |memo, ans| {
                    if memo.is_empty() && first_run {
                        first_run = false;
                        ans.clone()
                    } else {
                        let intersect = memo.intersection(&ans).cloned().collect();
                        intersect
                    }
                })
                .len()
        })
        .sum()
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
galoisgirl profile image
Anna

COBOL

   IDENTIFICATION DIVISION.
   PROGRAM-ID. AOC-2020-06-2.
   AUTHOR. ANNA KOSIERADZKA.

   ENVIRONMENT DIVISION.
   INPUT-OUTPUT SECTION.
   FILE-CONTROL.
       SELECT INPUTFILE ASSIGN TO "d6.input"
       ORGANIZATION IS LINE SEQUENTIAL.

   DATA DIVISION.
   FILE SECTION.
     FD INPUTFILE
     RECORD IS VARYING IN SIZE FROM 1 to 99
     DEPENDING ON REC-LEN.
     01 INPUTRECORD PIC X(99).
   WORKING-STORAGE SECTION.
     01 FILE-STATUS PIC 9 VALUE 0.
     01 REC-LEN PIC 9(2) COMP.
     01 WS-GROUP-ANSWERS PIC 9 OCCURS 26 TIMES.
     01 WS-CHAR PIC X.

   LOCAL-STORAGE SECTION.
     01 I UNSIGNED-INT VALUE 1.
     01 C UNSIGNED-INT VALUE 1.
     01 X UNSIGNED-INT VALUE 1.
     01 GROUP-SIZE UNSIGNED-INT VALUE 0.
     01 GROUP-TOTAL UNSIGNED-INT VALUE 0.
     01 TOTAL UNSIGNED-INT VALUE 0.

   PROCEDURE DIVISION.
   001-MAIN.
       PERFORM 004-INIT-VARIABLES.
       OPEN INPUT INPUTFILE.
       PERFORM 002-READ UNTIL FILE-STATUS = 1.
       CLOSE INPUTFILE.
       PERFORM 004-NEXT-GROUP.
       DISPLAY TOTAL.
       STOP RUN.

   002-READ.
        READ INPUTFILE
            AT END MOVE 1 TO FILE-STATUS
            NOT AT END PERFORM 003-PROCESS-RECORD
        END-READ.

   003-PROCESS-RECORD.
       IF REC-LEN = 0 THEN
          PERFORM 004-NEXT-GROUP
       ELSE 
          PERFORM 005-PROCESS-ROW
       END-IF.

   004-INIT-VARIABLES.
       PERFORM VARYING I FROM 1 BY 1 UNTIL I > 26
          MOVE 0 TO WS-GROUP-ANSWERS(I)
       END-PERFORM.
       MOVE 0 TO GROUP-SIZE.
       MOVE 0 TO GROUP-TOTAL.

   004-NEXT-GROUP.
       IF GROUP-SIZE > 0 THEN
          PERFORM 006-TALLY-GROUP-TOTAL
       END-IF.
       ADD GROUP-TOTAL TO TOTAL.
       PERFORM 004-INIT-VARIABLES.

   005-PROCESS-ROW.
       ADD 1 TO GROUP-SIZE.
       PERFORM VARYING I FROM 1 BY 1 UNTIL I > REC-LEN
          MOVE INPUTRECORD(I:1) TO WS-CHAR
          COMPUTE C = FUNCTION ORD(WS-CHAR)
          COMPUTE X = WS-GROUP-ANSWERS(C - 97) + 1
          MOVE X TO WS-GROUP-ANSWERS(C - 97)
       END-PERFORM.

   006-TALLY-GROUP-TOTAL.
       PERFORM VARYING I FROM 1 BY 1 UNTIL I > 26
          IF WS-GROUP-ANSWERS(I) = GROUP-SIZE THEN 
             ADD 1 TO GROUP-TOTAL
          END-IF 
       END-PERFORM.
Enter fullscreen mode Exit fullscreen mode
Collapse
 
particleflux profile image
Stefan Linke

2 Solutions again.

Go:

package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "os"
)

func countQuestions(group []byte) (int, int) {
    counts := map[byte]int{}
    numPeople := 1
    for _, question := range group {
        if question == '\n' {
            numPeople++
        } else {
            counts[question]++
        }
    }

    numAllYes := 0
    for _, c := range counts {
        if c == numPeople {
            numAllYes++
        }
    }

    return len(counts), numAllYes
}

func main() {
    input, _ := ioutil.ReadAll(os.Stdin)
    groups := bytes.Split(input, []byte("\n\n"))

    sum1, sum2 := 0, 0
    for _, group := range groups {
        a, b := countQuestions(group)
        sum1 += a
        sum2 += b
    }

    fmt.Println(sum1, sum2)
}
Enter fullscreen mode Exit fullscreen mode

And tweet-sized PHP:

<?for($i=0,$z=explode("

",file_get_contents('input'));$z[$i];){$a+=count($f=count_chars($z[$i++],1))-(($n=$f[10])>0);foreach($f as$k=>$v)$b+=$k!=10&$v==$n+1;}echo"$a $b";
Enter fullscreen mode Exit fullscreen mode
Collapse
 
flwidmer profile image
flwidmer

I decided to do this year in Haskell. Perhaps if it gets too crazy, I'll revert to what I know better, but until now it's been fun.

I might start a library with the groups and addAll functions, seems I'm going to use them for every puzzle...

import Data.List.Split ( splitOn )
import Data.List ( intersect, nub )

main :: IO ()
main = do
    input <- readFile "input"
    putStrLn "day6"
    print $ solve1 input
    print $ solve2 input

solve1 :: String -> Int
solve1 a = 
  let grouped = groups a
      answers = map (filter (\x -> x `elem` ['a'..'z']) . nub) grouped
  in  addAll answers

solve2 :: String -> Int
solve2 a =
  let grouped = map lines $ groups a
      intersected = map (foldr1 intersect) grouped 
  in  addAll intersected

addAll :: [[a]] -> Int 
addAll = sum . map length 

groups :: String -> [String]
groups = splitOn "\n\n"
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
 
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
 
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
 
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
 
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