# Problem

Assume you have a single 7-segment display without a decimal point, so 7 "lines" that can be labelled A through G as seen here.

This display will only show the numbers from 0 to 9 as usual, like so:

Any time this display changes from one number to another, some of its lights will have to change their state from on to off or vice versa, or they can simply stay on, e.g. in the case of 1 -> 2, segment B can stay on, while segments A, G, E, D need to turn on, and C needs to turn off; for the change 6 -> 8, only segment B needs to be changed from off to on.

Your task is to take a sequence of numbers as reasonably formatted input and output the amount of on and off changes as the display shows each number in the sequence in input order. You should output a separate score for "on" and "off" in a reasonable format. A light that stays on between numbers shall not be counted towards either on or off. The initial state of the display is all lights off, so the first number will always count as only "on" changes. The final state will be the last number displayed.

# Test cases

123 -> 7, 2 (7 on, 2 off)
111 -> 2, 0 (2 segments on, then no changes)
987 -> 7, 4
2468 -> 11, 4 (thanks to @ovs for catching a mistake here!)


This is , so the shortest code wins. Also, this is my first post here, so I hope I made no mistakes for my submission :)

# APL (Dyalog Extended), 34 bytes

Anonymous tacit prefix function taking a list of numbers. Requires 0-based indexing (⎕IO←0).

2(<⌿,⍥(≢⍸)>⌿)0⍪11⎕DR'~0my3[_p␡{'⊇⍪


Try it online!

⍪ change list into column matrix

'~0my3[_p␡{'⊇ map to ASCII characters with corresponding pit pattern

11⎕DR get corresponding binary Data Representation as 8-column table

0⍪ prepend an all-zero row

2() apply the following tacit function with 2 as left argument:

>⌿ indicate where a 1 is above a 0 (lit. greater-than reduction over vertical windows of size 2)

<⌿,⍥() concatenate with where-0-is-above-1 as follows:

⍸ list of indices where there are 1s

≢ tally the indices (this gives the count of 1s)

# 05AB1E, 40 35 bytes

•z]∍1:pIªy•2в7ôDIнè(sã€øÆIü2è«˜®1‚¢


-5 bytes thanks to @Steffan

Explanation:

•z]∍1:pIªy• # Push compressed integer 1098931065668123279355
2в        # Convert it to base-2 as list: [1,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,1,1,1,1,0,1,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1]
7ô      # Split it into parts of size 7: [[1,1,1,0,1,1,1],[0,0,1,0,0,1,0],[1,0,1,1,1,0,1],[1,0,1,1,0,1,1],[0,1,1,1,0,1,0],[1,1,0,1,0,1,1],[1,1,0,1,1,1,1],[1,0,1,0,0,1,0],[1,1,1,1,1,1,1],[1,1,1,1,0,1,1]]
D           # Duplicate it
Iн         # Push the first digit of the input
è        # Use it to index into this list of lists
(       # Negate all 1s to -1s in the list
s           # Swap so the duplicated list is at the top again
ã          # Create all possible pairs using the cartesian power with itself
€ø        # Zip/transpose each inner pair of lists; swapping rows/columns
Æ       # Reduce each inner-most pair by subtracting
I      # Push the input-integer again
ü2    # Create overlapping pairs of it
è   # Use that to index into the other list
«           # Merge the two lists together
˜          # Flatten it
®1‚       # Push pair [-1,1]
¢      # Count the amount of -1s and 1s in the list
# (after which this pair is output implicitly as result)


See this 05AB1E tip of mine (sections How to compress large integers? and How to compress integer lists?) to understand why •z]∍1:pIªy• is 1098931065668123279355 and •z]∍1:pIªy•2в is [1,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,1,1,1,1,0,1,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1].

• 35 bytes? Jun 2 at 16:27

# JavaScript (Node.js), 92 bytes

Expects a list of digits. Returns [off, on].

Contains some unprintable characters.

a=>a.map(n=>(g=k=>k&&g(k/2,o[q&1]+=k&1,q/=2))(x^(x=q=Buffer('?[Ofm}o')[n])),o=[x=0,0])&&o


Try it online!

### How?

Given the segment bitmask $$\x\$$ of the previous digit and the segment bitmask $$\q\$$ of the new digit, we compute $$\k=x\operatorname{XOR}q\$$ and use the helper function $$\g\$$ to update the off and on counters which are stored in the array $$\o[\:]\$$:

g = k =>       // k = result of the XOR between the 2 bitmasks
k &&           // stop when k = 0, which actually happens because
// of arithmetic underflow because we use k / 2
// rather than k >> 1 to save a byte
g(             // otherwise, do a recursive call:
k / 2,       //   pass k / 2 for the next iteration
o[q & 1] +=  //   select the counter according to the LSB of q
k & 1,     //   increment it if the LSB of k is set
q /= 2       //   divide q by 2
)              // end of recursive call


# JavaScript (ES6), 99 bytes

An alternate version without Buffer().

The order of the segment bits and the XOR value were chosen to optimize the overall length of the lookup code.

a=>a.map(n=>(g=k=>k&&g(k/2,o[q&1]+=k&1,q/=2))(x^(x=q=[6,90,44,8,80,1,5,74,4][n]^123)),o=[x=0,0])&&o


Try it online!

• -2 bytes for your second answer using '}!Ws+z~1\x7F'.charCodeAt(n) Jun 2 at 23:21
• @MatthewJensen You're right. But the purpose of my 2nd version was to optimize the lookup with an array of numbers. (Maybe I should have said without a string rather than without Buffer().) Jun 3 at 6:22

# Rust, 147 bytes

|a|{let(b,mut e,f)=(b"~0my3[_p{",0,|a,b:u8,c:u8|a+(b^c&b).count_ones());a.iter().fold((0,0),|(l,r),&d|{let c=(f(l,b[d],e),f(r,e,b[d]));e=b[d];c})}


Try it online!

Note that the space in the byte string is really the ASCII delete character (code point 0x7f). The basic idea is that the set bits are stored as a mask packed in a byte string, and the changed bits are detected with some bit twiddling, similar to the other answers from practical langs (although not the same- we've all taken different approaches for the same general technique, at least so far).

• 136 bytes. Jun 8 at 9:11

# PARI/GP, 107 bytes

-4 bytes and fixed a bug thanks to @Arnauld.

a->d=127;sum(i=1,#a,[g(c=[2,91,40,9,81,5,4,27,0,1][1+a[i]],d),g(d,d=c)])
g(a,b)=sumdigits(bitand(-1-a,b),2)

Attempt This Online!

# Python, 116 112 bytes

-4 bytes thanks to @Kevin Cruijssen

def f(s):
A=X=Y=0
for b in s:B=b'~0my3[_p\x7f{'[b];X+=(~A&B).bit_count();Y+=(A&~B).bit_count();A=B
print(X,Y)

Attempt This Online!

• Both bin(...).count("1") can be (...).bit_count() for -4 bytes. Jun 2 at 18:09

# C (gcc), 136 134 bytes

#define B+=__builtin_popcount((d^c)
i;c;d;x;y;f(a,n)int*a;{for(i=x=y=d=0;n--;d=c)c="{HW^l>?X\x7f~"[a[i++]],x B&c),y B&d);*a=x;a[1]=y;}


Try it online!

Saved 2 bytes thanks to ceilingcat!!!

Inputs a pointer to an array of digits to display and its length (because C pointers carry no length info).
Return the number of lights turned on and the number turned off through the input pointer.

# Vyxal, 41 bytes

k-»>xWƛo4ø₅=»b7ẇ:?hiN$2ẋΠv∩vvƒ-?S2l⌊İJfvO  ## How? k-»>xWƛo4ø₅=»b7ẇ:?hiN$2ẋΠv∩vvƒ-?S2l⌊İJfvO
k-                                         # Push [-1, 1] (will be used later)
»>xWƛo4ø₅=»                              # Push compressed integer 1098931065668123279355
b                             # Convert to binary as a list
7ẇ                           # Split into chunks of size 7
:                          # Duplicate
?h                        # Push the first digit of the input
i                       # Index it into the list
N                      # Negate: replace all 1s with -1
\$                     # Swap so the list of size-7 chunks are at the top again
2ẋΠ                  # Cartesian product with self
v∩                # For each, transpose
vvƒ-            # For each inner pair, reduce by subtraction
?S          # Push stringified input
2l        # Get all overlapping pairs
⌊       # Convert them to integers
İ      # Index these into the other list
J     # Join the two lists together
f    # Flatten
vO  # Count the number of -1s and 1s in this list


# Charcoal, 44 bytes

ＩＥ²Ｌ⭆⪪”←＆⊞YJΣζ⁹)Ｇ；⧴4L⁴↷U³”¶Φθ‹⁼ι№λ§⁺ θξ⁼ι№λν


Try it online! Link is to verbose version of code. Explanation:

  ²                         Literal integer 2
Ｅ                          Map over implicit range
...                   Compressed string
⪪                      Split on
¶                  Literal newline
⭆                       Map over elements and join
θ                Input string
Φ                 Filtered where
ι             Outer value
⁼              Equals
№            Count of
Literal space
⁺         Concatenated with
θ       Input string
§          Indexed by
ξ      Inner index
λ           In current string piece
‹               Is less than
ι    Outer value
⁼     Equals
№   Count of
ν Current input character
λ  In current string piece
Ｌ                        Take the length
Ｉ                           Cast to string
Implicitly print


The compressed string splits to the list  1237,  14,  56,  017,  134579,  147,  2 which for each segment contains the digits (including the initial "off" state as a "digit") where the segment is off.