# 'Plane' the numbers in any base!

Inspired by this Numberphile video.

## What is planing?

In order to 'plane' a sequence of digits, you need to:

1. Identify the lengths of the 'runs' (adjacently repeated digits) in the sequence. Non-repeated digits count as runs of one.
2. Decrease the length of each run by one. Runs of length 1 become length 0.
3. Reconstruct the sequence, using the new lengths for the runs. Runs that are now length 0 are deleted altogether.

For example, planing the sequence $$\1, 1, 2, 3, 3, 3, 4, 4, 5, 6\$$ would give us:

1. The length of the runs is demonstrated below:
 1  1  2  3  3  3  4  4  5  6
|____||_||_______||____||_||_|
2    1     3      2    1  1

1. Decreasing the length of the runs gives us:
            1  1  2  3  3  3  4  4  5  6
|____||_||_______||____||_||_|
2   1     3      2    1  1
becomes:      1   0     2      1    0  0

(0s removed): 1  2  1

1. Using these lengths to reconstruct the original sequence, e.g. the first run is now length 1, the second length 0, etc., gets us our 'planed' sequence:
 1  3  3  4
|_||____||_|
1   2    1


Note that the planing operation can also have a strength: the above example used a strength of $$\1\$$, but a strength of $$\2\$$ would decrease the length of each run by 2, strength $$\3\$$ by 3, etc.. In cases where this would make the length of a run go negative, the length just becomes 0.

E.g. with the above example and a strength of $$\2\$$, we get:

Input:       1  1  2  3  3  3  4  4  5  6
|____||_||_______||____||_||_|
Run lengths:  2    1     3      2    1  1
Lengths - 2:  0   -1     1      0   -1 -1
Rounded to 0: 0    0     1      0    0  0

Output sequence:

3
|_|
1


## Challenge

Given a limit $$\n\$$, a strength $$\s\$$ and a base $$\b\$$, output the base-10 result of having 'planed' each of the numbers from 1 to $$\n\$$ in base $$\b\$$ with the strength $$\s\$$.

The basic steps to complete this are as follows:

1. Make a list $$\x\$$ of numbers 1 to $$\n\$$, including both 1 and $$\n\$$.
2. Convert each number $$\x\$$ in the list to base $$\b\$$.
3. Treating each digit of the base-$$\b\$$ number as an item of a sequence, plane the sequence with strength $$\s\$$.
4. Reading the sequence as a set of digits (or 0, in case of an empty sequence), convert the planed sequence back from base $$\b\$$ digits to a base 10 number.
5. Output this number and repeat for all values in $$\x\$$.

## Examples

Given $$\b\$$ = 4, $$\n\$$ = 250, and $$\s\$$ = 2:

[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,5,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,10,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]


$$\b\$$ = 2, $$\n\$$ = 112, and $$\s\$$ = 3:

[0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,3,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0]


$$\b\$$ = 12, $$\n\$$ = 1024, and $$\s\$$ = 1:

[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,1,13,1,1,1,1,1,1,1,1,1,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,2,2,26,2,2,2,2,2,2,2,2,2,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,3,3,3,39,3,3,3,3,3,3,3,3,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,4,4,4,4,52,4,4,4,4,4,4,4,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,5,5,5,5,5,65,5,5,5,5,5,5,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,6,6,6,6,6,6,78,6,6,6,6,6,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0]


## Rules

• This is , so the shortest code in bytes wins.
• Standard loopholes apply.
• I/O may be taken/given in any acceptable, valid format. Each number in the sequence should be delimited in some way by a non-numeric digit, but the precise delimiting is up to you.
• You may assume $$\b>1\$$, $$\n>1\$$, and $$\s>0\$$. You may also assume reasonable upper bounds for these, depending on the capacity for number types in your language.
• If you wish, input arguments may have an offset (e.g. an input of $$\1\$$ to $$\b\$$ represents base 2), but this must be stated in your answer and should not impede the ability of your code to cover the whole range of allowed inputs.
• Output may be given as floats, ints, strings, etc., but all values must represent a numeric, base-10 value - so e.g. no NaNs in place of 0s.

### Test code

For reference, here is the (shoddily-written and ungolfed) code I used to generate the above examples (in console JS):

// called as f(length, base, strength)
const f = (l, b, s) => {
return JSON.stringify(
Array(l)
.fill(0)
.map((e, i) => i + 1)
.map(e =>
e.toString(b)
.split('')
.reduce((a, e, i, A) => (A[i - 1] && A[i - 1] === e) ?
[...a.slice(0, -1), [a.slice(-1)[0][0], a.slice(-1)[0][1] + 1]] :
[...a, [e, 1]], [])
.map(e => [e[0], Math.max(0, e[1] - s)])
.map(e => e[0].repeat(e[1]))
.join('')
)
.map(e => parseInt(e, b) || 0),
null,
1
)
}


Note that this implementation is not a complete/valid one, as it only works for $$\b \le 36\$$. As per the instructions, complete answers should be able to handle any base in theory, up to the integer limit of your language.

• "Convert each number x in the list to base b." but what is x?
Jun 3, 2021 at 12:30
• @Adám As in, 'for each number, calling the current number x, in the list, convert x to base b'. Jun 3, 2021 at 12:33
• @user True - I should probably note that. Yes, in theory, a 'real' answer should be able to handle bases >36, since the conversion is a purely internal process - no I/O has to happen in bases other than 10. So it's just my 'test' implementation that's flawed - not an indication of an added constraint. Jun 3, 2021 at 12:42
• When you say "If you wish, input arguments may have an offset", does that mean we can return the results for [0,n-1] instead of for [1,n]? Jun 3, 2021 at 21:15
• @Shaggy - unfortunately, no - you may, for instance, request to be given n-1 instead of n, but the actual output must function equivalently. So since starting at 0 would mean that the core output of the program has changed, I wouldn't consider it a 'valid' interpretation of the rules. Tl;dr [1, n-1] is fine, [0, n-1] is not. Jun 3, 2021 at 22:30

# Japt, 11 bytes

How is Japt winning this (so far) despite the need for an essentially unnecessary byte in the ,? It's making me feel like I've done something wrong!

õÈìV,ÈòÎcsW


Try it

õÈìV,ÈòÎcsW     :Implicit input of integers U=n, V=b & W=s
õ               :Range [0,U]
È              :Map
ìV            :  Convert to base V digit array
,           :  Does nothing (more accurately, prevents the V from becoming the first parameter of the next function)
È          :  Pass through the following function and convert back to decimal
ò         :    Partition between elements where
Î        :      The sign of their difference is truthy (not zero)
c       :    Flat map
sW     :      Slice off the first W elements


Or, to avoid that pesky comma, we can reverse the second 2 inputs and do this, where NÌ is the last element of the array of all inputs:

õÈìNÌÈòÎcsV


Try it

# APL (Dyalog Extended), 32 23 bytes (SBCS)

Full program. Prompts for $$\n\$$ then $$\s\$$, then $$\b\$$.

∊⎕(⊣⊥∘∊⎕↓¨⊤⊂⍨1,2≠/⊤)¨⍳⎕


Try it online!

⎕ prompt for $$\n\$$

⍳ generate $$\x\$$, the indices 1 through $$\n\$$

⎕()¨ establish the following tacit function, then apply it using each element of $$\x\$$ as right argument, and a prompted-for $$\b\$$-value as left argument:

⊤ encode $$\x_i\$$ in base $$\b\$$

2≠/ pairwise difference

1, prepend 1, giving a mask of starting positions of runs

⊤⊂⍨ use that to partition $$\x_i\$$ re-encoded in base $$\b\$$

⎕↓¨ prompt for $$\s\$$ and use that to drop the first that many elements from each run

∘∊ enlist (flatten), then:

⊣⊥ evaluate in base $$\b\$$

∊ enlist (flatten)

# 05AB1E, 12 bytes

LIвεγIδ.$˜²β  Try it online! L # push range [1 .. n] Iв # convert each number to base b (represented as lists of digits) ε # for each list of digits: γ # group equal adjacent digits Iδ.$     #   remove s digits from each group
˜    #   flatten into a single list of digits
²β  #   convert back from base b

• Lol, you beat me to it. I had just finished this exact program and was about to write an answer. :) Obvious +1 from me. Jun 3, 2021 at 13:17

# Jelly, 12 bytes

Ḋ¡€F
b€ŒgÇ€ḅ


Try it online!

## How it works

Ḋ¡€F - Helper link. Takes a list of grouped digits D
€  - Over each group:
¡   -   Do the following S times:
Ḋ    -     Dequeue; remove the first element
F - Flatten

b€ŒgÇ€ḅ - Main link. Takes n on the left and b on the right
b€      - Convert each integer 1 to n to base b
Œg    - Over each, group adjacent equal digits
Ç€  - Run the helper link over each list of groups
ḅ - Convert each back from base b

• Ah right, quicks take the last argument which lets you tacitly get the 5th parameter without actually using ⁵. Clever :P Jun 3, 2021 at 15:06
• @hyper-neutrino Kind of annoyingly, it's actually the same length as the one liner, but I think this one is more elegant :) Jun 3, 2021 at 15:49
• Oh, you need the ⁵ to one-line it? That would explain why my attempt at golfing wasn't working. Unfortunate :/ but yeah I think this looks much nicer :P Jun 3, 2021 at 15:52
• @hyper-neutrino Yeah, ¡ only takes the last input if either 1) it only gets one link (Ḋ¡), or if the second link is a nilad. Otherwise, it'd be read as ŒgḊ¡, not Ḋ⁵¡ Jun 3, 2021 at 15:54

# R, 107 104 bytes

function(x,b,s)Map(function(n,l=0:n,r=rle(n%/%b^l%%b))sum(rep(r$v,t<-pmax(r$l-s,0))*b^(1:sum(t)-1)),1:x)


Try it online!

Goes out-of-range for the last test-case: add +7 bytes to extend the range of amenable calculations and fix this.

# Vyxal, 14 12 bytes

ɾvτƛ⁰nĠvȯf¹β


Try it Online!

ɾ            # 1...n
vτ          # Foreach, base convert
ƛ         # Foreach...
⁰        # Strength
nĠ      # Each one grouped
vȯ    # Trim each
f   # Flatten
¹β # Convert back


# JavaScript (ES6), 92 bytes

Saved 1 byte thanks to @Shaggy

Expects (b)(s)(n).

b=>s=>F=n=>n?[...F(n-1),(t=0,p=g=x=>x?g(x/b|0,x%=b,x==p?k++:k=1,p=x,k>s?t=t*b+x:0):t)(n)]:[]


Try it online!

### Commented

b => s =>               // outer functions taking b and s
F = n =>                // F = inner recursive function taking n
n ?                     // if n is not equal to 0:
[ ...F(n - 1),        //   do a recursive call to F with n - 1
(                   //   and append the next value to the array:
t = 0,            //     t = final value converted back from base b
p =               //     p = previous term in the sequence
g = x =>          //     g is a recursive function taking x
x ?             //       if x is not equal to 0:
g(            //         do a recursive call to g ...
x / b | 0,  //           ... using floor(x / b)
x %= b,     //           reduce x modulo b
x == p ?    //           if it's equal to the previous term:
k++       //             increment k
:           //           else:
k = 1,    //             reset k to 1
p = x,      //           update the previous term p to x
k > s ?     //           if k is greater than the strength:
t = t * b //             update t to t * b + x
+ x   //             (i.e. add the digit x in base b)
:           //           else:
0         //             do nothing
)             //         end of recursive call
:               //       else (x = 0):
t             //         return t
)(n)                //     initial call to g with x = n
]                     //   end of array
:                       // else (n = 0):
[]                    //   stop the main recursion

• @Shaggy Err ... right! :p Thank you. Jun 3, 2021 at 15:36
• Let's pretend that never happened! 😂 Jun 3, 2021 at 20:30
• A byte off for you Jun 3, 2021 at 23:59
• @Ausername I didn't do that because I'm not 100% sure it's safe if the sequence starts with a zero. Jun 4, 2021 at 0:01
• @Arnauld Oh, true. It seems to work, but hard to tell. Jun 4, 2021 at 0:03

# K (ngn/k), 28 bytes

{(y/*'(1=#'?:')#z':y\)'1+!x}


Try it online!

Takes three arguments: n => x, b => y, and s => z. s needs to be offset by +1 (i.e. to apply a strength of 2, pass in 3).

• (...)'1+!x call the code in parenthesis on each value from 1..n
• y\ convert each value to base b, returning a list of digits
• z': take s-length rolling windows of the converted digits
• (1=#'?:')# filter out rolling windows containing more than one distinct digit
• *' take the first digit of each remaining rolling window
• y/ convert back to original base

# Jelly, 12 11 bytes

b€Œr_⁵Ż¤Œṙḅ


Try it online!

Takes three arguments in the form n b [s].

-1 byte thanks to @cairdcoinheringaahing

## Explanation

b€Œr_⁵Ż¤Œṙḅ   Main dyadic link
b€            Convert each to base b (implicitly casting n to [1..n])
Œr          Run-length encode
_         Subtract [vectorized]
¤        (
⁵            The third argument
Ż           Prepend a 0
¤        )
Œṙ    Run-length decode
ḅ   Convert from base b

• I'm helping you outgolf me, but if you take s as [s], you can save 1 byte Jun 3, 2021 at 18:16
• @cairdcoinheringaahing Is that an allowed input method? Great! I take it that we have a common goal to outgolf/match other golfing languages ;) Jun 3, 2021 at 19:02
• I'd just go for b€Œr_⁵Œṙ+⁵ḅ which needs no weird wrapping of inputs. (-n is treated as a run length of 0.) - TIO Jun 5, 2021 at 13:37

# Scala, 155 bytes

b=>s=>1.to(_)map{y=>(0+:Seq.unfold(y){q=>Option.when(q>0)(q%b,q/b)}:\(0,0,0)){case(d,(c,p,a))=>if(d==p)(c+1,d,a)else(1,d,(a/:Seq.fill(c-s)(p))(_*b+_))}._3}


Try it in Scastie!

It's been a while since I wrote an answer as complex as this. Takes (b)(s)(n). Explanation coming soon.

Ungolfed:

b => s => n =>
//Make a range [1,n]
1.to(n) map { y => //Perform these operations on each y
//Build a list of digits in base b (reversed)
q =>                        //q is the quotient at each iteration
//If q is still greater than 0, add another digit
Option.when(q > 0)(
q % b,   //The next digit is q mod b
q / b    //The next quotient is the floor of q÷b
)
}
//Plane and convert back to decimal here
((0 +: toBaseB) //First prepend a 0 so that the last digit of toBaseB is counted
:\ (0, 0, 0) //First accumulator (count 0, prev item 0, sum 0)
) {
//d is the current digit, p is the previous digit,
//c is the current run length,
//and a is the sum (partially converted to decimal)
case (d, (c, p, a)) =>
if (d == p)     //If the run's still going
(c + 1, d, a) //Increase the run length
else            //Otherwise
(1, d,        //Set the run length to 1 and set p to the new d
//Add the current run to the sum
//Make a sequence of (c-s) p's
//Convert to decimal, and add the previous sum (a) to that
(a /: Seq.fill(c - s)(p))(_ * b + _)
)
}._3 //Only get the sum (planed sequence converted to decimal)
}


# Jelly, 14 bytes

RbŒgṫ⁵$Ḋ$€F$€ḅ  Try it online! RbŒgṫ⁵$Ḋ$€F$€ḅ  Main Link; takes [n, b, s]
R               Range from 1 to n
b              Base convert (each) into base b
Œg            Group equal runs (vectorizes)
(    )-$€ For each number ( )-$       - For each run
ṫ⁵\$           - Tail; remove the first s-1 items
Ḋ          - Dequeue; remove the first element
F     - Flatten
ḅ  Convert (each) back from base b


# Husk, 11 bytes

möB⁰ṁ↓²gB⁰ḣ


Try it online!

m           # map the following function
ḣ # across all the integers from 1 to arg3
# the 'plane' function:
ö          # composition of 4 commands
B⁰  # get the digits in base arg2
g    # group equal values
ṁ↓²     # drop the first arg1 elements from each group, and flatten
B⁰        # interpret as digits in base arg2


# Charcoal, 31 bytes

ＮθＮηＩＥＥＮ↨⊕ιη↨Φι∧¬‹μθ⬤…⮌…ιμθ⁼νλη


Try it online! Link is to verbose version of code. Takes inputs in the order s, b, n. Explanation:

ＮθＮη


Input s and b.

   Ｎ                        Input n
Ｅ                         Map over implicit range
⊕ι                     Current index, 1-indexed
↨  η                    Convert to base b
Ｅ                          Map over list
ι                 Current base conversion
Φ                  Filtered where
μ             Current position
¬‹ θ            Is not less than s
∧                Logical And
…⮌…ιμθ     Last s elements
⬤           All satisfy
ν   Inner element
⁼    Equal to
λ  Outer element
↨                 η Convert from base b
Ｉ                           Cast to string
Implicitly print each result on its own line