An Algorithmic Lucidity

a blog

Forgetting an Idea

Occasionally I have a good idea, but neglect to write it down immediately, and end up forgetting it very soon thereafter; often I can ressociate my way back to it, but not always. I'm given to understand that this is not uncommon for other people, either. Only I have to wonder if it's at all telling that we remember the emotional experience of "I just had a good idea! Clearly I am a Smart and Creative Person!" but forget the idea that was ostensibly its referent. Shouldn't it be the other way around? Why, it's almost as if the deception and posturing that defines our social worlds extends even into the sacred domain of the self!

Ford-Fulkerson

Dear reader, have you ever dreamed of solving instances of the maximum flow problem? Sure you have! Suppose we have a weighted directed graph, which we might imagine as a network of pipes (represented by the edges) between locations (represented by the nodes), pipes through which some sort of fluid might thereby be transported across the network. One node is designated the source, another is called the sink, and the weight of the edge (i, j) represents the maximum capacity of the pipe which transports fluid from the location i to location j. The maximum-flow problem is precisely the question of how to transport the maximum possible amount of fluid from the source to the sink (without any fluid leaking or magically appearing at any of the intermediate nodes). That is, we want to assign an amount of fluid flow to each edge, not to exceed that edge's capacity, such that inflow equals outflow for all the intermediate (i.e., non-source, non-sink) nodes, and such that the total flow reaching the sink is maximized.

It turns out that there's a conceptually straightforward algorithm for solving the maximum flow problem, known as the Ford-Fulkerson method, which we'll implement in Ruby. But first, we'll want to pin down exactly how the flow network will be represented. Let's define an Edge class, each instance of which will be initialized with the names of the nodes at its head and tail, and the edge's maximum capacity:

class Edge
  attr_accessor :tail
  attr_accessor :head
  attr_accessor :capacity

  def initialize(t, h, c)
    @tail = t
    @head = h
    @capacity = c
  end
end

And let's also make a FlowNetwork class, defined by the names of its source and sink nodes, and a hash which maps Edges to the amount of flow currently assigned to that Edge:

class FlowNetwork
  attr_accessor :source
  attr_accessor :sink
  attr_accessor :network

  def initialize(source, sink, edges)
    @source = source
    @sink = sink
    @network = {}
    edges.each do |e|
      @network[e] = 0
    end
  end

(Note that I haven't yet closed the FlowNetwork class definition yet, because we still want to define more methods implementing the Ford-Fulkerson algorithm!)

So how do we solve the maximum-flow problem? It's pretty simple! We start with the "zero flow." (In our code above, this is already done when we initialize an instance of FlowNetwork.) We then find an augmenting path from the source to the sink (possibly including edges traversed backwards) such that all the forward-traversed edges along the path have not been assigned their maximum capacity, and all the backward-traversed edges have a nonzero amount of flow. This is path along which we can push more flow from the source to the sink, by increasing the amount of flow along the forward-traversed edges, and decreasing the amount of flow along the backward-traversed edges. (Convince yourself that pushing flow through an edge backwards amounts to decreasing the amount of flow along that edge.) Once we've found such an augmenting path, we push as much flow as we can along it. Then we look for another augmenting path and do the same, until no more augmenting paths can be found. It turns out that the resulting flow assignment solves our problem. So that's the Ford-Fulkerson method:

  def ford_fulkerson
    path = augmenting_path
    while path
      flow_augmentation(path)
      path = augmenting_path
    end
  end

Of course, we need to specify exactly how to find these augmenting paths. This is a little bit more involved. It amounts to doing walking through the graph starting at the source, "labeling" nodes that could be part of an augmenting path, and scanning neighbors of labeled nodes looking for more labelable nodes, until we reach the sink (in which case we have found a path) or until we run out of nodes to scan (in which case there are no augmenting paths left). Sort of like this:

  def augmenting_path
    labeled = {@source=>nil} # keys are labeled nodes; values, parents thereof
    scanned = {}
    now_scanning = @source
    while not labeled.empty?
      if labeled.include?(@sink) # i.e., we've found an augmenting path
        backtrace = [@sink]
        parent = labeled[@sink]
        while parent != nil # reconstruct the path found
          backtrace.push(parent)
          parent = scanned[parent]
        end
        return backtrace.reverse!
      end
      edges = @network.select do |e, v|
        e.tail == now_scanning or e.head == now_scanning
      end
      edges.each do |e, v|
        if e.tail == now_scanning
          if @network[e] < e.capacity
            if not labeled.merge(scanned).include?(e.head)
              labeled[e.head] = now_scanning
            end
          end
        elsif e.head == now_scanning
          if @network[e] > 0
            if not labeled.merge(scanned).include?(e.tail)
              labeled[e.tail] = now_scanning
            end
          end
        end
      end
      scanned[now_scanning] = labeled[now_scanning]
      labeled.delete(now_scanning)
      now_scanning = labeled.keys[0]
    end
    return nil # no path found
  end

And of course, we also need code to actually augment the flow along the path found—

  def flow_augmentation(path)
    flow = +1.0/0 # positive infinity
    edges = []
    def query_edge(tail, head) # select edge given node names
      @network.select{|e, v| e.tail == tail and e.head == head}
    end
    path[0..path.length-2].each_index do |i|
      forward = query_edge(path[i], path[i+1])
      backward = query_edge(path[i+1], path[i])
      if backward.empty?
        edge_flow = forward
        edges.push(edge_flow.keys[0])
        available = edge_flow.keys[0].capacity - edge_flow.values[0]
        if flow > available
          flow = available
        end
      elsif forward.empty?
        edge_flow = backward
        edges.push(edge_flow.keys[0])
        available = edge_flow.values[0]
        if flow > available
          flow = available
        end
      end
    end
    edges.each do |e|
      network[e] += flow
    end
  end

Then throw in a reporting method so we have some way of actually inspecting our network and its completed flow assignment, close the FlowNetwork class definition (at long last) ...

  def report
    print "Source: ", @source, "\n"
    print "Sink: ", @sink, "\n"
    @network.each_pair do |e, v|
      print e.tail, ' -> ', e.head, '; capacity: ', e.capacity, ', flow: ', v, "\n"
    end
    puts
  end

end

—and dear reader, the maximum-flow problem is solved! For suppose we are faced with the network depicted in the following diagram:

hand-drawn directed graph of nine nodes, A through I, connected by numbered-capacity edges, forming the example flow network

We can then (admittedly with some typing) call for a solution like so:

my_edges = [Edge.new('A', 'B', 12), Edge.new('A', 'E', 15),
  Edge.new('A', 'G', 13), Edge.new('B', 'C', 9), Edge.new('E', 'C', 11),
  Edge.new('G', 'E', 7), Edge.new('C', 'D', 18), Edge.new('C', 'F', 10),
  Edge.new('H', 'E', 8), Edge.new('G', 'H', 12), Edge.new('F', 'D', 6),
  Edge.new('H', 'F', 6), Edge.new('D', 'I', 12), Edge.new('F', 'I', 20),
  Edge.new('H', 'I', 10)]
my_network = FlowNetwork.new('A', 'I', my_edges)
my_network.ford_fulkerson
my_network.report

And receive it like so:

Source: A
Sink: I
A -> B; capacity: 12, flow: 9
A -> E; capacity: 15, flow: 11
A -> G; capacity: 13, flow: 12
B -> C; capacity: 9, flow: 9
E -> C; capacity: 11, flow: 11
G -> E; capacity: 7, flow: 0
C -> D; capacity: 18, flow: 12
C -> F; capacity: 10, flow: 8
H -> E; capacity: 8, flow: 0
G -> H; capacity: 12, flow: 12
F -> D; capacity: 6, flow: 0
H -> F; capacity: 6, flow: 2
D -> I; capacity: 12, flow: 12
F -> I; capacity: 20, flow: 10
H -> I; capacity: 10, flow: 10

Bibliography

Dimitris Bertsimas and John N. Tsitsiklis, Introduction to Linear Optimization, §7.5

Anany Levitin, Introduction to the Design and Analysis of Algorithms, §10.2

Cryonics as Memoir

I wonder if cryonics would have a better reputation if it were sold as being more like leaving a memoir, than a bid for personal immortality. Historians are glad to have Samuel Pepys's diary for all that it tells us about life in 1660s London; would they not be more overjoyed to have Samuel Pepys's brain, if only we knew how to read brains as easily as we can read books?

Talking Too Much

"Sorry, have I been dominating our conversations too much?"

"Not at all; why do you ask?"

"I feel like my model of you is less detailed than my model of your model of me."

Lyrics to the Song About Truthseekers

Yesterday my sister won the Nobel prize
Her work will be a benefit to all of humankind
She went and proved some things which no one had surmised
Yesterday my sister won the Nobel physics prize

Yesterday I dreamed I won the Pulitzer prize
I uncovered the scandal, was a President's demise
I found the truth and brought it out to people's eyes
Yesterday I dreamed I won the Pulitzer journalism prize

And then I woke up
And rubbed my eyes

Yesterday my sister won the Nobel prize
Her work will be a benefit to all of humankind
She went and proved some things which no one had surmised
Yesterday my sister won the Nobel physics prize

The History of the Universe

"So, the universe starts out being made out of physics, then turns into game theory as life, then civilization, then artificial intelligence do increasingly a priori improbable things, then turns back into physics again as everyone runs out of negentropy. Poetically speaking."

"Right. Literally, it would just be physics throughout."

"But the poetry pays rent: the more agent-like a process is, the more predictively useful it is to take the intentional stance of talking about what it wants, rather than computing out the physics."

Personhood

"Did you hear that India has recognized dolphins as nonhuman persons with rights to life and liberty?"

"Hm, yes."

"I thought you'd approve."

"Oh, I do; any measure that reduces the suffering of sentient creatures is a very noble thing. I'm just uncertain about the correct way to generalize the concept of personhood in the wake of the knowledge that humanity isn't ontologically privileged. Some people seem to favor a broad interpretation, such that chimps and dolphins are persons. But sometimes I wonder if a narrow construal would be better, where not all humans are persons."

"You mean, excluding infants and brain-damage cases?"

"No, I mean excluding us. After you filter through all the memetic noise and tribal posturing, real-life humans don't exhibit nearly as much moral agency as we like to think. We have not yet dreamed of what our highest ideals of personhood would look like when implemented consistently."

Quotations I

"As far as anyone knows, there's never been an animal population that was stable in the absence of predation, famine, or disease."

"Don't get discouraged," Carla said, reaching over and putting a hand on his shoulder. "That's just the history of life for the past few eons. It's not as if it's a law of physics."

The Eternal Flame by Greg Egan

Cherryl did not answer, then said suddenly, desperately, "Look ... what I don't want is charity."

"Jim must have told you—and it's true—that I never engage in charity."

"Yes, he did ... But what I mean is—"

"I know what you mean."

"But there's no reason why you should have to feel concern for me ... I didn't come here to complain and ... and load another burden on your shoulders. ... That I happen to suffer, doesn't give me a claim on you."

"No, it doesn't. But that you value all the things I value, does."

"You mean ... if you want to talk to me, it's not alms? Not just because you feel sorry for me?"

"I feel terribly sorry for you, Cherryl, and I'd like to help you—not because you suffer, but because you haven't deserved to suffer."

"You mean, you wouldn't be kind to anything weak or whining or rotten aobut me? Only to whatever you see in me that's good?"

"Of course."

Atlas Shrugged by Ayn Rand

The wizard gets the delight of working in a specialized area—magic—and gets a good look at the foundations of the Universe, the way things really work. It should be stated that there are people who consider the latter more of a curse than a blessing.

So You Want to Be a Wizard by Diane Duane

But I don't need to know the answer. I just recite to myself, over and over, until I can choose sleep:

It all adds up to normality.

Quarantine by Greg Egan

Quicksort in FIM++

Dear reader, I have got to tell you, fandom is intense. One day last October Equestria Daily (internet clearinghouse for fans of the animated series My Little Pony: Friendship Is Magic) posts a joke proposal for a programming language (FIM++) based on the show, and within the week there's a working interpreter for it. What does it mean to model a programming language after a cartoon, you ask? Well, in the show, episodes typically end with our heroine Twilight Sparkle (or after Season Two, Episode Three "Lesson Zero", one of her friends) writing a letter about what she's learned about the magic of friendship to her mentor (and God-Empress of the sun) Princess Celestia. So, then, why not have an esoteric programming langauge where the source code reads like a letter to Princess Celestia? Adorable, right?

So, this gift having been provided to us courtesy of Karol S. and the brony community, let's do something with it! More specifically, how about we implement quicksort?—that is a classic. What's quicksort? Well, we want to sort a list, right? So—bear with me—we define this partitioning procedure that, given indices into an array, partitions the subarray between those indices into a subsubarray of elements less-than-or-equal-to a given element dubbed the pivot, then the pivot itself, then a subsubarray of elements greater than the pivot. How do we do that? Well, let's designate the last element in our subarray as the pivot. Then we're going to scan through all the other elements, and if any of them are less-than-or-equal-to the pivot, we swap it into our first subsubarray and increment a variable keeping track of where the first subsubarray ends. Then, we swap the pivot into place and return its index. In Ruby—

def partition(array, p, r)
  i = p-1
  for j in p..(r-1) do
      if array[j] <= array[r]
        i += 1
        array[i], array[j] = array[j], array[i]
      end
  end
  array[i+1], array[r] = array[r], array[i+1]
  i+1
end

Then we can sort an entire array with a bunch of recursive calls to our partitioning procedure:

def quicksort(array, p, r)
  if p < r
    q = partition(array, p, r)
    quicksort(array, p, q-1)
    quicksort(array, q+1, r)
  end
end

# Let's try it!
my_array = [9, 5, 4, 11, 2, 10, 6, 3, 8, 12, 1, 7]
quicksort(my_array, 0, my_array.length-1)
print my_array # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

So that's quicksort. With a little more effort, we can do the same thing in FIM++:

Dear Princess Celestia: Letter about Quicksort:

I learned about exchange with Applejack, Rainbow Dash, and Rarity.

  On the page numbered by Rarity of Applejack I read about Sweetie
  Belle.

  On the page numbered by Rainbow Dash of Applejack I read about
  Scootaloo.

  On the page numbered by Rarity of Applejack I wrote what I knew
  about Scootaloo.

  On the page numbered by Rainbow Dash of Applejack I wrote what I
  knew about Sweetie Belle.

That's about exchange.


I learned about partitioning with Applejack, Rainbow Dash, and Rarity.

  On the page numbered by Rarity of Applejack I read about Apple
  Bloom.

  Sweetie Belle made the difference of Rainbow Dash and the number one.

  Did you know Scootaloo likes Rainbow Dash?

  I did this while Scootaloo had less than Rarity:

    On the page numbered by Scootaloo of Applejack I read about
    Diamond Tiara.

      When Diamond Tiara had not more than Apple Bloom:

        Sweetie Belle got one more.

        I did exchange of Applejack, Sweetie Belle, and Scootaloo.

      That's what I did.

    Scootaloo got one more.

  That's what I did.

  Sweetie Belle got one more.

  I also caused exchange of Applejack, Sweetie Belle, and Rarity.

That's about partitioning with Sweetie Belle.


I learned about quicksort with Applejack, Rainbow Dash, and Rarity:

  When Rainbow Dash had less than Rarity:

    Fluttershy did partitioning of Applejack, Rainbow Dash, and Rarity.

    Fluttershy got one less.

    I caused quicksort of Applejack, Rainbow Dash, and Fluttershy.

    Fluttershy got two more.

    I caused quicksort of Applejack, Fluttershy, and Rarity.

  That's what I did.

That's about quicksort.


Today I learned:

  Did you know Applejack likes 9, 5, 4, 11, 2, 10, 6, 3, 8, 12, 1, and 7?

  Applejack did dictionary of Applejack.

  I said: Applejack!

  I did quicksort of Applejack, one, and twelve.

  I said: Applejack! 

Your Faithful Student,
Twilight Sparkle

Bibliography Cormen et al., Introduction to Algorithms, third ed'n, §7.1

Compensation

"Maybe there should be an effort to cryopreserve specimens of endangered species. 'Hey, sorry we killed your entire species, but when we get more computing power later, we'll be sure to give you lots of happy runtime as compensation.'"

Guns

"Do you know, I've decided I like guns. Of course it would be preferable to wave a magic wand and have all sentient life live in peace and harmony in paradise forever. But if Reality puts you in a situation where you have to kill, at least we have tools to do it quickly: a well-aimed bang and there isn't a creature there to suffer for very long. That's actually a huge improvement over the state of nature, where animals kill with nothing but teeth and claws."

The Demandingness Objection

"Well, I'm not giving up dairy, but I can probably give up meat, and milk is at the very bottom of Brian's table of suffering per kilogram demanded, so I'd be contributing to much less evil than I was before. That's good, right?

"For all the unimaginably terrible things our species do to each other and to other creatures, we're not—we're probably not any worse than the rest of nature. Gazelles suffer terribly as lions eat them alive, but we can't intervene because then the lions would starve, and the gazelles would have a population explosion and starve, too. We have this glorious idea that people need to consent before sex, but male ducks just rape the females, and there's no one to stop it—nothing else besides humans around capable of formulating the proposition, as a proposition, that the torment and horror in the world is wrong and should stop. Animals have been eating each other for hundreds of millions of years; we may be murderous, predatory apes, but we're murderous, predatory apes with Reason—well, sort of—and a care/harm moral foundation that lets some of us, with proper training, to at least wish to be something better.

"I don't actually know much history or biology, but I know enough to want it to not be real, to not have happened that way. But it couldn't have been otherwise. In the absence of an ontologically fundamental creator God, Darwinian evolution is the only way to get purpose from nowhere, design without a designer. My wish for things to have been otherwise ... probably isn't even coherent; any wish for the nature of reality to have been different, can only be made from within reality.

"And nature to be commanded must be obeyed—only by figuring out how things actually work now, do we have any hope of making them better. That's how intelligence works: while True, predict outcomes conditional on performing various actions, then return the best.

"I guess this is how you tell the difference between politics and actual altruism. Being angry about school or gender roles felt good, felt righteous. Dulce et decorum est. But after stripping away all the ideology and looking at what people actually are ... it just makes me sad. And annoyed that my altruism should be so sorely needed—it would be so much more fun to eat lots of tasty meat and enjoy my decadent lifestyle without having to be haunted that not everyone has it, that it hasn't always been here, that all the reasons we can't have nice things could easily carry the eon ...

"But as I keep saying, I need to quit philosophy; it's beyond my reach; we have theorems about decisionmaking under uncertainty that imply there has to be some exchange rate describing how many minutes of sunshine and happiness are worth how many minutes of being eaten alive by predators, but the part of me that doesn't want to be hurt and the part of me that knows math aren't well-integrated enough to name any particular figure. And luckily, it doesn't matter; my actions for the near-future are already determined; I'm going to establish an income, tithe ten percent of it to well-chosen causes, and bask in the warm glow of discharged moral responsibility. As I keep saying, there's plenty of shiny math and computer science to do that has nothing to do with the ugly real world; someone else, someone stronger can concern themselves with the moral law."

"You might underestimate the degree to which pure math and theoretical computer science are actually moral philosophy."

"But a lot of it isn't. Group theory, not game theory; big-O complexity, not Kolmogorov complexity."

Revisionist History I

"It is my considered opinion that Emily Dickinson was a time-traveling cryonicist."

"That is an opinion I have not previously heard advanced."

"C'mon! 'Because I could not stop for Death, / He kindly stopped for me; / The carriage held but just ourselves / And Immortality'? It's obvious!"

Huffman

Dear reader, you know what's way more fun than feeling sad about the nature of the cosmos? Data compression, that's what! Suppose you want to send a message to your friends in a nearby alternate universe, but interuniversal communication bandwidth is very expensive (different universes can't physically interact, so we and our alternate-universe analogues can only communicate by mutually inferring what the other party must be saying, which takes monstrous amounts of computing power and is not cheap), so you need to make your message as brief as possible. Note that 'brief' doesn't just have to do with how long your message is in natural language, it also has to do with how that message is represented over the transuniveral communication channel: indeed, the more efficient the encoding, the more you can afford to say on a fixed budget.

The classic ASCII encoding scheme uses seven bits to represent each character. (Seven?—you ask perplexedly, surely you mean eight? Apparently it was seven in the original version.) Can we do better? Well ... ASCII has a lot of stuff that arguably you don't need that badly. Really, upper and lower case letters? Ampersands, asterisks, backslashes? And don't get me started about those unprintable control characters! If we restrict our message to just the uncased alphabet A through Z plus space and a few punctuation marks, then we can encode our message using only a 32 (= \(2^5\)) character set, at five bits per character.

Can we do better? Seemingly not—\(2^4 = 16\) isn't a big enough character set to cover the alphabet. Unless ...

Unless we abandon the assumption that each character needs to be represented by the same number of bits!

Adopting such a variable-length character encoding scheme couldn't help us if all possible messages were equally likely, but this is manifestly not true of the sort of messages anyone would actually want to send: the letter e is more common than the letter q; the word the more common than the string zkb. We can take advantage of the regularities in the sort of messages people would actually want to send by adopting a variable-length encoding scheme that assigns shorter codes to characters that occur more frequently. But then since we can no longer rely on each n bits (for some fixed n) representing one character, we'll also want our encoding scheme to be prefix-free: no character code should be a prefix of any other. That way, our message is unambiguous: if the message starts with (say) 010, which represents (say) e, then we know that the first character is e, because there won't be any other characters with codes that start with 010 (like say 01011).

Consider the process of decoding a prefix-free code: we read bits from the start of the message until we recognize a valid character code. We can visualize this graphically in the form of a binary tree with the characters in our character set at the leaves: start at the root, and then go the the left child if the first bit is 0, and the right child if it's 1, and so on until you reach a leaf, the path taken representing the code for the character at that leaf. The task of constructing such an optimal such tree (and thereby, an optimal prefix-free code) given the relative frequencies of the characters (either in a particular message, or a general class of messages that people would actually want to send) is solved by a algorithm due to David A. Huffman, which we'll implement here in Python.

First, we'll need a data structure to represent the nodes of the tree. Actually, we'll use a structure that represents a node and all of its children, which we'll call Subtree. Each instance of Subtree will have fields for a character and frequency associated with a node, and the node's left and right children if any (which are also instances of Subtree).

class Subtree:
    def __init__(self, char, freq, left, right):
        self.char = char
        self.freq = freq
        self.left = left
        self.right = right

The leaf nodes of our tree will have the character field set to a character in our set, the frequency field set to the frequency of that character, and the left and right children fields set to None. The internal nodes of the tree will have the character field set to None and the frequency field set to the sum of the frequencies of its children.

We'll also want nodes to be comparable by their frequencies:

    def __gt__(self, othernode):
        return self.freq > othernode.freq

When all is said and done, Huffman's algorithm will give us an instance of Subtree representing our tree, but it would really be more convenient to have the decoding information embodied by the tree in the form of Python dictionary mapping bitstrings to the characters they encode. We can get this by doing a standard recursive tree walk:

    def codebook(self):
        codes = {}
        def traversal(item, code):
            if item != None:
                traversal(item.left, code+'0')
                if item.char != None:
                    codes[item.char] = code
                traversal(item.right, code+'1')
        traversal(self, '')
        return codes

We're also going to need a min-priority queue, a dynamic set from which we can insert items and retrieve the "smallest":

class MinPriorityQueue:
    def __init__(self):
        self.queue = []

    def put(self, item):
        self.queue.append(item)
        self.queue.sort(reverse=True)

    def get(self):
        return self.queue.pop()

Now that we have the appropriate data structures, we're ready for Huffman's algorithm. Suppose our character/frequency data are given to us in a Python dictionary C. For each character, we create an instance of Subtree to be the corresponding leaf node in our tree, and put them all in a min-priority queue. Then we build our tree from the bottom up by selecting the two lowest-frequency nodes in the queue, making them the children of a new node whose frequency is the sum of their frequencies, putting the new node back in the priority queue, and again until we've built the entire tree:

def Huffman(C):
    Q = MinPriorityQueue()
    leaves = {Subtree(k, C[k], None, None) for k in C}
    for leaf in leaves:
        Q.put(leaf)
    for i in range(len(C)-1):
        left = Q.get()
        right = Q.get()
        new_node = Subtree(None, left.freq + right.freq, left, right)
        Q.put(new_node)
    return Q.get().codebook()

And that's Huffman's algorithm. Of course, we'll also want functions for encoding and decoding a message:

def code(plaintext, codebook):
    return ''.join(codebook[c] for c in plaintext)

def decode(ciphertext, codebook):
    decodebook = {v:k for k, v in codebook.items()}
    codeword = ''
    plaintext = ''
    for i in range(len(ciphertext)):
        codeword += ciphertext[i]
        if codeword in decodebook:
            plaintext += decodebook[codeword]
            codeword = ''
    return plaintext

So, suppose you want to send your friends in a nearby alternate universe the eighty-two character message, "I USED TO WONDER WHAT FRIENDSHIP COULD BE, UNTIL YOU ALL SHARED ITS MAGIC WITH ME." If you used seven-bit ASCII, you'd have to pay the cost of transmitting 82*7 = 574 bits. But if you use a Huffman code informed by knowledge of English letter frequencies ...

eng_freqs = {'A' : 8167, 'B' : 1492, 'C' : 2782, 'D' : 4253, 'E' : 12702,
'F' : 2228, 'G' : 2015, 'H' : 6094, 'I' : 6966, 'J' : 153, 'K' : 772,
'L' : 4025, 'M' : 2406, 'N' : 6749, 'O' : 7507, 'P' : 1929, 'Q' : 95,
 'R' : 5987, 'S' : 6327, 'T' : 9056, 'U' : 2758, 'V' : 978, 'W': 2360,
'X' : 150, 'Y' : 1974, 'Z' : 74, ' ' : 13000, '.': 4250, ',': 4250}
eng_codebook = Huffman(eng_freqs)
plain = "I USED TO WONDER WHAT FRIENDSHIP COULD BE, UNTIL YOU ALL SHARED ITS MAGIC WITH ME."
cipher = code(plain, eng_codebook)

You can send this instead:

0111010111100000100111001010110110010101110101001011011001001111110101110100
0001010110101011100111111011100101101100100010000011110000101011110110011111
0010110110010101000000011100001011110001101101011110110010100010100111110001
0101010110101100100001000010101111100111001010011111010001010111011101010001
1011111110101011101001111101000001011101100110111

—which is only 353 bits, for a savings of 38.5%.

Bibliography Cormen et al., Introduction to Algorithms, third ed'n, §16.3

Remembering

"I remember feeling like a person, and feeling like people were ontologically distinct from animals, and I don't know how it's possible to pick up the pieces after that illusion has gone.

"I remember caring about parochial political concerns. I cared about gender equality, and educational freedom. And, and, I cared very badly about being respected for being intelligent. But now that I see that the latter was just a standard male primate status-seeking drive gone haywire—or not gone haywire, but functioning normally—and that my less-obviously-selfish concerns were driven by idiosyncratic features of my own psychology that few others have any reason to care about—none of it seems as compelling anymore.

"Then what is compelling? Well, I'm terrified of the pain of being physically hurt, so if I don't know what's real and I don't know what's right, I can always fall back on 'pain and suffering are bad.'

"But there has to be more to morality than that. I complained about how people in institutional contexts optimize for not-being-personally-blamed and no one is actually trying to do anything. But of course passive helplessness is the result when you don't have any goals except not-being-hurt.

"I want to be Innocent and Safe with Probability One, but Probability One is an absurdity that can't exist. In a sufficiently large universe, random fluctuations in maximum entropy heat death form a Boltzmann brain Judeo-Christian God who will punish you for masturbating. But somehow I'm not worried about that.

"But I shouldn't be thinking about any of this. I have my own life to tend to, and it looks great; the rest of space and time will have to take care of itself. I seem to have memories of being in the save/destroy/take over the world business, but now it seems more convenient to be agnostic about whether any of that actually happened."

Good Works

"And you, you louse! How do you justify your existence?"

"I tutor orphans."

"Oh, yeah? What subject?"

"Galois theory."

Lyrics to the Song About Matt Reeves

Dead kid gets a bench
Dead kid gets a memorial bench
So now we all know his name
Though we don't know who he is

Class of nineteen ninety two
Though he died in 'ninety one
Was he a better friend than you?
And what'd he do for fun?
What were his opinions on the issues of the day?
And what exactly took his breath away?

Now he's still and in the grave
And since the dead seem all the same
No one really cares to wonder what he was
Forgotten as we're staring at his name

Dead kid gets a bench
Dead kid gets a memorial bench
So now we all know his name
Though we don't care who he is

Dead kid gets a bench
And the inscription just screams "Rust this"
No inscription can do justice
Though we don't know who he is
And we don't care who he is

Retirement

"Rational agents should never be made worse off by more information—well, almost never. So if I can no longer contemplate the big picture without life seeming like a bad thing—the fewer needs you have, the fewer ways in which you can be hurt; if you don't exist, you can't be hurt—then maybe I could just—not contemplate it? If my will to live is something that can be destroyed by the truth, then maybe P. C. Hodgell was wrong? This needn't entail self-delusion: distraction is quite sufficient. There are plenty of things to do that won't remind me of the vastness of suffering in the multiverse.

"Daily life, exercise, practical programming skills, finding a job—pure math and compsci if I need something intellectual. But no philosophy, history, current events, futurism, social science, biology, or game theory. Not much fiction, because stories are about people's pain. I just don't want to know anymore."