The language

Splinter is an esoteric programming language where data is stored in 'splinters'. There are three different types of syntax:

  • Simple character output: \character, outputs a character
  • Splinter storage: (splinter name){code}, stores the code into the splinter with the correct name for later use as is. Note that these can be nested.
  • Splinter reference: (splinter name), evaluates the code of the splinter with the correct name.

(splinter name) just means the name, with no parentheses.

Here's an interpreter.

Splinter's computational class is a push-down automata, meaning it's one step behind Turing-Completeness. There are 26 splinters, one for each capital letter.

Because of the ability to store data in these 'splinters', Splinter programs can be interesting to compress.

Your challenge is to create a program that, when given a string as input, outputs a as-short-as-possible Splinter program that outputs that.


Your score is the combined length of your program run on the intended text of all of these:

Never gonna give you up
What a wonderful world
Hamlet's monologue
Something monkey-related
Gettysburg Adress
There was an old lady
House that Jaxk built
Buffalo buffalo buffalo

  • \$\begingroup\$ I think your link to Bears is wrong. Not sure what it's meant to be, but it's a duplicate. \$\endgroup\$
    – hyper-neutrino
    Jul 12 at 23:06
  • \$\begingroup\$ Did you mean to link (The other day) I met a bear for Bears? \$\endgroup\$ Jul 12 at 23:10
  • \$\begingroup\$ @cairdcoinheringaahing Yes, oops \$\endgroup\$
    – emanresu A
    Jul 13 at 3:35

Excel plus VBA, score 8210

VBA code

Sub splinter()

    substart = Timer()
    Application.ScreenUpdating = False
    [F2] = [E2]
    For i = 65 To 90
        maxsavings = 0
        maxsavingsj = 0
        [L1] = i
        [G1] = 255
        maxmatch = [j1]
        For j = 2 To 254
            [G1] = j
            If [j1] = maxmatch Then Exit For

        If [L2] > maxsavings Then
                maxsavings = [L2]
                maxsavingsj = j
            End If
        Next j
        If maxsavings <= 0 Then Exit For
        [G1] = maxsavingsj
        [F2] = [M2]
    Next i
    Application.ScreenUpdating = True
    MsgBox (Timer() - substart & " seconds")
End Sub

Excel Cells

O4:O31= {\,!,A..Z}

Link to Spreadsheet

VBA doesn't work with Office online so the spreadsheet will have to be downloaded to run the macro.

The spreadsheet is set up where cell I1 contains the string of length G2 that will result in the greatest shortening of the string. Cell M2 shows the result of replacing the string in I1 with the letter in M1.

The VBA loops through the letters A through Z, looks for the value of G1 that results in the greatest reduction in size. There is a special case if it reaches the string limit of 255. After it finds the best change it copies the result in M2 to cell F2.

One quirk of Excel is certain matches are not case sensitive so I had to use !(letter) for capitals and convert back to \ at the very end.

  • Rick Roll 772
  • Wonderful 602
  • Hamlet 1553
  • Monkey 865
  • Lincoln 1410
  • Old Lady 462
  • Bear 1932
  • Jack 573
  • Buffalo 41

Python 3, score: 11069 10495

This algorithm is very slow but I'm pretty sure it's close to optimal depending on the parameters MIN_SPLINTER_SIZE and MAX_SPLINTER_SIZE, though it takes quite long even when only set to 2, 120

import sys
import dataclasses

class Splinters:
    The splinters class is the basic source code unit. Variables is a list of variables in order. Each variable is a
    string formatted like data. Variables may only contain variables with lower indexes than themselves.

    Data is a list. Each item may either be a literal string or a index of a variable to include.
    variables: list
    data: list

    def reduce_text(self, text, pipe):
        Print a section of test to the pipe. Variables will be replaced by the appropriate numbers. Should only
        be called from result()"""
        for text_part in text:
            if isinstance(text_part, str):
                pipe.write(''.join('\\' + i for i in text_part))
                pipe.write(chr(ord('A') + text_part))

    def result(self, pipe):
        Output the formatted code to the pipe
        for index, variable in enumerate(reversed(self.variables)):
            pipe.write(f'{chr(ord("A") + len(self.variables) - 1 - index)}{{')
            self.reduce_text(variable, pipe)
        self.reduce_text(self.data, pipe)

MINIMUM_SPLINTER_LENGTH = 2  # Minimum length of a splinter
MAX_SPLINTER_LENGTH = 120  # Maximum length of a splinter

def add_to_tree(tree, value):
    Adds a single string to a tree
    :param tree: The tree dict object to add the string too
    :param value: The string value to add to the tree
    :return: None
    k = tree
    for char in value:
        ok = k
        if char in k:
            n, k = ok[char]
            ok[char] = n + 1, k
            ok[char] = 1, {}
            n, k = ok[char]

def get_from_tree(tree, value):
    Get a single value from the tree. Fixes duplicate items
    :param tree: The tree value
    :param value: The string to look up
    :return: How often this string has been added to the tree
    k = tree
    n = 0
    for char in value:
        if char in k:
            n, k = k[char]
            return 0
    return n - sum(i[0] for i in k.values())

def add_string_to_tree(tree, string):
    Adds every substring of a string to the tree
    :param tree: The tree
    :param string: The string to add
    :return: None
    for substring in get_substrings(string):

def recursively_add_splinter_to_dict(tree: dict, splinters: Splinters):
    Adds every string in a splinter to a tree
    :param tree: The tree to add it to
    :param splinters: A splinter object to add to the tree
    :return: None
    for variable in splinters.variables:
        for string in variable:
            if isinstance(string, str):
                add_string_to_tree(tree, string)
    for string in splinters.data:
        if isinstance(string, str):
            add_string_to_tree(tree, string)

def get_substrings(string):
    Iterate over all valid substrings of a string
    :param string: The string
    :return: A generator over substrings between MIN_SPINTER_LENGTH and MAX_SPLINTER_LENGTH
    for index, char in enumerate(string):
        for previous_value in range(max(0, index - MAX_SPLINTER_LENGTH), index - MINIMUM_SPLINTER_LENGTH + 2):
            yield string[previous_value:index + 1]

def get_splinter_substrings(splinter):
    Iterate over all possible substrings in a splinter
    :param splinter: The splinters object
    :return: A generator over all the substrings
    for variable in splinter.variables:
        for string in variable:
            if isinstance(string, str):
                yield from get_substrings(string)
    for string in splinter.data:
        if isinstance(string, str):
            yield from get_substrings(string)

def get_best_splinter(splinters: Splinters):
    Find the best splinter in terms of bytes saved
    :param splinters: The splinters object
    :return: The most optimal substring to make a variable
    tree = {}
    recursively_add_splinter_to_dict(tree, splinters)

    best_substring = None
    best_score = 0
    for substr in get_splinter_substrings(splinters):
        nrof_items = get_from_tree(tree, substr)
        if nrof_items >= 2:
            score = (nrof_items - 1) * len(substr)
            if score > best_score:
                best_substring = substr
                best_score = score
                best_number = nrof_items
    if best_score <= 3:
        return None
    return best_substring

def reduce_text(text, substring, index):
    Replaces all instances of substring with the variable
    :param text: the text to look in
    :param substring: text to replace
    :param index: what it should be replaced with
    if not isinstance(text, str):
        return text,
    elif substring in text:
        return text[:text.index(substring)], index, *reduce_text(text[text.index(substring) + len(substring):],
                                                                 substring, index)
        return text,

def reduce_splinter(splinters: Splinters, substring):
    Replaces all instances of a substring in a splinters object with a variable, and also add the variable.
    :param splinters:
    :param substring:
    :return: None, modifies splinters in place
    new_variable_index = len(splinters.variables)
    for index, variable in enumerate(splinters.variables):
        splinters.variables[index] = [y for ys in variable for y in reduce_text(ys, substring, new_variable_index)]
    splinters.data = [y for ys in splinters.data for y in reduce_text(ys, substring, new_variable_index)]

def get_splinters(data, pipe):
    Performs 26 iterations of get_best_splinter, and outputs the result to pipe
    :param data: The input text
    :param pipe: The pipe to write the output to
    :return: None
    splinters = Splinters(
        data=[data, ]
    for i in range(26):
        best_splinter = get_best_splinter(splinters)
        if best_splinter is None:
        reduce_splinter(splinters, best_splinter)
    return splinters.result(pipe)

if __name__ == "__main__":
    with open(sys.argv[1]) as f:
        data = f.read().strip()
    get_splinters(data, sys.stdout)

Score breakdown:

processing:  hamlet.txt
     hamlet.txt 2013
processing:  wonderfull_world.txt
     wonderfull_world.txt 747
processing:  jaxt_house.txt
     jaxt_house.txt 754
processing:  getysburg.txt
     getysburg.txt 1803
processing:  give_you_up.txt
     give_you_up.txt 1028
processing:  bear.txt
     bear.txt 2472
processing:  monkey.txt
     monkey.txt 1103
processing:  old_lady.txt
     old_lady.txt 533
processing:  buffalo.txt
     buffalo.txt 42
total:  10495

Edit: Saved 601 bytes with bug when a splinter appeared near the end of a string

  • 2
    \$\begingroup\$ Welcome to Code Golf! Very impressive first answer :) \$\endgroup\$
    – hyper-neutrino
    Jul 13 at 15:44

Jelly, 76 bytes, score 8611


Try it online!

A full program taking a string and printing the Splinter program. The TIO link can be used for all 9 required test cases by modifying the first argument from 1 to 9 for the relevant case.

  1. We're no strangers to love -> 997
  2. I see trees of green, red roses, too, -> 611
  3. To be, or not to be: that is the question: -> 1553
  4. sandor weores monkeyland -> 864 l
  5. Four score and seven years ago our fathers brought forth -> 1400
  6. There was an old lady who swallowed a fly. -> 489
  7. The other day, -> 1965
  8. This is the house that Jack built. -> 691
  9. Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo -> 41
  • \$\begingroup\$ Nice! Since this isn't scored in bytes, feel free to ungolf it a bit if that makes it faster. \$\endgroup\$
    – emanresu A
    Jul 13 at 21:20
  • \$\begingroup\$ I'm curious how this works. Would you be willing to name the algorithm used or link to an article or something similar that explains the high efficiency? \$\endgroup\$
    – mousetail
    Jul 14 at 6:40

Python 3, score: 24252

# splinter

def splinter(inp):
    print(*["\\" + x for x in inp], sep = "")
splinter("Hello, World!")

Try it online!

Just a baseline program, appends a "\" to each character in the input, although for the input, I noticed the bear program was just a duplicate of the old lady one, so I omitted that. If I got the score wrong, let me know and I'll fix it.

(Edit) Updated to include bear text + I doubled the char count not the byte count because the Gettysburg text has some unicode in it.


Python 3, score: 18074

import sys

data = sys.stdin.read()
o = "".join("\\" + c for c in data)

splinters = ""
kt = 65

for _ in range(26):
	for l in range(len(data) // 2, -1, -1):
		for k in range(len(data) - l):
			q = data[k:k + l]
			if "\0" in q: continue
			if data.count(q) > 1:
				spc = "".join("\\" + c for c in q)
				splinters += chr(kt) + "{" + spc + "}"
				data = data.replace(q, "\0")
				o = o.replace(spc, chr(kt))
				kt += 1

print(len(splinters + o), file = sys.stderr)

print(splinters + o)

Try it online!

  • NGGYU: 2103
  • WAWW: 944
  • TBONTB: 2724
  • MONKE: 1579
  • 4S&7YA: 2525
  • TWAOL: 1343
  • BEAR: 4245
  • TITHTJB: 2540
  • BBBBBBB: 71

This is just an extremely naive solution that takes the longest block and splinters it each time. No nested splintering is implemented. It's already quite slow.


Excel, score 13524


Link to Spreadsheet

This counts the occurrence of each character, assigns the 26 most common to splinters and the rest are set to \character.

  • Rick Roll 2049
  • Wonderful 766
  • Hamlet 1685
  • Monkey 1082
  • Lincoln 1602
  • Old Lady 1304
  • Bear 2437
  • Jack 2496
  • Buffalo 103

R, score 8298

find_best_split_for_length <- function(text, length, randomise = FALSE) {
  starts <- 1:(nchar(text) - length + 1)
  backslashes <- cumsum(nchar(strsplit(text, "\\", fixed = TRUE)[[1]]) + 1)
  backslashes <- backslashes[-length(backslashes)]
  starts <- starts[!(((starts - 1) %in% backslashes) | ((starts + length - 1) %in% backslashes))]
  text_split <- vapply(starts, function(start)substr(text, start, start + length - 1), character(1))
  groups <- split(seq_len(length(text_split)), text_split)
  group_savings <- (lengths(groups) - 1L) * vapply(groups, function(group)nchar(text_split[[group[1]]]) - 1L, integer(1))
  best_saving <- max(c(group_savings, 0L))
  which_best <- which(group_savings == best_saving)
  if (randomise) {
    best_split <- which_best[sample(length(which_best), 1)]
  } else {
    best_split <- which_best[length(which_best)]
  list(saving = best_saving, split = names(best_split))

find_best_split <- function(text, max_length = 70, randomise = FALSE) {
  splits <- lapply(1:min(nchar(text) %/% 2, max_length), find_best_split_for_length, text = text, randomise = randomise)
  split_savings <- vapply(splits, function(split) split$saving, integer(1))
  best_splits <- which(split_savings == max(split_savings))
  if (randomise) {
    best_split <- best_splits[sample(length(best_splits), 1)]
  } else {
    best_split <- best_split[length(best_split)]

splinter_encode <- function(text, max_length = 70, randomise = FALSE) {
  text <- gsub("(.)", "\\\\\\1", text)
  i <- 1
  done <- FALSE
  while (i <= 26 && !done) {
    best_split <- find_best_split(text, max_length, randomise = randomise)
    if (best_split$saving < 5) {
      done <- TRUE
    } else {
      # cat(best_split$split, "\n\n", sep = "")
      placeholder <- intToUtf8(64 + i)
      text <- paste0(placeholder, "{", best_split$split, "}", gsub(best_split$split, placeholder, text, fixed = TRUE))
      i <- i + 1
    # cat(nchar(text), "\n\n", sep = "")

splinter_multilength_encode <- function(text, max_lengths = c(seq(10, 70, 5))) {
  encodes <- vapply(max_lengths, splinter_encode, text = text, character(1))

splinter_randomised_encode <- function(text, min_max_length = 10, max_length = 70, n_tries = 10, seed = NULL) {
  if (!is.null(seed)) {
  seeds <- sample(2^31, n_tries)
  encodes <- sapply(seeds, function(cur_seed) {
    splinter_encode(text, max_length = if (min_max_length == max_length) min_max_length else sample(min_max_length:max_length, 1), randomise = TRUE)
  which_smallest <- which.min(nchar(encodes))
    seed = seeds[which_smallest],
    min_max_length = min_max_length,
    max_length = max_length,
    length = nchar(encodes[which_smallest]),
    encoded_text = encodes[which_smallest]

Try it online!

A recoded version of my Jelly answer, partly since it runs quicker (and so it's possible to try more possibilities for the max length) and partly for those wondering how the Jelly answer worked.

The score above (and detailed below) is based on using the splinter_randomised_encode() function and quite a lot of tries to give the following.

We're no strangers to love -> 863


I see trees of green, red roses, too -> 603


To be, or not to be: that is the question -> 1553


sandor weores monkeyland -> 864


Four score and seven years ago our fathers brought forth -> 1391


There was an old lady who swallowed a fly -> 466

}O{\d}N{WU}M{\i\g\g\lUOI}L{T\t}K{\b\iYO}J{X\p\iOUY}I{\ }H{I\tRSITI}G{FKCJ\,FJCQ\,E}F{\

The other day -> 1954


This is the house that Jack built -> 563

Z{\s}Y{OS\n\d}X{\i}W{\o}V{\r}U{\l}T{\t}S{\a}R{\e\d}Q{A\hW\uZ\eON\JS\c\kO\b\uXUT\.}P{WV\nC}O{\ }N{T\hSTO}M{A\fSV\m\eVOZW\wX\n\gO\hXZO\cP\k\e\pTJ}L{ST\eA\mSUT}K{\wWVVXRA\cSTC\kXUURAVSTO}J{AVWWZT\eVON\cVW\wROX\nA\mP\wW\k\eAHE}I{\

Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo -> 41

C{A\ \BA}B{\u\f\f\a\l\o}A{B\ \bB}\BC\ \bC 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.