## DEV Community

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.

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

``````Ryan's Leaderboard: 224198-25048a19
``````

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
JavaScript 2
C 2
Rust 2
COBOL 1
Elixir 1
D 1
TypeScript 1

Merry Coding!

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)
``````

Kai • Edited

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

and I did something with bits, just for fun:

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. 👍

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")])
``````

Part 2:

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

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

willsmart

Python impl today. Prob not great python but works.

``````print(reduce(
lambda acc, v: acc + len(
set(v.replace('\n', ''))
),
0
))

print(reduce(
lambda acc, v: acc + len(reduce(
lambda acc, v: acc & v,
map(
lambda v: set(v),
v.split('\n')
)
)),
0
))
``````

Tomorrow will try something like prolog?!

Ruby, part 2:

``````require 'set'

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
``````

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()
}
``````

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 {
.iter()
.map(|group| group.chars().filter(|x| *x != '\n').collect())
.collect();
}

#[aoc(day6, part2)]
fn sum_group_common_questions(input: &Vec<String>) -> usize {
input
.iter()
.map(|group| {
let mut first_run = true;
group
.lines()
.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()
}
``````

Anna

COBOL

``````   IDENTIFICATION DIVISION.
PROGRAM-ID. AOC-2020-06-2.

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.

AT END MOVE 1 TO FILE-STATUS
NOT AT END PERFORM 003-PROCESS-RECORD

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
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.
PERFORM 004-INIT-VARIABLES.

005-PROCESS-ROW.
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
END-IF
END-PERFORM.
``````

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() {
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)
}
``````

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";
``````

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
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

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

addAll = sum . map length

groups :: String -> [String]
groups = splitOn "\n\n"
``````

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)
}
``````

### Part 2

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

public override int Solve(IEnumerable<PlaneGroup> input)
}
``````

### PlaneGroup

``````    public class PlaneGroup
{

{
}

.Count();

}
``````

Derk-Jan Karrenbeld

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

``````require 'benchmark'

def self.from_lines(lines)
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
.split(/\n\n/)

Benchmark.bmbm do |b|
b.report do
puts groups.sum { |group| GroupAnswers.from_lines(group.split(/\n/)).to_i }
end
end
``````

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);
}
``````

``````module Main where

import Control.Arrow (Arrow((&&&)))
import Data.List.Split (splitOn)
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:xs) = length . foldr intersect x \$ xs

main :: IO ()
main = print . (solveP1 &&& solveP2) . parseInput =<< readFile "./day6inp.txt"
``````

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

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

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

(map #(split % #"\n"))
(map #(map (fn [x] (set (clojure.core/char-array x))) %))
(map #(apply intersection %))
(reduce #(+ %1 (count %2)) 0))) ; Part 2
``````

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}")
``````