<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Posts on David Bieber</title>
    <link>https://davidbieber.com/posts/</link>
    <description>Recent content in Posts on David Bieber</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 26 Oct 2018 00:00:00 +0000</lastBuildDate><atom:link href="https://davidbieber.com/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>The Go Note Go Story</title>
      <link>https://davidbieber.com/post/2022-12-30-go-note-go-story/</link>
      <pubDate>Fri, 30 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2022-12-30-go-note-go-story/</guid>
      
      <description>&lt;p&gt;&lt;small&gt;&lt;em&gt;This story is meant to be listened to. You can also read along below.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;audio
controls
src=&#34;https://davidbieber.com/audio/go-note-go-story.m4a&#34;&gt;
Your browser does not support the &lt;code&gt;audio&lt;/code&gt; element.
&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;Hi everyone. I&amp;rsquo;m David Bieber and I&amp;rsquo;m going to tell you the story of 
&lt;a href=&#34;https://davidbieber.com/projects/go-note-go&#34;&gt;Go Note Go&lt;/a&gt;, which you can see on the screen.&lt;/p&gt;
&lt;p&gt;And to do this, I need to bring you back with me to 2014. It&amp;rsquo;s my senior year of college.
I&amp;rsquo;m in my dorm, which is a beautiful stone building, like a castle.
I&amp;rsquo;m lying awake at the bottom bunk of a bed late at night.&lt;/p&gt;
&lt;p&gt;My thoughts are racing. I can&amp;rsquo;t sleep. There&amp;rsquo;s this girl. Does she like me? Do I like her?
What will I say to her tomorrow?&lt;/p&gt;
&lt;p&gt;Every time I finish a thought, I have a new thought, just subtle variations on the thoughts I&amp;rsquo;ve already had. And they won&amp;rsquo;t stop.
It&amp;rsquo;s getting even later and I need to fall asleep.&lt;/p&gt;
&lt;p&gt;Fortunately, I find a trick that works for me.
If I write down what I&amp;rsquo;m thinking, then there&amp;rsquo;s just one canonical version of the thought, not two hundred.
And it&amp;rsquo;s on paper, not in my head, and I can sleep.&lt;/p&gt;
&lt;p&gt;The only trouble is, I&amp;rsquo;m in a bunk bed; there&amp;rsquo;s a roommate six feet above me and I can&amp;rsquo;t just turn on the lights to write something down.
And I wouldn&amp;rsquo;t want to anyway. It would wake me up.
Writing in the dark is no good. Can&amp;rsquo;t read my handwriting that way.
And typing on my phone or computer is out too, because the lights are too bright and they would wake me up.&lt;/p&gt;
&lt;p&gt;So, I settle on typing on my laptop in the dark with the monitor turned completely off.&lt;/p&gt;
&lt;p&gt;I study computer science, so I write some software to make this even better.
And 
&lt;a href=&#34;https://davidbieber.com/projects/shh-shell&#34;&gt;Shh Shell&lt;/a&gt; is born.
I don&amp;rsquo;t have to worry about typing and falling asleep on the delete key, or typing and having the window not being in focus.
The name Shh Shell is a pun on SSH and sleeping, and it&amp;rsquo;s amusing to me.&lt;/p&gt;
&lt;p&gt;And I pile on the bells and whistles. Shh Shell lets me set alarms, send text messages, hear the time, the works, all without ever opening my eyes.
And I love it. I use it for years.&lt;/p&gt;
&lt;p&gt;I take it with me when I move out to California to work for Google.
I set it up in my shower so I can capture shower thoughts in addition to sleep thoughts.
Shh Shell quickly becomes my personal journal, a place I can put my most personal thoughts and then get a good night&amp;rsquo;s sleep.&lt;/p&gt;
&lt;p&gt;Fast forward five years. This is two months ago now. I&amp;rsquo;m driving in a car that I recently purchased from New Haven, Connecticut, where I live, to a campground in New Jersey to go camping.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m listening to an audiobook and I feel an old yearning, the desire to write something down, something important, just to get it out of my head.
It&amp;rsquo;s been a few years since I&amp;rsquo;ve used Shh Shell. I didn&amp;rsquo;t use it much after the move east, but I know exactly what I need. I need to write down my thoughts about the audiobook that I&amp;rsquo;m listening to.&lt;/p&gt;
&lt;p&gt;As I get to the campground, the thoughts about the book have evaporated.
I set up my tent, and it&amp;rsquo;s here that Go Note Go was born.
A new, improved version of Shell that supports driving and camping, as well, of course, as sleeping and showering.&lt;/p&gt;
&lt;p&gt;I design Go Note Go right there in the woods.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;ll be like Shh Shell, but with a host of improvements. You won&amp;rsquo;t need a laptop for it to work.
It&amp;rsquo;s just a standalone keyboard.
It supports audio with a beautiful red button you can use to record while driving.
And it works offline. Perfect for camping.
It will transcribe your notes and upload everything as soon as it gets an internet connection.&lt;/p&gt;
&lt;p&gt;I am so excited to build this. I order the parts right then and there from the floor of my tent in the middle of the woods.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t even wait for the parts to arrive. As soon as I get home, I write 
&lt;a href=&#34;https://github.com/dbieber/GoNoteGo&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;the software&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I configure mine to upload to 
&lt;a href=&#34;https://roamresearch.com/#/app/commons-db/page/9W2EycBcG&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Roam&lt;/a&gt;. But a month later when 
&lt;a href=&#34;http://ddohan.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;David&lt;/a&gt;, Aria, Sam, and 
&lt;a href=&#34;https://twitter.com/aaronmayer108&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Aaron&lt;/a&gt; are building their own, they set theirs to upload to Notion or RemNote or Ideaflow. It works with any note-taking app at all.&lt;/p&gt;
&lt;p&gt;David Dohan has connected me with this wonderful group of people after I built the first version.
And I give them a live demo, taking my fully assembled Go Note Go out into a tent for the first time, this time to go camping in my own backyard.&lt;/p&gt;
&lt;p&gt;I show Go Note Go to Aaron and the whole group. How it handles audio, how as I drift off to sleep I tend to switch from talking to typing the more tired I get, how you can still issue commands like asking it for the time without waking yourself up, just like you could with Shh Shell.&lt;/p&gt;
&lt;p&gt;I feel like when I built Shh Shell for the first time.
I love it.&lt;/p&gt;
&lt;p&gt;And now I have a group of people who love it as well, each building their own.&lt;/p&gt;
&lt;p&gt;I finish camping in my backyard here in New Haven and I come inside. Last night, I found myself in a situation like the one from years ago. Getting ready to present today, my thoughts again were swirling. This time, though, I knew immediately what to do. I took the dozens of versions of this story that were racing through my mind, and I used Go Note Go, through a combination of typing and speaking, to put just a single version of the story down on the page. My mind was settled, and I slept.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sending messages as I drift off to sleep</title>
      <link>https://davidbieber.com/post/2022-01-08-new-messager-setup/</link>
      <pubDate>Sat, 08 Jan 2022 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2022-01-08-new-messager-setup/</guid>
      
      <description>&lt;p&gt;In this post I describe my new Messager setup,
and how it allows me to send messages directly from Roam Research &lt;em&gt;or&lt;/em&gt;
from a standalone keyboard (no monitor) that I keep at my bedside as I drift off to sleep, and which I take driving and camping.&lt;/p&gt;
&lt;p&gt;Currently my setup supports sending messages to
Facebook Messenger, Twitter,
Slack, Discord, and iMessage.
I intend to add gChat and email support next, since there are still a handful of people I want to message that don&amp;rsquo;t use any of these.&lt;/p&gt;
&lt;p&gt;Above all else, the beauty of this project to me is its usability,
which is hard to communicate in a write-up like this.
Some of the usability comes from the way the components integrate with my existing workflows.
Some of the usability comes from design choices
(like using a keyboard with no monitor for note-taking and message drafting).
I&amp;rsquo;ll try to point out these usability elements along the way.
Bear in mind I designed this primarily for myself
(though I have a handful of friends along for the ride as well, trying it out and making their own contributions).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s dive in.&lt;/p&gt;
&lt;h3 id=&#34;go-note-go-the-headless-keyboard&#34;&gt;Go Note Go: The Headless Keyboard&lt;/h3&gt;
&lt;p&gt;For some background, I developed Go Note Go, a headless keyboard designed for note-taking on the go.
Go Note Go is a note-taking system for when you&amp;rsquo;re on the go, with a focus on driving and camping.
You can 
&lt;a href=&#34;https://davidbieber.com/projects/go-note-go&#34;&gt;read all about Go Note Go here&lt;/a&gt; and learn more on 
&lt;a href=&#34;https://github.com/dbieber/gonotego&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;its GitHub page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Go Note Go&amp;rsquo;s main purpose is note-taking.
Anything you type into Go Note Go or voice-record into it gets transcribed and uploaded to your notes as soon as it gets an internet connection.
For me, Go Note Go uploads to Roam Research. It also supports RemNote, IdeaFlow, Mem, and Notion, and adding additional systems isn&amp;rsquo;t too difficult.&lt;/p&gt;
&lt;p&gt;One of the usability perks of Go Note Go is that there is no monitor. This means you can&amp;rsquo;t get distracted browsing the internet while writing on Go Note Go. You also can&amp;rsquo;t get distracted wordsmithing your own writing. It makes writing pleasurable, even if it comes at the cost of having typos and stray thoughts in your writing; you can always clean those up later.&lt;/p&gt;
&lt;p&gt;Go Note Go does a lot more, so I do encourage you to 
&lt;a href=&#34;https://davidbieber.com/projects/go-note-go&#34;&gt;learn more about it&lt;/a&gt;, but it isn&amp;rsquo;t the focus of this post, which is about sending messages.
For the purposes of this post, Go Note Go is a data entry system for Roam. And since I&amp;rsquo;ve also built a system that allows sending messages directly from Roam, this means I can now send messages directly from Go Note Go. So, I can send messages when driving, camping, and as I drift off to sleep at night.&lt;/p&gt;
&lt;p&gt;To send a message in Go Note Go, you simply &amp;ldquo;@&amp;rdquo; the person you are writing to. To send a message to Adriana, I would write &amp;ldquo;@Adriana Message goes here&amp;rdquo;. To send a message to David Dohan (using first and last name), I could write &amp;ldquo;@David Dohan: Message goes here&amp;rdquo;. You can @ someone at any point in the message. You can also @ multiple people, and Messager will send them a group message.&lt;/p&gt;
&lt;p&gt;Go Note Go acts as an outliner now too; this is both a big usability improvement for Go Note Go on it&amp;rsquo;s own, and doubly so for using Go Note Go as a messager. An &amp;ldquo;outliner&amp;rdquo; is a system for taking notes as a series of nested bullets. You can push tab to indent notes underneath other notes; I&amp;rsquo;ve developed a user experience that makes this moderately natural, even in the absence of a monitor or any other visual feedback. For sending messages, the outliner feature is a boon. Any notes you nest under an @&amp;rsquo;d note will also register as additional messages for the @&amp;rsquo;d recipients. This makes writing longer multiline messages a pleasure, even without a monitor.&lt;/p&gt;
&lt;p&gt;The default behavior of my messaging system is to hold the messages for approval before sending them. So, the messages that I write as I drift off to sleep don&amp;rsquo;t send as I drift off to sleep. Instead, I see them in the morning and approve them, and only upon being approved are they sent automatically.&lt;/p&gt;
&lt;p&gt;This delayed-approve-then-send approach is particularly well suited for Go Note Go, where I likely make typos and want to clean up the messages before they are sent, since I am writing them without a monitor. Later in the post I&amp;rsquo;ll dive into what the approval process looks like (tl;dr there&amp;rsquo;s a spreadsheet where I can mark a message as &amp;ldquo;OK&amp;rdquo; to send), but first I want to share additional benefits of being able to send messages directly from my note-taking app, which is currently Roam Research.&lt;/p&gt;
&lt;h3 id=&#34;drafting-messages-in-roam-research&#34;&gt;Drafting Messages in Roam Research&lt;/h3&gt;
&lt;p&gt;Anything I write on my Go Note Go appears in Roam Research, but I can also take notes directly in Roam.
Just like on Go Note Go, the way to draft a message in Roam is to &amp;ldquo;@&amp;rdquo; the person you are writing to.
Messager has some heuristics it uses to translate your @&amp;rsquo;s into proper recipients.
So, the syntax for @&amp;lsquo;ing someone isn&amp;rsquo;t too strict.&lt;/p&gt;
&lt;p&gt;Using Roam offers some possibilities beyond what I can do on Go Note Go, such as including images in messages.
Like on Go Note Go, you can use nested bullets to create longer messages,
and you can @ as many people as you want for a message.
In Roam, however, you can also include images in your messages.
Messager will intelligently send those in the native format used by the underlying messaging service,
rather than blindly sending the markdown stored in Roam.
I find this feature, being able to use images naturally in Roam, and then have them send naturally as messages,
quite pleasant.&lt;/p&gt;
&lt;p&gt;One future direction I&amp;rsquo;d like for this project is to add a &amp;ldquo;send&amp;rdquo; button in Roam next to any messages I&amp;rsquo;ve drafted there,
as well as to display the status of the messages inline in my notes. That&amp;rsquo;s not implemented now, but would be a nice-to-have for the future.&lt;/p&gt;
&lt;p&gt;Today, all messages are automatically added to my &amp;ldquo;messager queue&amp;rdquo; where they wait for approval before being sent.
I like having this approve-before-sending approach as the default, since it psychologically frees me up to write things I might not otherwise write if I was instead using a send button that sent immediately.
However, sometimes being able to send something immediately is desirable, and that&amp;rsquo;s not a clean option in the current setup.&lt;/p&gt;
&lt;h3 id=&#34;roam-to-sheets-the-messager-queue&#34;&gt;Roam to Sheets: The Messager Queue&lt;/h3&gt;
&lt;p&gt;Any messages in Roam (whether entered directly into Roam, or entered via Go Note Go), are automatically added to a spreadsheet, the &amp;ldquo;Messager Queue&amp;rdquo;. These are the spreadsheet&amp;rsquo;s columns:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Service&lt;/td&gt;
&lt;td&gt;Recipient&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Time&lt;/td&gt;
&lt;td&gt;Approval&lt;/td&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;Metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The Messager Queue sheet is filtered by default to show all pending messages. For each message it shows the &lt;em&gt;Service&lt;/em&gt; on which it will be sent and well as the &lt;em&gt;Recipient&lt;/em&gt; of the message. The service can be one of FB Messenger, Twitter, iMessage, a Slack server, or Discord. The recipient can either be an individual, a list of individuals, a group, a Slack channel, or a Discord server and channel.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a sheet that lists aliases, so it&amp;rsquo;s easy to use a short name or informal name to refer to a longer service or recipient; e.g. I use &amp;ldquo;@Audio Tools&amp;rdquo; as a shorthand for sending to a group of 5 people on Messenger all interested in audio tools for networked thought.&lt;/p&gt;
&lt;p&gt;The Messager Queue sheet also displays the &lt;em&gt;Text&lt;/em&gt; of the message to send, so you can easily clean up the message here for any typos or clarifications before it is sent.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Date&lt;/em&gt; and &lt;em&gt;Time&lt;/em&gt; columns allow for scheduling messages to be sent at any point in the future. Leave them blank and the message will be sent as soon as it is approved. Use keyboard shortcuts cmd-; (for date) and cmd-shift-; (for time) to quickly fill out these columns if desired.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;em&gt;Approval&lt;/em&gt; column is the most important. Mark a messages as OK if you want it to be sent, or as Ignored if you deside to skip it. The &lt;em&gt;Status&lt;/em&gt; column is updated automatically by Messager when the message is sent (or if it fails to send, then the error appears here). You can also mark the Status manually with whatever value you want (e.g. if you send the message manually), and the Messager system will ignore that message going forward. If you want the system to retry sending a message, simply clear out the Status column and it will try again.&lt;/p&gt;
&lt;p&gt;I include this spreadsheet as a custom service in 
&lt;a href=&#34;https://getferdi.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Ferdi&lt;/a&gt;, so it lives alongside all the messaging apps I use. (This in itself might be the subject of a future 
&lt;a href=&#34;https://davidbieber.com/snippets&#34;&gt;snippet&lt;/a&gt;.)&lt;/p&gt;
&lt;h3 id=&#34;why-the-approval-system-over-immediate-messages&#34;&gt;Why the approval system over immediate messages?&lt;/h3&gt;
&lt;p&gt;Beyond allowing me to catch a bunch of typos, it&amp;rsquo;s also a psychologically useful thing for me.
I am more willing to write and I feel able to say more things that I might not otherwise,
knowing I have the opportunity to cancel or change the message later.
In that way, it&amp;rsquo;s like gmail&amp;rsquo;s undo feature. In gmail, undo not a true undo, but having those 30 seconds after clicking send to retract the message make a big difference.&lt;/p&gt;
&lt;p&gt;Another benefit of sending the messages only after approving them is the ability to sleep at night. If I write a message at night, I know you haven&amp;rsquo;t responded yet because my messages haven&amp;rsquo;t even sent yet; they&amp;rsquo;ll go out in the morning, after I&amp;rsquo;ve had a chance to clean them up. So, I don&amp;rsquo;t spend any mental effort wondering if there&amp;rsquo;s a response from you. It also allows me to set the appropriate tone for the messages, by having them go out at a reasonable time, rather than appearing urgent by being sent in the middle of the night. And it does this without me needing to keep the message draft in my head as I sleep. So, I sleep better.&lt;/p&gt;
&lt;p&gt;This is why, while the ability to send messages immediately would also be a good feature to add, I am inclined to leave approve-in-the-morning messages as the system&amp;rsquo;s default behavior.&lt;/p&gt;
&lt;h3 id=&#34;notifications-from-bieber-bot&#34;&gt;Notifications from Bieber Bot&lt;/h3&gt;
&lt;p&gt;One additional small usability feature is that Bieber Bot will message me in the morning or evening whenever there are messages in the Messager Queue awaiting my approval.&lt;/p&gt;
&lt;p&gt;This sounds like a small convenience, and in fact has proven even more useful than it might at first sound.
When I wake up in the morning, I sometimes don&amp;rsquo;t remember that I&amp;rsquo;ve written messages the preceding night.
So, having Bieber Bot send me the link to the Messager Queue and gently remind me to approve the messages has been
consistently charming and welcome. Thanks, Bieber Bot.&lt;/p&gt;
&lt;h3 id=&#34;automatic-sending-from-sheets-with-messager&#34;&gt;Automatic Sending from Sheets with Messager&lt;/h3&gt;
&lt;p&gt;Messager is the underlying system that sends the messages in the Messager Queue system.
As noted previously, it supports
Facebook Messenger, Twitter,
Slack, Discord, and iMessage.
It also supports Hacker News, which hasn&amp;rsquo;t been so useful for this project.
And I am thinking I may add gChat and email support next.&lt;/p&gt;
&lt;p&gt;I use Messager for more than just sending messages from the Messager Queue spreadsheet;
it also backs other projects like my 
&lt;a href=&#34;https://davidbieber.com/snippets/2021-01-30-sql-for-the-kangaroo-auto-responder/&#34;&gt;Kangaroo Auto-responder&lt;/a&gt; and 
&lt;a href=&#34;https://davidbieber.com/projects/bieber-bot/&#34;&gt;several parts of Bieber Bot&lt;/a&gt;.
Its purpose, in the most broad sense, is to support programmatically sending and receiving messages in a uniform manner across all the messaging systems I use.&lt;/p&gt;
&lt;p&gt;For Facebook Messager, it uses 
&lt;a href=&#34;https://fbchat.readthedocs.io/en/stable/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;fbchat&lt;/a&gt; to enable programmatically sending and receiving messages as myself. It also uses Facebook&amp;rsquo;s API to allow sending messages as Bieber Bot.&lt;/p&gt;
&lt;p&gt;For Twitter, it uses the API to support public tweets, private tweets (using a separate account; 
&lt;a href=&#34;https://davidbieber.com/post/2021-03-07-roam-twitter-bot-dev-guide/&#34;&gt;see here for how I use this to reclaim my attention&lt;/a&gt;), and DMs.&lt;/p&gt;
&lt;p&gt;For iMessage, it uses AppleScript.&lt;/p&gt;
&lt;p&gt;Slack and Discord are the most recent additions, and they currently live outside the core Messager system;
they are implemented using Browserflow flows.
This means that I am effectively sending Slack and Discord messages as myself, rather than using an API to do so.
The messages are sent in a browser using clicks and keyboard presses,
all in the same human-centric UI that I would use if I were to send the messages manually.
I&amp;rsquo;m so grateful to Browserflow for making this possible, as Slack and Discord have been really key additions to this project.&lt;/p&gt;
&lt;h3 id=&#34;reflections-on-the-setup&#34;&gt;Reflections on the setup&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve been using the setup for only a few days so far, and am continuing to actively develop it.
So far, I absolutely love it.&lt;/p&gt;
&lt;p&gt;The headless typing experience provided by Go Note Go makes for a great environment for drafting messages.
So too does using Roam Research while scrolling through social media. (I&amp;rsquo;m the sort of person who doesn&amp;rsquo;t usually like replying publicly to social media posts, but does enjoy engaging with them in 1:1 or small group chat messages.)
Drifting off to sleep has proven to be another excellent time to share thoughts with friends.
I don&amp;rsquo;t want to start &lt;em&gt;a conversation&lt;/em&gt; with friends as I drift off to sleep, but loads of thoughts come to mind that I do want to share with people, and so adding them to my Messager Queue to send the next day has been quite satisfying.&lt;/p&gt;
&lt;p&gt;If this interests you, feel free to get in touch.
If you do, I&amp;rsquo;ll do my best to get back to you, likely as I&amp;rsquo;m drifting off to sleep.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A Step-by-Step Developer Guide to Building a Personal Roam-to-Twitter Bot</title>
      <link>https://davidbieber.com/post/2021-03-07-roam-twitter-bot-dev-guide/</link>
      <pubDate>Sun, 07 Mar 2021 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2021-03-07-roam-twitter-bot-dev-guide/</guid>
      
      <description>&lt;p&gt;
&lt;a href=&#34;https://davidbieber.com/snippets/2021-02-08-repurposing-my-twitter-feed/&#34;&gt;Last month I wrote a little Twitter bot for myself&lt;/a&gt; with the goal of reclaiming my attention from Twitter. The idea is to fill my Twitter feed with things that &lt;em&gt;I&lt;/em&gt; care about, rather than letting the Twitter algorithm control my attention. By programmatically injecting cards into my Twitter feed I can repurpose the time I spend scrolling on Twitter from time lost to time well spent.&lt;/p&gt;
&lt;p&gt;This post explains, step-by-step, how you can build your own private Twitter bot so you too can repurpose the time you spend doom-scrolling.&lt;/p&gt;
&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;p&gt;In this guide you&amp;rsquo;ll see how to set up a private personal Twitter bot that only you can follow. We&amp;rsquo;ll use this to inject content directly into your own feed. In order to choose what to Tweet, we&amp;rsquo;ll extract content from our Roam Research database. Here are the six steps we&amp;rsquo;ll follow.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set up a private Twitter account&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Get your developer API keys&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the twython library to post as your new Twitter account&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write the tweet generator; I pull my tweets from Roam Research&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the schedule library to periodically tweet&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(Bonus) Use supervisord to keep your Twitter bot running&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;step-1-set-up-a-private-twitter-account&#34;&gt;Step 1: Set up a private Twitter account&lt;/h2&gt;
&lt;p&gt;Go ahead and register a new Twitter account. I called mine &amp;ldquo;@PrivateBieber6&amp;rdquo; since my main account is just @Bieber. You can call yours whatever you like!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://firebasestorage.googleapis.com/v0/b/firescript-577a2.appspot.com/o/imgs%2Fapp%2Fplayground%2FiWU9fP2Eji.png?alt=media&amp;amp;token=25592447-c25f-4027-a68b-7a70d0275bd9&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll have to provide a phone number or email address while registering. If you reuse a phone number from an existing account, it will work, but you&amp;rsquo;ll lose the ability to use SMS commands and receive SMS notifications on your main account. Only the account most recently linked to this phone number will have these features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pro-tip:&lt;/strong&gt; you can add a plus (&amp;quot;+&amp;quot;) string to your email address to turn in into a new email address that still gets delivered to your inbox. E.g. mail to &lt;a href=&#34;mailto:david810+hello@gmail.com&#34;&gt;david810+hello@gmail.com&lt;/a&gt; still gets delivered to &lt;a href=&#34;mailto:david810@gmail.com&#34;&gt;david810@gmail.com&lt;/a&gt; (feel free to say hello).&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve created your new account, two important things to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Follow it from your main account!&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set it to protected.&lt;/strong&gt; To do this, go to your settings (
&lt;a href=&#34;https://twitter.com/settings/account&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://twitter.com/settings/account&lt;/a&gt;), choose &amp;ldquo;Privacy and safety&amp;rdquo; &amp;gt; &amp;ldquo;Audience and tagging&amp;rdquo; &amp;gt; Protect your Tweets.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this enabled, you&amp;rsquo;ll be the only one able to see the tweets from this account.&lt;/p&gt;
&lt;h2 id=&#34;step-2-get-your-developer-api-keys&#34;&gt;Step 2: Get your developer API keys&lt;/h2&gt;
&lt;p&gt;First you need to apply for a developer account. This just takes a minute. Go to &lt;a href=&#34;https://developer.twitter.com/en/apply-for-access&#34;&gt;https://developer.twitter.com/en/apply-for-access&lt;/a&gt; to apply for a developer account. Answer the questions honestly. Ping me on Twitter if any of the questions are unclear.&lt;/p&gt;
&lt;p&gt;Once you have a developer account, create a new project and app. I called mine &amp;ldquo;Attention Playground.&amp;rdquo; Get creative! Choose a fun name.&lt;/p&gt;
&lt;p&gt;Navigate to your newly created app, and Edit the &amp;ldquo;App permissions&amp;rdquo;. Set the app permissions to &amp;ldquo;Read and Write&amp;rdquo;. This will enable your bot to post tweets.&lt;/p&gt;
&lt;p&gt;Next navigate to the project you created, and click the key icon next to your App.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://firebasestorage.googleapis.com/v0/b/firescript-577a2.appspot.com/o/imgs%2Fapp%2Fplayground%2FzZPsa9Ywas.png?alt=media&amp;amp;token=134c8176-ca99-4837-89b7-8ef994cef9b2&#34; alt=&#34;Click the key icon.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Write down your API key and API secret key. Generate an Access token and secret and write those down too. Your access token &amp;amp; secret should say &amp;ldquo;Created with &lt;strong&gt;Read and Write&lt;/strong&gt; permissions&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;These four tokens will be essential in the next steps.&lt;/p&gt;
&lt;h2 id=&#34;step-3-use-twython-to-tweet-as-your-new-account&#34;&gt;Step 3: Use twython to tweet as your new account&lt;/h2&gt;
&lt;p&gt;Now things get a bit technical. Install the twython Python library by running &lt;code&gt;pip install twython&lt;/code&gt; from your shell. If you&amp;rsquo;re not familiar with installing Python packages, you can 
&lt;a href=&#34;https://packaging.python.org/tutorials/installing-packages/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;learn how to do this here&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s send a practice tweet. Create a new file called &lt;code&gt;hellotweet.py&lt;/code&gt; with the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import twython

client = twython.Twython(
  &amp;quot;YOUR_API_KEY&amp;quot;,
  &amp;quot;YOUR_API_SECRET_KEY&amp;quot;,
  &amp;quot;YOUR_ACCESS_TOKEN&amp;quot;,
  &amp;quot;YOUR_ACCESS_TOKEN_SECRET&amp;quot;)

def sendTweet(text):
  return client.update_status(status=text)

sendTweet(&amp;quot;Hello world!&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On lines 4-7, place the keys you got from Step 2 in stead of the placeholders I&amp;rsquo;ve included above.&lt;/p&gt;
&lt;p&gt;Now, run your program! &lt;code&gt;python hellotweet.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If everything&amp;rsquo;s worked, you&amp;rsquo;ll have a new tweet from your private account saying &amp;ldquo;Hello world!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t move on to the next steps until you get this working.&lt;/p&gt;
&lt;h2 id=&#34;step-4-write-the-tweet-generator-i-pull-my-tweets-from-roam-research&#34;&gt;Step 4: Write the tweet generator; I pull my tweets from Roam Research&lt;/h2&gt;
&lt;p&gt;The simplest possible tweet generator looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import random

def choose_message():
  messages = [&amp;quot;Tweet 1: Make spaghetti&amp;quot;,
              &amp;quot;Tweet 2: Learn about salmon&amp;quot;,
              &amp;quot;Tweet 3: Call Mom&amp;quot;]
  return random.choice(messages)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&amp;rsquo;re going to write a more interesting tweet generator that pulls tweets out of a Roam Research database.&lt;/p&gt;
&lt;p&gt;To do this, first set up a recurring json backup of your Roam Research database. You can use the project 
&lt;a href=&#34;https://github.com/everruler12/roam2github-demo&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;roam2github&lt;/a&gt; to set up the backup. There are detailed instructions on its github page for how to do this. You also have the option of using Roam&amp;rsquo;s built-in automatic backups, which you access from the command palette (cmd-P).&lt;/p&gt;
&lt;p&gt;Use git to clone your backup locally. If your github repo is &amp;ldquo;&lt;a href=&#34;https://github.com/user/roam-backup%22,&#34;&gt;https://github.com/user/roam-backup&amp;quot;,&lt;/a&gt; then the command to clone the repo is &lt;code&gt;git clone https://github.com/dbieber/roam-backup&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create roam.py. You can 
&lt;a href=&#34;https://gist.github.com/dbieber/7722280c7fa10d1fdfa507612427001d&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;copy the contents of roam.py from here&lt;/a&gt;. This will allow us to pull the latest backup of our Roam database from GitHub programmatically. (1) Set the ROAMRESEARCH_DBPATH variable in roam.py to the location of json backup in your roam-backup directory. For me, this is &amp;lsquo;/Users/dbieber/code/github/dbieber/roam-backup/json/playground.json&amp;rsquo;. (2) Set the roam_dir variable in the update function to the path to your roam-backup directory. For me, this is &amp;lsquo;/Users/dbieber/code/github/dbieber/roam-backup&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;Using the new roam module we just created, it&amp;rsquo;s time to write a function that extracts potential tweets from your Roam database. Here&amp;rsquo;s the function I use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def choose_message():
  all_children = roam.get_children()
  messages = []
  for child in all_children:
    if (&#39;Fleeting TODOs&#39; in child[&#39;string&#39;]
        or &#39;Distraction TODOs&#39; in child[&#39;string&#39;]):
      children = child.get(&#39;children&#39;, [])
      for c in children:
        if (c[&#39;string&#39;]
            and &#39;DONE&#39; not in c[&#39;string&#39;]
            and len(c[&#39;string&#39;]) &amp;lt;= 280):
          messages.append(c[&#39;string&#39;])
  return random.choice(messages)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;get_children&lt;/code&gt; function gets all the blocks from your Roam graph backup.  The for loop looks through all these blocks for ones that say &amp;ldquo;Fleeting TODOs&amp;rdquo; or &amp;ldquo;Distraction TODOs&amp;rdquo;. It takes the children of &lt;em&gt;those&lt;/em&gt; blocks (except those that are too long or say &amp;ldquo;DONE&amp;rdquo;) as potential tweets. One of those is selected randomly each time choose_messages is called.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s our whole tweet generator. In the next step, we&amp;rsquo;ll pull the Roam backup from GitHub periodically automatically so it&amp;rsquo;s always somewhat up to date, and we&amp;rsquo;ll schedule the bot to tweet every ten minutes.&lt;/p&gt;
&lt;h2 id=&#34;step-5-use-the-schedule-library-to-periodically-tweet&#34;&gt;Step 5: Use the schedule library to periodically tweet&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;schedule&lt;/code&gt; library is a useful Python library for scheduling tasks to run at a particular time or on a recurring basis.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a simple demonstration of how to use &lt;code&gt;schedule&lt;/code&gt; to print &amp;ldquo;Hello world!&amp;rdquo; every 15 minutes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;import schedule

def print_hello_world():
  print(&#39;Hello world!&#39;)

schedule.every(15).minutes.do(print_hello_world)
while True:
  schedule.run_pending()
  time.sleep(30)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We don&amp;rsquo;t want to print hello world though; we want to tweet!&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to use &lt;code&gt;schedule&lt;/code&gt; to schedule two recurring tasks: (1) every 6 hours we&amp;rsquo;ll pull the latest Roam backup from GitHub, (2) every 10 minutes we&amp;rsquo;ll have our Bot pick a random block using the logic from the previous step and tweet it to twitter.&lt;/p&gt;
&lt;p&gt;The code for that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def tweet():
  message = choose_message()
  sendTweet(message)


def main():
  schedule.every(10).minutes.do(tweet)
  schedule.every(360).minutes.do(roam.update)

  while True:
    schedule.run_pending()
    time.sleep(30)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Putting it all together, our complete code now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;&amp;quot;&amp;quot;&amp;quot;Every 10 minutes selects a topic from my Roam database and tweets it
from a private Twitter account.
&amp;quot;&amp;quot;&amp;quot;

import random
import time

import schedule
import roam

import twython


client = twython.Twython(
  &amp;quot;YOUR_API_KEY&amp;quot;,
  &amp;quot;YOUR_API_SECRET_KEY&amp;quot;,
  &amp;quot;YOUR_ACCESS_TOKEN&amp;quot;,
  &amp;quot;YOUR_ACCESS_TOKEN_SECRET&amp;quot;)


def sendTweet(text):
  return client.update_status(status=text)


def choose_message():
  all_children = roam.get_children()
  messages = []
  for child in all_children:
    if (&#39;Fleeting TODOs&#39; in child[&#39;string&#39;]
        or &#39;Distraction TODOs&#39; in child[&#39;string&#39;]):
      children = child.get(&#39;children&#39;, [])
      for c in children:
        if (c[&#39;string&#39;]
            and &#39;DONE&#39; not in c[&#39;string&#39;]
            and len(c[&#39;string&#39;]) &amp;lt;= 280):
          messages.append(c[&#39;string&#39;])
  return random.choice(messages)


def tweet():
  message = choose_message()
  sendTweet(message)


def main():
  schedule.every(10).minutes.do(tweet)
  schedule.every(360).minutes.do(roam.update)

  while True:
    schedule.run_pending()
    time.sleep(30)


if __name__ == &#39;__main__&#39;:
  main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure that both the &amp;ldquo;tweet&amp;rdquo; function and &amp;ldquo;roam.update&amp;rdquo; run successfully.&lt;/p&gt;
&lt;p&gt;Save this as roam-tweeter.py. You can now run it with &lt;code&gt;python roam-tweeter.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s it! While this is running, your Twitter bot will post a new tweet to twitter every ten minutes. Using this, you can flood your Twitter timeline with things &lt;em&gt;you&lt;/em&gt; care about, whether that&amp;rsquo;s your TODOs, spaced repetition, or old ideas you want to revisit.&lt;/p&gt;
&lt;h2 id=&#34;step-6-bonus-use-supervisord-to-keep-your-twitter-bot-running&#34;&gt;Step 6 (Bonus): Use supervisord to keep your Twitter bot running&lt;/h2&gt;
&lt;p&gt;This is an optional step, but highly worthwhile! Supervisord is a utility you can use to monitor processes and automatically restart them if they die. If you plan on using your computer for long-running processes like a Twitter bot, this can save you lots of headaches.&lt;/p&gt;
&lt;p&gt;Follow the instructions at 
&lt;a href=&#34;http://supervisord.org/installing.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;supervisord.org&lt;/a&gt; to get set up. The key steps are:&lt;/p&gt;
&lt;p&gt;To install supervisord, use &lt;code&gt;pip install supervisord&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Add a config for running your Twitter bot. Mine looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;[program:private-bieber-twitter]
command=/Users/dbieber/.virtualenvs/_3/bin/python roam-tweeter.py
directory=/Users/dbieber/code/playground/private-bieber-twitter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that I&amp;rsquo;ve included the full Path to python in the command.&lt;/p&gt;
&lt;p&gt;Finally, start supervisord with &lt;code&gt;sudo&lt;/code&gt; indicating the path to the config. (e.g. for me the command is &lt;code&gt;sudo /Users/dbieber/.virtualenvs/_3/bin/supervisord -c ~/path/to/supervisord.conf&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;With supervisord set up, you&amp;rsquo;re bot is now bullet-proof! (Not bug-proof though.) Even if it encounters an error, it will automatically restart.&lt;/p&gt;
&lt;h2 id=&#34;wrap-up&#34;&gt;Wrap-up&lt;/h2&gt;
&lt;p&gt;Hopefully this guide makes it clear how you can use a private Twitter bot to reclaim your attention. The main contribution of this guide is its emphasis on using a private Twitter bot to design your own personal Twitter feed. We selected Roam Research backups as the source of tweets for our bot, but of course you can substitute Roam with any tweet generator that you please.&lt;/p&gt;
&lt;p&gt;You can use a bot like this for revisiting old ideas, for surfacing your TODOs when you&amp;rsquo;re distracted, for sending yourself messages, or anything else you come up with.&lt;/p&gt;
&lt;p&gt;Since making myself a private Twitter bot one month ago, I&amp;rsquo;ve been delighted to find that its tweets are often among the highest quality on my timeline. I hope you find the same.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;When it says that using a virtual environment is optional, know that it&amp;rsquo;s definitely worthwhile! &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Where are all the posts from 2020?</title>
      <link>https://davidbieber.com/post/2021-01-08-where-are-all-the-posts-from-2020/</link>
      <pubDate>Fri, 08 Jan 2021 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2021-01-08-where-are-all-the-posts-from-2020/</guid>
      
      <description>&lt;p&gt;You&amp;rsquo;ll notice I have no 
&lt;a href=&#34;https://davidbieber.com/posts/&#34;&gt;blog posts&lt;/a&gt; from 2020, but this is not because I stopped writing. Instead, I have been writing &amp;ldquo;snippets,&amp;rdquo; which 
&lt;a href=&#34;https://davidbieber.com/snippets/&#34;&gt;you can read here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re going to read just one snippet, 
&lt;a href=&#34;https://davidbieber.com/snippets/2019-12-30-writing-for-no-audience/&#34;&gt;I recommend that it be this one&lt;/a&gt;.
The main idea behind snippets is to allow me to write without the inhibitions I sometimes experience when writing a blog post for publication.
By writing in a secluded section of the website with no (now, few) inbound links, and by deliberately keeping the quality bar for publication low to non-existent, I made it easier for myself to write often.&lt;/p&gt;
&lt;p&gt;To keep the quality bar low,
sometimes I would deliberately post a short half-baked thought, possibly without editing or revision.
Despite this aim of keeping the quality bar low, and despite explicitly writing for no one, I often would find myself pleased with what I had written and eager for someone else to read it.&lt;/p&gt;
&lt;p&gt;To me, this is the beauty of the setup. The (lack of) quality standards make &lt;em&gt;starting writing&lt;/em&gt; easy. I typically start writing a snippet with the mindset that I have a little idea I want to get out of my head and onto paper. As I begin, I think it&amp;rsquo;s a small idea that will take just a few lines. Perhaps in the back of my mind I even think it will contribute to lowering of the quality-bar for the section of the website. And sometimes it does. But just as often, I find myself ready to elaborate on the idea. Perhaps I explain where the idea came from, or why I find it interesting. And by the time I&amp;rsquo;ve finished writing, I have a snippet that I&amp;rsquo;m proud of, and which I would love to discuss.&lt;/p&gt;
&lt;p&gt;This is why I sometimes put my email address at the end of a snippet. With just a cursory description of the snippets section (&amp;ldquo;a place to write for no audience&amp;rdquo;), seeing my contact info at the end of a post may feel contradictory. Now you see that it is not; the lack of a quality bar and the lack of an audience serve their purpose in allowing me to start writing easily, and to publish readily, but once I have written something and released it to (a tiny unlinked corner of) the world I do not necessarily want to keep it hidden.&lt;/p&gt;
&lt;p&gt;So, I am now for the first time linking from my main website 
&lt;a href=&#34;https://davidbieber.com/snippets/&#34;&gt;directly to the snippets section of my website&lt;/a&gt;. It&amp;rsquo;s not a prominent link, as it&amp;rsquo;s contained only within this blog post, but it&amp;rsquo;s a meaningful step for me. Web crawlers now have a means in to index the site. Visitors to the site now have a means in too, not just people to whom I explicitly send the link.&lt;/p&gt;
&lt;p&gt;If you do venture in, and read a snippet or two, there are a few things to know.
First, you&amp;rsquo;re welcome here. It&amp;rsquo;s not voyeurism. These aren&amp;rsquo;t private posts; they&amp;rsquo;re simply not written for an audience, but I am happy for them to have one. Second, they&amp;rsquo;re snippets. Think of them like ideas scrawled in a hurry. I&amp;rsquo;m not wedded to the ideas I express, and I&amp;rsquo;m open to reconsidering what I wrote. And thirdly, I value your thoughts (and your time!), and it would delight me to hear them.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll find ideas for side-projects, philosophical musings, technical notes meant mainly for future-me, technical notes meant for other people, project updates, nonsense, personal notes, and more. All of this, 
&lt;a href=&#34;https://davidbieber.com/snippets&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Keeping Track of My Life in a Spreadsheet</title>
      <link>https://davidbieber.com/post/2019-12-29-track-your-life-in-a-spreadsheet/</link>
      <pubDate>Sun, 29 Dec 2019 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2019-12-29-track-your-life-in-a-spreadsheet/</guid>
      
      <description>&lt;p&gt;As many of my friends know, I keep track of much of my life in spreadsheets. There are several pieces to this system, but fortunately it&amp;rsquo;s the kind of system that you can adopt (and discard) incrementally. So, rather than explaining the whole thing in one go, I will explain one core piece in this post.&lt;/p&gt;
&lt;p&gt;In this post, I will detail how I use a spreadsheet as a &lt;em&gt;daily activity log&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;My spreadsheet lets me keep a record of all the things I&amp;rsquo;ve done.
It then also rolls up this data in interesting ways that help me with reflecting on and planning my life. Finally, it interacts with a bot system I&amp;rsquo;ve written for myself to help me keep my life on track.&lt;/p&gt;
&lt;p&gt;I will answer all of the following questions in this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What&amp;rsquo;s the organizational system?&lt;/li&gt;
&lt;li&gt;What are the main benefits of this system?&lt;/li&gt;
&lt;li&gt;Why is this system easy to adopt?&lt;/li&gt;
&lt;li&gt;Why is this system easy to keep doing for months on end, without forgetting?&lt;/li&gt;
&lt;li&gt;How does this system help me with planning my life?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;the-system&#34;&gt;The System&lt;/h3&gt;
&lt;p&gt;I have a single spreadsheet with columns &lt;em&gt;Evening&lt;/em&gt;, &lt;em&gt;Morning&lt;/em&gt;, &lt;em&gt;Keywords&lt;/em&gt;, &lt;em&gt;Weekday&lt;/em&gt;, &lt;em&gt;Date&lt;/em&gt;, &lt;em&gt;People&lt;/em&gt;, &lt;em&gt;Notes&lt;/em&gt;. In this sheet, I have a single row &lt;em&gt;per day&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The essense of the system is to jot down (1) what I do, and optionally (2) who I engage with each day.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t update the spreadsheet every day, usually just once a week or so I&amp;rsquo;ll fill in the activity from all of the missing days.&lt;/p&gt;
&lt;p&gt;This system serves several purposes, the main one being a journal of sorts so I can see a historical log of my activities. As you&amp;rsquo;ll see in the Planning and Notifications sections, it also helps me remember to regularly do all the activities I enjoy doing.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll start by providing a brief description of each column in the spreadsheet. Then we&amp;rsquo;ll get to discuss how I use the spreadsheet, and you&amp;rsquo;ll see why they&amp;rsquo;re each important to me. We&amp;rsquo;ll then also dive into all the benefits I get from this spreadsheet and the infrastructure I&amp;rsquo;ve built up around it.&lt;/p&gt;
&lt;h4 id=&#34;column-descriptions&#34;&gt;Column Descriptions&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Evening&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;A single cell in which to put the main activity I did in the evening (e.g. after work) on that day.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Morning&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Optional, Text, infrequently used&lt;/td&gt;
&lt;td&gt;The main activity I did in the morning (e.g. before work) on that day.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Keywords&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Optional, comma separated&lt;/td&gt;
&lt;td&gt;Keywords describing the activities of that day.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Weekday&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Filled automatically&lt;/td&gt;
&lt;td&gt;The day of the week, e.g. Thursday&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Date&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Filled automatically&lt;/td&gt;
&lt;td&gt;The date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;People&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Names, comma separated&lt;/td&gt;
&lt;td&gt;A list of the people I engaged with on that day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;em&gt;Notes&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Optional, Text&lt;/td&gt;
&lt;td&gt;A bit like a diary, this is a place to jot down additional notes about the day.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Really all of the columns are optional, but the ones that are particularly optional are marked as such.&lt;/p&gt;
&lt;h3 id=&#34;usage&#34;&gt;Usage&lt;/h3&gt;
&lt;p&gt;Now I&amp;rsquo;ll describe the data entry process, and then all the ways I take advantage of this data.&lt;/p&gt;
&lt;h4 id=&#34;data-entry&#34;&gt;Data Entry&lt;/h4&gt;
&lt;p&gt;About once a week on average I&amp;rsquo;ll go to the spreadsheet, and fill in all the activities I did since the last time I updated the sheet.&lt;/p&gt;
&lt;p&gt;For each day, I try to capture just the main one to two outside-of-work activities that I engaged in. It&amp;rsquo;s totally fine if I don&amp;rsquo;t capture every little thing, and it&amp;rsquo;s also fine if there are some days that I didn&amp;rsquo;t do anything of note.&lt;/p&gt;
&lt;p&gt;I said above that I do this about once a week, but there&amp;rsquo;s no strong rhythm to it; sometimes I do it every day for multiple consecutive days, and sometimes I&amp;rsquo;ll go multiple weeks between updating the sheet.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using this system reliably since mid-2018, and it&amp;rsquo;s served me well. The notifications that we&amp;rsquo;ll get to shortly, combined with the simplicity of the method (just one cell to fill in per day), have allowed me to keep using this system without being disrupted by travel or high-workload parts of life.&lt;/p&gt;
&lt;h4 id=&#34;planning&#34;&gt;Planning&lt;/h4&gt;
&lt;p&gt;I have a second sheet that is updated automatically using data from the sheet I&amp;rsquo;ve been describing so far. This sheet has one row &lt;em&gt;per activity&lt;/em&gt; that I enjoy engaging in.&lt;/p&gt;
&lt;p&gt;The most important columns in this sheet are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Category&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;The category of the activity (e.g. Athletic).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activity&lt;/td&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;An activity that I enjoy engaging in. (e.g. Skiing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last Date&lt;/td&gt;
&lt;td&gt;Filled automatically&lt;/td&gt;
&lt;td&gt;The most recent date on which I engaged in the activity.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;Last Date&lt;/code&gt; column is filled automatically using the data from the main sheet. The Google Sheets formula that fills in this column is a query a bit like this one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;=IFERROR(QUERY(scheduled,
   &amp;quot;select E
    where A contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39;
      OR B contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39;
      OR C contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39;
    order by E desc
    limit 1
    label E &#39;&#39;&amp;quot;
))
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C2&lt;/code&gt; refers to the &lt;code&gt;Activity&lt;/code&gt; from the current sheet&lt;/li&gt;
&lt;li&gt;&lt;code&gt;A&lt;/code&gt; is the &lt;code&gt;Evening&lt;/code&gt; column from the Data Entry sheet&lt;/li&gt;
&lt;li&gt;&lt;code&gt;B&lt;/code&gt; is the &lt;code&gt;Morning&lt;/code&gt; column from the Data Entry sheet&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C&lt;/code&gt; is the &lt;code&gt;Keywords&lt;/code&gt; column from the Data Entry sheet&lt;/li&gt;
&lt;li&gt;&lt;code&gt;E&lt;/code&gt; is the &lt;code&gt;Date&lt;/code&gt; from the Data Entry sheet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This lets me quickly see, for each activity that I enjoy, how recently I&amp;rsquo;ve done that activity. For example, I see that I haven&amp;rsquo;t gone camping since 2019-07-05, I haven&amp;rsquo;t played tennis since 2018-07-31, and I haven&amp;rsquo;t gone swimming since 2019-10-07. Really overdue for some tennis!&lt;/p&gt;
&lt;p&gt;My spreadsheet also rolls up this data further, by category. I can quickly see how recently I&amp;rsquo;ve done any activity in a given category.&lt;/p&gt;
&lt;p&gt;The categories I use are:
&amp;ldquo;Athletic&amp;rdquo;, &amp;ldquo;Civic Something Or Other&amp;rdquo;, &amp;ldquo;Day Trips&amp;rdquo;, &amp;ldquo;Educational&amp;rdquo;, &amp;ldquo;Entertainment&amp;rdquo;, &amp;ldquo;Family&amp;rdquo;, &amp;ldquo;Financial&amp;rdquo;, &amp;ldquo;Food&amp;rdquo;, &amp;ldquo;Music + Arts&amp;rdquo;, &amp;ldquo;Outdoorsy&amp;rdquo;, &amp;ldquo;Personal Projects&amp;rdquo;, &amp;ldquo;Puzzles&amp;rdquo;, &amp;ldquo;Social&amp;rdquo;, &amp;ldquo;Social Games&amp;rdquo;, and &amp;ldquo;Well being&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Here you can see how recently I&amp;rsquo;ve done something in each of the categories.&lt;/p&gt;





  
  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://davidbieber.com/post/2019-12-29-track-your-life-in-a-spreadsheet/i-activities-by-category_hu2806f35bd91a07c85620e1b361834210_97421_2000x2000_fit_box_2.png&#34; &gt;


  &lt;img data-src=&#34;https://davidbieber.com/post/2019-12-29-track-your-life-in-a-spreadsheet/i-activities-by-category_hu2806f35bd91a07c85620e1b361834210_97421_2000x2000_fit_box_2.png&#34; class=&#34;lazyload&#34; alt=&#34;A two-column spreadsheet showing how recently I&amp;#39;ve done an activity in each of the 15 activity categories.&#34; width=&#34;200px&#34; height=&#34;732&#34;&gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;As you can see, I&amp;rsquo;m doing a pretty good job of doing most of the categories of activities. When I go to make plans next, I&amp;rsquo;ll most likely schedule activities from the bottom categories. For example, I may do a financial review, schedule a board game night, and plan to attend a City Council meeting.&lt;/p&gt;
&lt;h4 id=&#34;notifications&#34;&gt;Notifications&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;ve also given 
&lt;a href=&#34;https://davidbieber.com/projects/bieber-bot/&#34;&gt;Bieber Bot&lt;/a&gt; (my digital personal assistant and robot friend) access to this data, and he helps me make better use of it.&lt;/p&gt;
&lt;p&gt;If it&amp;rsquo;s been too long since I&amp;rsquo;ve scheduled an activity in any particular category, Bieber Bot will message me on FB Messenger reminding me to schedule something in that category.&lt;/p&gt;





  
  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://davidbieber.com/post/2019-12-29-track-your-life-in-a-spreadsheet/i-bieberbot-financial_huf983f5ecddfe8f1a3e2e0cb4f01b88c8_58408_2000x2000_fit_box_2.png&#34; &gt;


  &lt;img data-src=&#34;https://davidbieber.com/post/2019-12-29-track-your-life-in-a-spreadsheet/i-bieberbot-financial_huf983f5ecddfe8f1a3e2e0cb4f01b88c8_58408_2000x2000_fit_box_2.png&#34; class=&#34;lazyload&#34; alt=&#34;A message from Bieber Bot reminding me that it&amp;#39;s been a while since I scheduled a Financial activity.&#34; width=&#34;350px&#34; height=&#34;240&#34;&gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;Similarly BieberBot will remind me each day of any upcoming activities planned for that day.
And if I don&amp;rsquo;t have any activity planned for a particular day, BieberBot may take the liberty of suggesting an activity.&lt;/p&gt;





  
  











&lt;figure &gt;


  &lt;a data-fancybox=&#34;&#34; href=&#34;https://davidbieber.com/post/2019-12-29-track-your-life-in-a-spreadsheet/i-bieberbot-hudson_hued59abe63d99bee46aaa05358969b28a_120266_2000x2000_fit_box_2.png&#34; &gt;


  &lt;img data-src=&#34;https://davidbieber.com/post/2019-12-29-track-your-life-in-a-spreadsheet/i-bieberbot-hudson_hued59abe63d99bee46aaa05358969b28a_120266_2000x2000_fit_box_2.png&#34; class=&#34;lazyload&#34; alt=&#34;A message from Bieber Bot suggesting I take a walk along the Hudson River.&#34; width=&#34;350px&#34; height=&#34;360&#34;&gt;
&lt;/a&gt;



&lt;/figure&gt;

&lt;p&gt;Finally BieberBot will also remind me if I haven&amp;rsquo;t been entering activities into the spreadsheet in a while, so I don&amp;rsquo;t fall too far behind on the logging system.&lt;/p&gt;
&lt;p&gt;Setting up these notifcations is a bit outside the scope of this post, but if it interests you how BieberBot works, you can subscribe for future posts here:&lt;/p&gt;

&lt;link href=&#34;//cdn-images.mailchimp.com/embedcode/classic-10_7.css&#34; rel=&#34;stylesheet&#34; type=&#34;text/css&#34;&gt;
&lt;style type=&#34;text/css&#34;&gt;
  #mc_embed_signup{
    background:#fff;
    clear:left;
    font:14px Helvetica,Arial,sans-serif;
  }
&lt;/style&gt;
&lt;div id=&#34;mc_embed_signup&#34;&gt;
&lt;form action=&#34;https://gmail.us3.list-manage.com/subscribe/post?u=a55774562ae9fa9a1d879fa75&amp;amp;id=132f89def3&#34; method=&#34;post&#34; id=&#34;mc-embedded-subscribe-form&#34; name=&#34;mc-embedded-subscribe-form&#34; class=&#34;validate&#34; target=&#34;_blank&#34; novalidate&gt;
    &lt;div id=&#34;mc_embed_signup_scroll&#34;&gt;
  
&lt;div class=&#34;mc-field-group&#34;&gt;
  &lt;label for=&#34;mce-EMAIL&#34;&gt;Email Address&lt;/label&gt;
  &lt;input type=&#34;email&#34; value=&#34;&#34; name=&#34;EMAIL&#34; class=&#34;required email&#34; id=&#34;mce-EMAIL&#34;&gt;
&lt;/div&gt;
  &lt;div id=&#34;mce-responses&#34; class=&#34;clear&#34;&gt;
    &lt;div class=&#34;response&#34; id=&#34;mce-error-response&#34; style=&#34;display:none&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;response&#34; id=&#34;mce-success-response&#34; style=&#34;display:none&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;    
    &lt;div style=&#34;position: absolute; left: -5000px;&#34; aria-hidden=&#34;true&#34;&gt;&lt;input type=&#34;text&#34; name=&#34;b_a55774562ae9fa9a1d879fa75_132f89def3&#34; tabindex=&#34;-1&#34; value=&#34;&#34;&gt;&lt;/div&gt;
    &lt;div class=&#34;clear&#34;&gt;
      &lt;input type=&#34;submit&#34; value=&#34;Subscribe&#34; name=&#34;subscribe&#34; id=&#34;mc-embedded-subscribe&#34; class=&#34;button&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;script type=&#39;text/javascript&#39; src=&#39;//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js&#39;&gt;&lt;/script&gt;
&lt;script type=&#39;text/javascript&#39;&gt;(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]=&#39;EMAIL&#39;;ftypes[0]=&#39;email&#39;;fnames[1]=&#39;FNAME&#39;;ftypes[1]=&#39;text&#39;;fnames[2]=&#39;LNAME&#39;;ftypes[2]=&#39;text&#39;;fnames[3]=&#39;ADDRESS&#39;;ftypes[3]=&#39;address&#39;;fnames[4]=&#39;PHONE&#39;;ftypes[4]=&#39;phone&#39;;fnames[5]=&#39;BIRTHDAY&#39;;ftypes[5]=&#39;birthday&#39;;}(jQuery));var $mcj = jQuery.noConflict(true);&lt;/script&gt;

&lt;p&gt;You can also learn a bit more about BieberBot 
&lt;a href=&#34;https://davidbieber.com/projects/bieber-bot/&#34;&gt;here&lt;/a&gt;, but there&amp;rsquo;s so much more to be written.&lt;/p&gt;
&lt;h4 id=&#34;archiving-old-events&#34;&gt;Archiving Old Events&lt;/h4&gt;
&lt;p&gt;To keep things clean, my spreadsheet also uses Google Apps Script to archive old events. You can see the code I use for the auto-archiving 
&lt;a href=&#34;https://gist.github.com/dbieber/42153e6a27382ba6193f108c13b84cf9&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;getting-started&#34;&gt;Getting Started&lt;/h4&gt;
&lt;p&gt;If this system sounds appealing to you, it&amp;rsquo;s very simple to get started.&lt;/p&gt;
&lt;p&gt;Simply create a new Google sheet with the columns you&amp;rsquo;d like to track. I use the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;em&gt;Evening&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Morning&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Keywords&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Weekday&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Date&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;People&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Notes&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;But for just getting started you might be satisfied with something simpler, say just these three columns:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;em&gt;Activity&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Weekday&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Date&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;You can use the formula &lt;code&gt;=TEXT(WEEKDAY(E3), &amp;quot;dddd&amp;quot;)&lt;/code&gt; to autopopulate the &lt;code&gt;Weekday&lt;/code&gt; column, where E3 represents the date (tip: you can use cmd-; to enter today&amp;rsquo;s date into a cell).&lt;/p&gt;
&lt;p&gt;I also create a second sheet called &lt;code&gt;Archive&lt;/code&gt; with the same columns as the main sheet, and use the 
&lt;a href=&#34;https://gist.github.com/dbieber/42153e6a27382ba6193f108c13b84cf9&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;auto-archiving feature&lt;/a&gt; mentioned earlier to prevent the main sheet from getting cluttered.&lt;/p&gt;
&lt;p&gt;Next create the analysis sheet with the columns:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;em&gt;Category&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Activity&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;Last Date&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p&gt;The first two columns you can fill in with whatever categories and activities you like, and you can add new categories and activities at any time. No need to fill this in too much until you start to see how you&amp;rsquo;re using the main sheet.&lt;/p&gt;
&lt;p&gt;To autofill the &lt;code&gt;Last Date&lt;/code&gt; column, the full formula I use is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;=MAX(
  IFERROR(QUERY(scheduled, 
    &amp;quot;select E
    where A contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39; OR B contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39; or C contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39;
    order by E desc
    limit 1
    label E &#39;&#39;&amp;quot;
  )),
  IFERROR(QUERY(archive, 
    &amp;quot;select E
    where A contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39; OR B contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39; or C contains &#39;&amp;quot; &amp;amp; C2 &amp;amp; &amp;quot;&#39;
    order by E desc
    limit 1
    label E &#39;&#39;&amp;quot;
  ))
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This searches both the main sheet (scheduled) and the archive sheet for the most recent occurrence of each activity (C2). For this to work, the rows in the archive and main sheet need to be in reverse chronological order.&lt;/p&gt;
&lt;p&gt;Finally, to get the roll-up by category, I use the following Google Sheets formula:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;=QUERY(A:E,
       &amp;quot;select B, max(D), count(B) where B&amp;lt;&amp;gt;&#39;&#39; group by B order by max(D) desc
        label max(D) &#39;Last date&#39;,
              count(B) &#39;Count&#39;
       &amp;quot;, -1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Though I would like to, I won&amp;rsquo;t go into how to set up the notifications in this post. For now, that&amp;rsquo;s just between me and 
&lt;a href=&#34;https://davidbieber.com/projects/bieber-bot/&#34;&gt;BieberBot&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;As a quick recap of my system:
(1) I manually enter the activity(s) I do each day; (2) the spreadsheet automatically figures out how recently I did each category of activity; (3) bonus: BieberBot uses this data to help me keep my life on track.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using this system for about 18 months now, and I&amp;rsquo;ve been quite happy with the insight it provides and how helpful it is for planning.&lt;/p&gt;
&lt;p&gt;Still, there are many pieces of my system left undiscussed: tracking financial transactions, taking notes on academic papers, staying in touch with friends and colleagues, dealing with social media, and more! I&amp;rsquo;d love to tell you about the rest, so stay tuned. And don&amp;rsquo;t hesitate to 
&lt;a href=&#34;mailto:david810&amp;#43;blog@gmail.com&#34;&gt;reach out&lt;/a&gt; if you&amp;rsquo;d like to discuss anything you read. I hope you find this useful, and do let me know if you start using a spreadsheet to track your own life or have a system of your own.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Weisfeiler-Lehman Isomorphism Test</title>
      <link>https://davidbieber.com/post/2019-05-10-weisfeiler-lehman-isomorphism-test/</link>
      <pubDate>Fri, 10 May 2019 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2019-05-10-weisfeiler-lehman-isomorphism-test/</guid>
      
      <description>&lt;h3 id=&#34;graph-isomorphism&#34;&gt;Graph Isomorphism&lt;/h3&gt;
&lt;p&gt;Two graphs are considered isomorphic if there is a mapping between the nodes of the graphs that preserves node adjacencies.
That is, a pair of nodes may be connected by an edge in the first graph if and only if the corresponding pair of nodes in the second graph is also connected by an edge in the same way.
An example of two isomorphic graphs is shown here.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism.png&#34; alt=&#34;Example of isomorphic graphs&#34; title=&#34;Two isomorphic graphs are shown.&#34;&gt;
&lt;em&gt;Figure: Graph 1 and Graph 2 are isomorphic.
The correspondance between nodes is illustrated by the node colors and numbers.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In general, determining whether two graphs are isomorphic when the correspondance is not provided is a challenging problem; precisely how hard this problem is remains an open question in computer science.
It isn&amp;rsquo;t known whether there is a polynomial time algorithm for determining whether graphs are isomorphic, and it also isn&amp;rsquo;t known whether the problem is $\text{NP-complete}.$
The graph isomorphism problem may even be an example of an 
&lt;a href=&#34;https://en.wikipedia.org/wiki/NP-intermediate&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;$\text{NP-intermediate}$&lt;/a&gt; problem, but this would only be possible if $\text{P} \ne \text{NP}.$&lt;/p&gt;
&lt;h3 id=&#34;the-weisfeiler-lehman-isomorphism-test&#34;&gt;The Weisfeiler-Lehman Isomorphism Test&lt;/h3&gt;
&lt;p&gt;Here is the algorithm for the Weisfeiler-Lehman Isomorphism Test.
It produces for each graph a canonical form.
If the canonical forms of two graphs are not equivalent, then the graphs are definitively not isomorphic.
However, it is possible for two non-isomorphic graphs to share a canonical form, so this test alone cannot provide conclusive evidence that two graphs are isomorphic.&lt;/p&gt;
&lt;p&gt;The Algorithm:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For iteration $i$ of the algorithm we will be assigning to each node a tuple $L_{i,n}$ containing the node&amp;rsquo;s old compressed label and a multiset of the node&amp;rsquo;s neighbors&#39; compressed labels.
A multiset is a set (a collection of elements where order is not important) where elements may appear multiple times.&lt;/li&gt;
&lt;li&gt;At each iteration we will additionally be assigning to each node $n$ a new &amp;ldquo;compressed&amp;rdquo; label $C_{i,n}$ for that node&amp;rsquo;s set of labels.
Any two nodes with the same $L_{i,n}$ will get the same compressed label.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;To begin, we initialize $C_{0,n} = 1$ for all nodes $n$.&lt;/li&gt;
&lt;li&gt;At iteration $i$ of the algorithm (beginning with $i=1$), for each node $n$, we set $L_{i,n}$ to be a tuple containing the node&amp;rsquo;s old label $C_{i-1,n}$ and the multiset of compressed node labels $C_{i-1,m}$ from all nodes $m$ neighboring $n$ from the previous iteration $(i-1)$.&lt;/li&gt;
&lt;li&gt;We then complete iteration $i$ by setting $C_{i,n}$ to be a new &amp;ldquo;compressed&amp;rdquo; label, such as a hash of $L_{i,n}$.
Any two nodes with the same labels $L_{i,n}$ must get the same compressed label $C_{i,n}$.&lt;/li&gt;
&lt;li&gt;Partition the nodes in the graph by their compressed label.
Repeat 2 + 3 for up to $N$ (the number of nodes) iterations, or until there is no change in the partition of nodes by compressed label from one iteration to the next.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When using this method to determine graph isomorphism, it may be applied in parallel to the two graphs.
The algorithm may be terminated early after iteration $i$ if the sizes of partitions of nodes partitioned by compressed labels diverges between the two graphs; if this is the case, the graphs are not isomorphic.&lt;/p&gt;
&lt;h3 id=&#34;example-of-the-weisfeiler-lehman-isomorphism-test&#34;&gt;Example of the Weisfeiler-Lehman Isomorphism Test&lt;/h3&gt;
&lt;p&gt;We demonstrate here the Weisfeiler-Lehman isomorphism test using the example graphs from above.
The graphs are shown again here for completeness.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism-000.png&#34; alt=&#34;Two isomorphic graphs are shown.&#34; title=&#34;Two isomorphic graphs are shown.&#34;&gt;
&lt;em&gt;Figure: Graph 1 and Graph 2 are isomorphic.
We will apply the Weisfeiler-Lehman isomorphism test to these graphs as a means of illustrating the test.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To initialize the algorithm (Step 1), we set $C_{0,n} = 1$ for all nodes $n$.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism-001.png&#34; alt=&#34;Initialization: $C\_{0,n} = 1$ for all nodes $n$&#34; title=&#34;We initialize $C_{0,n} = 1$ for all nodes.&#34;&gt;&lt;/p&gt;
&lt;p&gt;For iteration 1, Step 2, we compute $L_{1,n}$.
The first part of a node&amp;rsquo;s $L$ is the node&amp;rsquo;s old compressed label; the second part of a node&amp;rsquo;s $L$ is the multiset of the neighboring nodes&#39; compressed labels.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism-002.png&#34; alt=&#34;Iteration 1, Step 2: $L\_{1,n}$&#34; title=&#34;Iteration 1, Step 2: $L_{1,n}$&#34;&gt;&lt;/p&gt;
&lt;p&gt;For iteration 1, Step 3, we introduce &amp;ldquo;compressed&amp;rdquo; labels $C_{1,n}$ for the nodes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism-003.png&#34; alt=&#34;Iteration 1, Step 3: $C\_{1,n}$&#34; title=&#34;Iteration 1, Step 3: $C_{1,n}$&#34;&gt;&lt;/p&gt;
&lt;p&gt;We now begin iteration 2.
In iteration 2, Step 2, we compute $L_{2,n}$:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism-004.png&#34; alt=&#34;Iteration 2, Step 2: $L\_{2,n}$&#34; title=&#34;Iteration 2, Step 2: $L_{2,n}$&#34;&gt;&lt;/p&gt;
&lt;p&gt;In iteration 2, Step 3, we compute $C_{2,n}$:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism-005.png&#34; alt=&#34;Iteration 2, Step 3: $C\_{2,n}$&#34; title=&#34;Iteration 2, Step 3: $C_{2,n}$&#34;&gt;&lt;/p&gt;
&lt;p&gt;In iteration 3, Step 2, we compute $L_{3,n}$:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism-006.png&#34; alt=&#34;Iteration 3, Step 2: $L\_{3,n}$&#34; title=&#34;Iteration 3, Step 2: $L_{3,n}$&#34;&gt;&lt;/p&gt;
&lt;p&gt;In iteration 3, Step 3, we compute $C_{3,n}$:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;graph-isomorphism-007.png&#34; alt=&#34;Iteration 3, Step 3: $C\_{3,n}$&#34; title=&#34;Iteration 3, Step 3: $C_{3,n}$&#34;&gt;&lt;/p&gt;
&lt;p&gt;Since the partition of nodes by compressed label has not changed from $C_{2,n}$ to $C_{3,n}$, we may terminate the algorithm here.&lt;/p&gt;
&lt;p&gt;Concretely, the partition of nodes by compressed label may be represented as the number of nodes with each compressed label.
That is: &lt;strong&gt;&amp;ldquo;2 7s, 1 8, and 2 9s&amp;rdquo;&lt;/strong&gt;.
This is the canonical form of our graph.
Since both Graph 1 and Graph 2 have this same canonical form, we cannot rule out the possibility that they are isomorphic (they &lt;em&gt;are&lt;/em&gt; in fact isomorphic, but the algorithm doesn&amp;rsquo;t allow us to conclude this definitively.)&lt;/p&gt;
&lt;h3 id=&#34;implementation-considerations&#34;&gt;Implementation Considerations&lt;/h3&gt;
&lt;p&gt;One detail omitted is that the algorithm requires canonical forms of the multisets for comparison.
Placing the elements of the multiset in sorted order provides one way of doing this.&lt;/p&gt;
&lt;p&gt;Additionally, for the representation of the graph that the algorithm emits to be deterministic, we need to establish a convention for creating compressed labels.
One possible convention is to use increasing integers starting from 1, and to assign compressed labels to nodes in lexicographical order of their non-compressed labels.
Another possible convention is to use the hash of the multiset to create the compressed label.&lt;/p&gt;
&lt;p&gt;When comparing the partition of the nodes by compressed label from one iteration to the next to see whether to proceed to another iteration, it is sufficient to compare which nodes are in each partition.
If the partitions change, we proceed to the next iteration.
If no partition changes, the algorithm can terminate early.
When comparing the partitions of the nodes across graphs to see if graphs are isomorphic, we use the canonical form as described above in the algorithm.&lt;/p&gt;
&lt;h3 id=&#34;finding-the-correspondance-between-isomorphic-graphs&#34;&gt;Finding the Correspondance Between Isomorphic Graphs&lt;/h3&gt;
&lt;p&gt;The core idea of the Weisfeiler-Lehman isomorphism test is to find for each node in each graph a signature based on the neighborhood around the node.
These signatures can then be used to find the correspondance between nodes in the two graphs, which can be used to check for isomorphism.&lt;/p&gt;
&lt;p&gt;In the algorithm descibed above, the &amp;ldquo;compressed labels&amp;rdquo; serve as the signatures.
Since multiple nodes may have the same compressed label, there are multiple possible correspondances suggested by a Weisfeiler-Lehman labeling.
The Weisfeiler-Lehman isomorphism test itself does not provide a way of narrowing down the possible correspondances further.&lt;/p&gt;
&lt;h3 id=&#34;the-disappearance-of-boris-weisfeiler&#34;&gt;The Disappearance of Boris Weisfeiler&lt;/h3&gt;
&lt;p&gt;Boris Weisfeiler &amp;ndash; one of the two mathematicians responsible for the Weisfeiler-Lehman isomorphism test &amp;ndash; was a professor at Penn State University until he disappeared mysteriously in Chile in 1985.
His disappearance in the 80s is unsettling, and I would encourage you to visit 
&lt;a href=&#34;http://www.boris.weisfeiler.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;boris.weisfeiler.com&lt;/a&gt; to learn more about the search for Weisfeiler and the ongoing (2018) judicial proceedings surrounding his disappearance.&lt;/p&gt;
&lt;p&gt;Steven List produced an award winning short film called &lt;em&gt;The Colony&lt;/em&gt; based on the disappearance of Boris Weisfeiler, available 
&lt;a href=&#34;https://vimeo.com/60220290&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;on Vimeo here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After a brief internet search, I have been unable to find any information about Weisfeiler&amp;rsquo;s colleague and coauthor A. Lehman, not even a first name.
(Update Aug. 2020: A reader pointed me to 
&lt;a href=&#34;https://towardsdatascience.com/a-forgotten-story-of-soviet-ai-4af5daaf9cdf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;this recent story&lt;/a&gt; about &lt;em&gt;Andrey&lt;/em&gt; Leman. Thank you!)&lt;/p&gt;
&lt;h3 id=&#34;np-intermediate-problems&#34;&gt;NP-Intermediate Problems&lt;/h3&gt;
&lt;p&gt;As mentioned in the introduction, it is not known whether the graph isomorphism problem is in $\text{P}$ or $\text{NP-complete}$.
In fact, it is not even known whether graph isomorphism falls in either of these categories, or whether this problem is $\text{NP-intermediate}$.
The problem of factoring integers is another problem like this, where it is unknown whether the problem is in $\text{P}$, $\text{NP-complete}$, or is $\text{NP-intermediate}$.
Bear in mind that $\text{NP-intermediate}$ problems can only exist if $\text{P} \ne \text{NP}$.
So if anyone were to show conclusively that factoring or graph isomorphism were $\text{NP-intermediate}$ they would also have shown $\text{P} \ne \text{NP}$ and won the 
&lt;a href=&#34;https://www.claymath.org/millennium-problems/p-vs-np-problem&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Clay Institute P vs NP Millenium Problem&lt;/a&gt;&amp;rsquo;s million dollar prize.&lt;/p&gt;
&lt;h3 id=&#34;applications-of-graph-isomorphisms&#34;&gt;Applications of Graph Isomorphisms&lt;/h3&gt;
&lt;p&gt;Now that you&amp;rsquo;re familiar with the Weisfeiler-Lehman test for graph isomorphisms, what is the graph isomorphism problem good for?&lt;/p&gt;
&lt;p&gt;There are applications of the graph isomorphism problem in, for example, computational chemistry and in circuit design.&lt;/p&gt;
&lt;p&gt;In chemistry, it is common to represent a molecule as a graph, with the atoms in the molecule being the nodes of the graph and the bonds between molecules being the edges.
Here, the graph isomorphism problem can be used to determine if two chemical structures are in fact the same structure.
This is important for 
&lt;a href=&#34;https://en.wikipedia.org/wiki/Drug_discovery&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;drug discovery&lt;/a&gt;, where scientists are working to create molecular structures that will be useful in order to fight diseases.&lt;/p&gt;
&lt;p&gt;A circuit is commonly represented as a graph as well.
The components in the circuit form the nodes of the graph, and the connections between components form the edges.
The graph isomorphism problem is useful here for determining whether two circuits that are laid out different are in fact identical.&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;p&gt;Weisfeiler and Lehman first proposed this method in their paper &lt;em&gt;A reduction of a graph to a canonical form and an algebra arising during this reduction&lt;/em&gt; in 1968.
An English translation of this paper, originally published in Russian, is 
&lt;a href=&#34;https://www.iti.zcu.cz/wl2018/pdf/wl_paper_translation.pdf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;available here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;More recently the method is also described succinctly in 
&lt;a href=&#34;http://www.jmlr.org/papers/volume12/shervashidze11a/shervashidze11a.pdf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Weisfeiler-Lehman Graph Kernels (Shervashidze 2011)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now go forth and detect graph isomorphisms!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Writing a Mail Merge in Google Apps Script</title>
      <link>https://davidbieber.com/post/2018-10-21-apps-script-mail-merge/</link>
      <pubDate>Sun, 21 Oct 2018 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2018-10-21-apps-script-mail-merge/</guid>
      
      <description>&lt;p&gt;Google Apps Script is endlessly useful; in this post you will learn how to write a mail merge with Google Apps Script. It&amp;rsquo;s less than 30 lines of code! You can use this technique to send personalized emails individually to many recipients. In the second part of this post, you will learn how to schedule these emails to send in the future.&lt;/p&gt;
&lt;h2 id=&#34;part-1-writing-a-mail-merge&#34;&gt;Part 1: Writing a Mail Merge&lt;/h2&gt;
&lt;p&gt;A &lt;em&gt;mail merge&lt;/em&gt; is a program that sends an email to many recipients, automatically filling in the contents of the email according to a template. It&amp;rsquo;s like a form letter. Each recipient receives a personalized copy of the email.&lt;/p&gt;
&lt;p&gt;In this section we use Google Apps Script to create a mail merge that sends personalized &amp;ldquo;Happy Birthday&amp;rdquo; messages to a group of our friends. This will involve writing a template email, creating a spreadsheet, and writing a small amount of JavaScript code. With practice, you can perform tasks like this in well under an hour.&lt;/p&gt;
&lt;p&gt;First, we draft the email in Gmail. We&amp;rsquo;ll use angle brackets for the parts of the message we want to replace with data from our spreadsheet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-md&#34;&gt;Subject:
  Happiest Birthday!

Body:
  Dear &amp;lt;name&amp;gt;,

  Happy birthday! I think you, &amp;lt;name&amp;gt;, personally are awesome.
  You may be getting older, but you&#39;re still the coolest person I know.
  What are you now, &amp;lt;age&amp;gt;? Wow.

  Party on!
  David
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we create a Google Spreadsheet (
&lt;a href=&#34;https://sheets.google.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;sheets.google.com&lt;/a&gt;) with three columns like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-md&#34;&gt;Name  Email  Age  
...   ...    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(To add yourself to my spreadsheet and actually receive a happy birthday email on your alleged birthday, just 
&lt;a href=&#34;https://goo.gl/forms/qEy8f5CQ8itVVFoe2&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;fill out this form&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Inside your spreadsheet, choose &lt;em&gt;Tools &amp;gt; Script Editor&lt;/em&gt;. This creates a new Apps Script associated with your spreadsheet.&lt;/p&gt;
&lt;p&gt;Now it&amp;rsquo;s time to write some JavaScript. We&amp;rsquo;re going to write a function to perform the mail merge.
We&amp;rsquo;ll call it &lt;code&gt;performMailMerge&lt;/code&gt; and it won&amp;rsquo;t have any parameters, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;function performMailMerge() {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will use the built in SpreadsheetApp library to access the spreadsheet, the SpreadsheetDB library to perform basic data lookup operations on the spreadsheet, and the built in GmailApp library to look up drafts and send emails.&lt;/p&gt;
&lt;p&gt;SpreadsheetDB is a small library (a collection of useful functions) that allows us to quickly process rows in a spreadsheet, referencing columns by their header names. Choose one of the two options below for using SpreadsheetDB.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;To use the library SpreadsheetDB, choose Resources &amp;gt; Libraries &amp;gt; Add a library and enter &lt;code&gt;193p7IxBukPVkoxwMH8UDcdkYJiNCDWSylMA_htgliEPxjjqixhzKt_2g&lt;/code&gt;. This is the ID of the SpreadsheetDB library. Select the latest version from the version dropdown menu. It should say &amp;ldquo;SpreadsheetDB&amp;rdquo; if you&amp;rsquo;ve done it right. This will allow you to use functions from the SpreadsheetDB library in your project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To use SpreadsheetDB from source, create a new file in your project and name it &lt;code&gt;spreadsheet.gs&lt;/code&gt; (the name is not important). Copy and paste in the source of SpreadsheetDB from the 
&lt;a href=&#34;https://gist.github.com/dbieber/471a3103adb727d6985892338de00aea&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Gist here&lt;/a&gt;. If you choose to copy and paste the source into your project, then you must omit the prefix &amp;ldquo;SpreadsheetDB.&amp;rdquo; from your calls to the SpreadsheetDB functions in the following code. E.g. &lt;code&gt;SpreadsheetDB.getColumnIndexesFromSheet&lt;/code&gt; would become just &lt;code&gt;getColumnIndexesFromSheet&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that you&amp;rsquo;re set up with SpreadsheetDB, I&amp;rsquo;ll start by showing you the full program, then I&amp;rsquo;ll explain what each line does.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;function performMailMerge() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName(&amp;quot;Sheet 1&amp;quot;);
  var indexes = SpreadsheetDB.getColumnIndexesFromSheet(sheet);
  var subject = &amp;quot;Happiest Birthday!&amp;quot;;
  var draftMessage = getDraftMessageBySubject(subject);
  var templateBody = draftMessage.getPlainBody();
  SpreadsheetDB.forEachRow(sheet, function(row) {
    var name = row[indexes[&amp;quot;Name&amp;quot;]];
    var email = row[indexes[&amp;quot;Email&amp;quot;]];
    var age = row[indexes[&amp;quot;Age&amp;quot;]];
    
    var body = templateBody.replace(/&amp;lt;name&amp;gt;/g, name).replace(/&amp;lt;age&amp;gt;/g, age);
    GmailApp.sendEmail(email, subject, body);
  });
}

function getDraftMessageBySubject(subject) {
  var draftMessages = GmailApp.getDraftMessages();
  for (var i = 0; i &amp;lt; draftMessages.length; i++) {
    var draftMessage = draftMessages[i];
    if (draftMessage.getSubject() == subject) {
      return draftMessage;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The documentation for the built in libraries is very good. You can find the documentation for 
&lt;a href=&#34;https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;SpreadsheetApp&lt;/a&gt; here and for 
&lt;a href=&#34;https://developers.google.com/apps-script/reference/gmail/gmail-app&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;GmailApp&lt;/a&gt; here.&lt;/p&gt;
&lt;p&gt;Let’s look at the pieces of this function individually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName(&amp;quot;Sheet 1&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a 
&lt;a href=&#34;https://developers.google.com/apps-script/reference/spreadsheet/sheet&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Sheet&lt;/a&gt; object that we can use to access and edit the main sheet of our spreadsheet.&lt;/p&gt;
&lt;p&gt;The function call SpreadsheetDB.getColumnIndexesFromSheet(sheet) gives us back a mapping from column name to index. This is useful for accessing elements of a row by name later on. In our case, the result of getColumnIndexesFromSheet is the following mapping: &lt;code&gt;{Name: 0, Email: 1, Age: 2}&lt;/code&gt;. This means that Name is column index 0, Email is column index 1, and Age is column index 2. We’re counting columns starting with zero. Perfect!&lt;/p&gt;
&lt;p&gt;The call to &lt;code&gt;getDraftMessageBySubject&lt;/code&gt; gets a draft from our Gmail drafts that we’ll use later to send the emails to our friends.&lt;/p&gt;
&lt;p&gt;We use the &lt;code&gt;SpreadsheetDB.forEachRow&lt;/code&gt; function to perform an action for each row in the spreadsheet except the headers. We pass a function with no name (an &amp;ldquo;anonymous function&amp;rdquo;) to SpreadsheetDB, and SpreadsheetDB will call that function once per non-header row of our spreadsheet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;    var name = row[indexes[&amp;quot;Name&amp;quot;]];
    var email = row[indexes[&amp;quot;Email&amp;quot;]];
    var age = row[indexes[&amp;quot;Age&amp;quot;]];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The input to our anonymous function, row, is an array with the contents of the row currently being processed. From earlier, indexes[&amp;ldquo;Name&amp;rdquo;] is 0, indexes[&amp;ldquo;Email&amp;rdquo;] is 1, and indexes[&amp;ldquo;Age&amp;rdquo;] is 2. We can use this to get the name, email, and age of one of our friends from the spreadsheet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;var body = templateBody.replace(/&amp;lt;name&amp;gt;/g, name).replace(/&amp;lt;age&amp;gt;/g, age);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is where the textual replacement actually happens. &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; is replaced with your friend&amp;rsquo;s name. &lt;code&gt;&amp;lt;age&amp;gt;&lt;/code&gt; is replaced with your friend&amp;rsquo;s age. The &amp;lsquo;g&amp;rsquo; indicates that a &amp;ldquo;global&amp;rdquo; replacement should be performed, so every time the string &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; appears it will be replaced by the name of your friend from the spreadsheet. Without the &amp;lsquo;g&amp;rsquo;, only the first occurence of &lt;code&gt;&amp;lt;name&amp;gt;&lt;/code&gt; would be replaced.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;GmailApp.sendMail(email, subject, body);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last, this line sends the email!&lt;/p&gt;
&lt;p&gt;Since this is JavaScript, you can comment out lines by putting &lt;code&gt;//&lt;/code&gt; at the start of the line. Everything occuring after &lt;code&gt;//&lt;/code&gt; on the line will have no affect.&lt;/p&gt;
&lt;p&gt;I recommend commenting out the GmailApp.sendMail line before running this for the first time to so that you don&amp;rsquo;t accidently email people incorrectly.&lt;/p&gt;
&lt;p&gt;Add the line &lt;code&gt;Logger.log(&amp;quot;To: &amp;quot; + email); Logger.log(&amp;quot;Subj: &amp;quot; + subject); Logger.log(&amp;quot;Body: &amp;quot; + body);&lt;/code&gt; in place of the GmailApp.sendMail line until you&amp;rsquo;re convinced that the correct emails are going to be sent to the correct people. Only once you are sure it&amp;rsquo;s working as intended, uncomment the GmailApp.sendMail line so it actually sends the emails.&lt;/p&gt;
&lt;p&gt;To run the performMailMerge function, choose performMailMerge from the dropdown and click the run (right-arrow) button.
To see your logs (the inputs to the Logger.log function calls), choose &amp;ldquo;View &amp;gt; Logs&amp;rdquo; or press ctrl-Enter or cmd-Enter.&lt;/p&gt;
&lt;img src=&#34;https://66.media.tumblr.com/da2acd06f66fb2c9b6f6e68f046029de/tumblr_pgyx7hQlOL1rfccnto1_1280.png&#34; alt=&#34;Apps Script menu bar: Run button&#34; style=&#34;width:700px;&#34;&gt;
&lt;p&gt;That&amp;rsquo;s it. When you run performMailMerge, the emails will be sent with the values from the spreadsheet substituted for the &amp;ldquo;&lt;name&gt;&amp;rdquo; and &amp;ldquo;&lt;age&gt;&amp;rdquo; placeholders in the draft. Once you&amp;rsquo;ve got the hang of this, let&amp;rsquo;s add a few finishing touches before moving on to scheduling emails to be sent in the future.&lt;/p&gt;
&lt;h3 id=&#34;finishing-touches&#34;&gt;Finishing Touches&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Finishing touch 1:&lt;/em&gt; Adding a &amp;ldquo;Status&amp;rdquo; column to avoid emailing people twice.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-md&#34;&gt;Name  Email  Age  Status
...   ...    ...  ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;rsquo;s add some finishing touches. Add to your spreadsheet a new column &amp;ldquo;Status&amp;rdquo;. When an email sends successfully, we&amp;rsquo;ll set the status of the row to &amp;ldquo;Done&amp;rdquo;. If the status of a row is already &amp;ldquo;Done&amp;rdquo;, we&amp;rsquo;ll skip that row. The updated code is shown here:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;function performMailMerge() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName(&amp;quot;Sheet 1&amp;quot;);
  var indexes = SpreadsheetDB.getColumnIndexesFromSheet(sheet);
  var subject = &amp;quot;Happiest Birthday!&amp;quot;;
  var draftMessage = getDraftMessageBySubject(subject);
  var templateBody = draftMessage.getPlainBody();
  SpreadsheetDB.forEachRow(sheet, function(row) {
    var name = row[indexes[&amp;quot;Name&amp;quot;]];
    var email = row[indexes[&amp;quot;Email&amp;quot;]];
    var age = row[indexes[&amp;quot;Age&amp;quot;]];
    var status = row[indexes[&amp;quot;Status&amp;quot;]];
    
    if (status == &amp;quot;Done&amp;quot;) {
      return;
    }
    
    var body = templateBody.replace(/&amp;lt;name&amp;gt;/g, name).replace(/&amp;lt;age&amp;gt;/g, age);
    GmailApp.sendEmail(email, subject, body);
    
    row[indexes[&amp;quot;Status&amp;quot;]] = &amp;quot;Done&amp;quot;;
    return row;
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to make this change, we just check the value of &lt;code&gt;row[indexes[&amp;quot;Status&amp;quot;]]&lt;/code&gt;, and use a return statement to skip the row if the status is &amp;ldquo;Done&amp;rdquo;.
In order to update the row, we modify the &lt;code&gt;row&lt;/code&gt; array and return it and SpreadsheetDB takes care of updating the underlying spreadsheet.&lt;/p&gt;
&lt;p&gt;With this change in place, we can now add new recipients to our spreadsheet even after having run the performMailMerge function, and if we run performMailMerge again we won&amp;rsquo;t email anyone who has already received an email.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Finishing touch 2:&lt;/em&gt; Adding a menu item that triggers our function.&lt;/p&gt;
&lt;p&gt;When we created our Apps Script, it was automatically associated with our spreadsheet. If we add a function named &lt;code&gt;onOpen()&lt;/code&gt; to our Apps Script, it will automatically get run when the associated spreadsheet is opened. We can add a menu item to our spreadsheet by writing an onOpen function like this one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;function onOpen() {
  SpreadsheetApp.getUi()
  .createMenu(&#39;Mail Merge&#39;)
  .addItem(&#39;Send Emails&#39;, &#39;performMailMerge&#39;)
  .addToUi();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go ahead and refresh your spreadsheet. You should now see a menu called &amp;ldquo;Mail Merge&amp;rdquo; with a menu item &amp;ldquo;Send Emails&amp;rdquo;. Selecting &amp;ldquo;Send Emails&amp;rdquo; will cause your performMailMerge function to be run.&lt;/p&gt;
&lt;img src=&#34;https://66.media.tumblr.com/bba93c6c694c1ed6196e86baa1307648/tumblr_pgyxyq8CVX1rfccnto1_1280.png&#34; alt=&#34;Mail merge custom menu bar&#34; style=&#34;width:700px;&#34;&gt;
&lt;h2 id=&#34;part-2-scheduling-emails-for-the-future&#34;&gt;Part 2: Scheduling Emails for the Future&lt;/h2&gt;
&lt;p&gt;In Part 1 we wrote a mail merge function that emails our friends &amp;ldquo;personalized&amp;rdquo; Happy Birthday messages when we run performMailMerge. But why would we want to say happy birthday to all our friends at once? That&amp;rsquo;s silly, it makes much more sense to send them a happy birthday message &lt;em&gt;on their birthday&lt;/em&gt;. In this section, we&amp;rsquo;ll modify our mail merge to send emails only on the birthday of the recipient.&lt;/p&gt;
&lt;p&gt;To start, let&amp;rsquo;s add a new &amp;ldquo;Birthday&amp;rdquo; column to our spreadsheet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-md&#34;&gt;Name  Email  Age   Birthday  Status
...   ...    ...   ...       ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&amp;rsquo;re going to use the moment.js library for working with dates. Create a new file (&lt;em&gt;File &amp;gt; New &amp;gt; Script File&lt;/em&gt;) in your Apps Script project. Call it moment.gs, or anything else, and paste in the contents of the moment.js library. You can copy and paste moment.js from 
&lt;a href=&#34;https://momentjs.com/downloads/moment.js&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;here&lt;/a&gt;. Note: Be sure to use the non-minified version of moment.js; if you try using the minified version you will likely have trouble pasting the library into your Apps Script project.&lt;/p&gt;
&lt;p&gt;Now that we have moment.js, we can get the date of our friends birthday like this:
&lt;code&gt;moment(row[indexes[&amp;quot;Birthday&amp;quot;]])&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We can then write the function isTodaysDate like this. This will check that the month and day of the given date are today&amp;rsquo;s month and day, irrespective of what year the given date has.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;function isTodaysDate(date) {
  var now = moment();
  return now.isSame(date.year(now.year()), &amp;quot;day&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Putting it all together, we have this updated version of performMailMerge that will only send the email if the current date matches the birthday of the recipient.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;function isTodaysDate(date) {
  var now = moment();
  return now.isSame(date.year(now.year()), &amp;quot;day&amp;quot;);
}

function performMailMerge() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName(&amp;quot;Sheet 1&amp;quot;);
  var indexes = SpreadsheetDB.getColumnIndexesFromSheet(sheet);
  var subject = &amp;quot;Happiest Birthday!&amp;quot;;
  var draftMessage = getDraftMessageBySubject(subject);
  var templateBody = draftMessage.getPlainBody();
  SpreadsheetDB.forEachRow(sheet, function(row) {
    var name = row[indexes[&amp;quot;Name&amp;quot;]];
    var email = row[indexes[&amp;quot;Email&amp;quot;]];
    var age = row[indexes[&amp;quot;Age&amp;quot;]];
    var status = row[indexes[&amp;quot;Status&amp;quot;]];
    var birthday = moment(row[indexes[&amp;quot;Birthday&amp;quot;]]);
    
    if (status == &amp;quot;Done&amp;quot;) {
      return;
    }
    if (!isTodaysDate(birthday)) {
      return;
    }
    
    var body = templateBody.replace(/&amp;lt;name&amp;gt;/g, name).replace(/&amp;lt;age&amp;gt;/g, age);
    GmailApp.sendEmail(email, subject, body);
    
    row[indexes[&amp;quot;Status&amp;quot;]] = &amp;quot;Done&amp;quot;;
    return row;
  });
}

function getDraftMessageBySubject(subject) {
  var draftMessages = GmailApp.getDraftMessages();
  for (var i = 0; i &amp;lt; draftMessages.length; i++) {
    var draftMessage = draftMessages[i];
    if (draftMessage.getSubject() == subject) {
      return draftMessage;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the only step remaining is to make this performMailMerge function run every day. To do this, click the &amp;ldquo;Triggers&amp;rdquo; button in the toolbar (the clock icon). Create a new time-based trigger, and set it up so that it runs Daily (you get to choose when each day it will run). Configure it to run the performMailMerge each day at a time of your choice.&lt;/p&gt;
&lt;img src=&#34;https://66.media.tumblr.com/587d3371fc52f0ae50434a52337dbdfc/tumblr_pgyxa5usTC1rfccnto1_1280.png&#34; alt=&#34;Apps Script menu bar: Set a trigger&#34; style=&#34;width:700px;&#34;&gt;
&lt;p&gt;&lt;em&gt;Viola!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You now have a mail merge that&amp;rsquo;s running daily automatically, sending your friends Happy Birthday messages on their birthdays. Aren&amp;rsquo;t you a good friend?&lt;/p&gt;
&lt;p&gt;While sending birthday emails automatically is somewhat impersonal, the techniques used here are broadly applicable. With Apps Script, you can send yourself reminders, monitor your Calendar events, track your finances, and more. It&amp;rsquo;s a super useful and super friendly set of APIs, and this automated birthday emailer just scratches the surface of what Apps Script makes possible.&lt;/p&gt;
&lt;h2 id=&#34;wrap-up&#34;&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;ve now learned how to program a mail merge function that uses a spreadsheet as input. You can use a template to send multiple recipients personalized emails. You can schedule these emails to send in the future.&lt;/p&gt;
&lt;p&gt;You now have a great power, and with it comes great responsibility. Don&amp;rsquo;t use this for spam. Be kind and courteous to your fellow citizens of the internet. If you find this useful, do let me know what you use it for (I&amp;rsquo;m 
&lt;a href=&#34;https://twitter.com/Bieber&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;@Bieber&lt;/a&gt; on Twitter). I&amp;rsquo;m using Google Apps Script for a variety of things these days and I am always interested to hear how others are using the same tools.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Introducing Python Fire</title>
      <link>https://davidbieber.com/post/2017-03-06-introducing-python-fire/</link>
      <pubDate>Mon, 06 Mar 2017 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2017-03-06-introducing-python-fire/</guid>
      
      <description>&lt;p&gt;&lt;em&gt;Originally posted on the Google Open Source Blog&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Today we are pleased to announce the open-sourcing of 
&lt;a href=&#34;https://github.com/google/python-fire&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Python Fire&lt;/a&gt;. Python Fire generates command line interfaces (CLIs) from any Python code. Simply call the Fire function in any Python program to automatically turn that program into a CLI. The library is available from 
&lt;a href=&#34;https://pypi.org/project/fire/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;pypi&lt;/a&gt; via &lt;code&gt;pip install fire&lt;/code&gt;, and the source is 
&lt;a href=&#34;https://github.com/google/python-fire&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Python Fire will automatically turn your code into a CLI without you needing to do any additional work. You don’t have to define arguments, set up help information, or write a main function that defines how your code is run. Instead, you simply call the &lt;code&gt;Fire&lt;/code&gt; function from your main module, and Python Fire takes care of the rest. It uses inspection to turn whatever Python object you give it – whether it’s a class, an object, a dictionary, a function, or even a whole module – into a command line interface, complete with tab completion and documentation, and the CLI will stay up-to-date even as the code changes.&lt;/p&gt;
&lt;p&gt;To illustrate this, let’s look at a simple example.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;#!/usr/bin/env python
import fire

class Example(object):
  def hello(self, name=&#39;world&#39;):
    &amp;quot;&amp;quot;&amp;quot;Says hello to the specified name.&amp;quot;&amp;quot;&amp;quot;
    return &#39;Hello {name}!&#39;.format(name=name)

def main():
  fire.Fire(Example)

if __name__ == &#39;__main__&#39;:
  main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the Fire function is run, our command will be executed. Just by calling Fire, we can now use the Example class as if it were a command line utility.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;$ ./example.py hello
Hello world!
$ ./example.py hello David
Hello David!
$ ./example.py hello --name=Google
Hello Google!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course, you can continue to use this module like an ordinary Python library, enabling you to use the exact same code both from Bash and Python. If you’re writing a Python library, then you no longer need to update your main method or client when experimenting with it; instead you can simply run the piece of your library that you’re experimenting with from the command line. Even as the library changes, the command line tool stays up to date.&lt;/p&gt;
&lt;p&gt;At Google, engineers use Python Fire to generate command line tools from Python libraries. We have an image manipulation tool built by using Fire with the 
&lt;a href=&#34;https://en.wikipedia.org/wiki/Python_Imaging_Library&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Python Imaging Library&lt;/a&gt;, PIL. In 
&lt;a href=&#34;https://ai.google/research/teams/brain&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Google Brain&lt;/a&gt;, we use an experiment management tool built with Fire, allowing us to manage experiments equally well from Python or from Bash.&lt;/p&gt;
&lt;p&gt;Every Fire CLI comes with an interactive mode. Run the CLI with the &lt;code&gt;--interactive&lt;/code&gt; flag to launch an 
&lt;a href=&#34;https://ipython.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;IPython&lt;/a&gt; 
&lt;a href=&#34;https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;REPL&lt;/a&gt; with the result of your command, as well as other useful variables already defined and ready to use. Be sure to check out 
&lt;a href=&#34;https://github.com/google/python-fire#python-fire-&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Python Fire’s documentation&lt;/a&gt; for more on this and the other useful features Fire provides.&lt;/p&gt;
&lt;p&gt;Between Python Fire’s simplicity, generality, and power, we hope you find it a useful library for your own projects.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Ĉu Vi Estas Mia Patrino?</title>
      <link>https://davidbieber.com/post/2015-07-01-estas-mia-patrino/</link>
      <pubDate>Wed, 01 Jul 2015 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2015-07-01-estas-mia-patrino/</guid>
      
      <description>&lt;p&gt;&lt;em&gt;This is my translation of P.D. Eastman&amp;rsquo;s children&amp;rsquo;s book &amp;ldquo;Are You My Mother?&amp;rdquo; into Esperanto. Enjoy. -David&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;ĉu-vi-estas-mia-patrino&#34;&gt;Ĉu Vi Estas Mia Patrino?&lt;/h2&gt;
&lt;p&gt;By P.D. Eastman&lt;/p&gt;
&lt;p&gt;Patrina birdo sidis sur ŝiaj ovo. La ovo saltis.&lt;br&gt;
&amp;ldquo;Mi devas ion por mia beba birdo manĝi!&amp;rdquo; ŝi diris.&lt;br&gt;
Tiel for ŝi iris.&lt;br&gt;&lt;/p&gt;
&lt;p&gt;Ene la nesto, la ovo saltis.&lt;br&gt;
Ĝi saltis kaj saltis kaj saltis.&lt;br&gt;
Gis&amp;hellip;&lt;/p&gt;
&lt;p&gt;&amp;hellip;el venis beban birdon!&lt;br&gt;
&amp;ldquo;Kie estas mia patrino?&amp;rdquo; li diris.&lt;br&gt;
Li ne vidis ŝin ie ajn.&lt;br&gt;
&amp;ldquo;Mi iros serĉi ŝin,&amp;rdquo; li diris.&lt;/p&gt;
&lt;p&gt;El la nesto li iris.&lt;br&gt;
Malsupren, malsupren, malsupren!&lt;br&gt;
Plopi! (1)&lt;br&gt;
La beba birdo ne povis flugi.&lt;br&gt;
Sed li povis marŝi.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Nun mi iros kaj trovi mian patrinon,&amp;rdquo; li diris.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Ĉu vi estas mia patrino?&amp;rdquo; la beba birdo demandis katidon.&lt;br&gt;
La katido nur rigardis kaj rigardis.&lt;br&gt;
Ĝi ne diris nenion.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Ĉu vi estas mia patrino?&amp;rdquo; la beba birdo demandis kokinon.&lt;br&gt;
&amp;ldquo;Ne,&amp;rdquo; diris la kokino.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Ĉu vi estas mia patrino?&amp;rdquo; la beba birdo demandis hundon.&lt;br&gt;
&amp;ldquo;Mi ne estas via patrino. Mi estas hundo,&amp;rdquo; diris la hundo.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Ĉu vi estas mia patrino?&amp;rdquo; la beba birdo demandis bovinon.&lt;br&gt;
&amp;ldquo;Kiel mi povus esti via patrino?&amp;rdquo; diris la bovino. &amp;ldquo;Mi estas bovino.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;La beba birdo haltis por pensi.&lt;br&gt;
La katido kaj la kokino ne estis lia patrino.&lt;br&gt;
La hundo kaj la bovino ne estis lia patrino.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Mi havas patrinon,&amp;rdquo; diris la beba birdo.&lt;br&gt;
&amp;ldquo;Mi scias ke mi trovos ŝin. Mi. Trovos. Ŝin.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Ĝuste tiam la beba birdo vidis grandan aferon.&lt;br&gt;
&amp;ldquo;Vi estas mia patrino!&amp;rdquo; li diris.&lt;/p&gt;
&lt;p&gt;La granda afero diris &amp;ldquo;SNUFANTE!&amp;rdquo; (2)&lt;br&gt;
&amp;ldquo;Ho ne!&amp;rdquo; diris la beba birdo. Vi estas terura snufanto!&lt;/p&gt;
&lt;p&gt;La Snufanto levis la beban birdon supren, supren, supren.&lt;/p&gt;
&lt;p&gt;Poste io okazis.&lt;br&gt;
La Snufanto metis la beban birdon reen en la arbo.&lt;br&gt;
La beba birdo estis hejmo.&lt;/p&gt;
&lt;p&gt;Ĝuste tiam la patrina birdo revenis.&lt;br&gt;
&amp;ldquo;Mi scias kiu vi estas,&amp;rdquo; diris la beba birdo.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Vi ne estas katido aŭ kokino aŭ hundo.&lt;br&gt;
Vi ne estas bovino aŭ Snufanto!&lt;br&gt;
Vi estas birdo, kaj vi estas mia patrino!&amp;rdquo;&lt;/p&gt;
&lt;h2 id=&#34;footnotes&#34;&gt;Footnotes&lt;/h2&gt;
&lt;p&gt;1: If a bird falls out of a tree in English, it hits the ground and goes &amp;ldquo;plop!&amp;rdquo; But if a birdo falls out of a tree in Esperanto, what sound does it make?&lt;/p&gt;
&lt;p&gt;2: The word for snort in Esperanto is snufante. It&amp;rsquo;s not exactly onomatopoeic, but imagining an enormous John Deere tractor going &amp;ldquo;SNUFANTE&amp;rdquo; before helping out a baby birdo is actually strangely heartwarming.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Running Firefox in the Cloud</title>
      <link>https://davidbieber.com/post/2014-11-08-firefox-in-the-cloud/</link>
      <pubDate>Sat, 08 Nov 2014 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2014-11-08-firefox-in-the-cloud/</guid>
      
      <description>&lt;p&gt;I found running Firefox in the cloud difficult, but with enough digging around I was able to get it working. Now I&amp;rsquo;m able to drive a headless version of Firefox with Selenium using Python. This lets me do things like have my 
&lt;a href=&#34;https://github.com/dbieber/ScrabbleBot&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Scrabble Bot&lt;/a&gt; play Words with Friends autonomously on Facebook.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What am I going to show you how to do?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to explain how you can install Firefox and Selenium on a virtual machine in the cloud. I&amp;rsquo;m going to show this using a Debian instance on 
&lt;a href=&#34;https://cloud.google.com/compute/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Google Compute Engine&lt;/a&gt;, but you could also do this using a virtual machine from Amazon EC2.&lt;/p&gt;
&lt;p&gt;Before continuing you should create a virtual machine running Debian on Google Compute Engine (GCE). 
&lt;a href=&#34;https://cloud.google.com/compute/docs/quickstart&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;You can learn how to spin up a VM here&lt;/a&gt;. Make sure that you can SSH into your machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Installing Firefox&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re actually going to install Iceweasel. To the best of my knowledge, the only difference between Iceweasel and Firefox is the branding. To do this, we&amp;rsquo;re going to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update the sources list that apt-get uses to find packages.
Edit the file &lt;code&gt;/etc/apt/sources.list&lt;/code&gt; and add the line &lt;code&gt;deb http://mozilla.debian.net/ wheezy-backports iceweasel-release&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Update apt-get by running the command &lt;code&gt;sudo apt-get update&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install Iceweasel by running the command &lt;code&gt;sudo apt-get install -t wheezy-backports iceweasel&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I figured this out by searching for &amp;ldquo;iceweasel sources list&amp;rdquo;, which brought me to 
&lt;a href=&#34;http://mozilla.debian.net/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;http://mozilla.debian.net/&lt;/a&gt;. If you&amp;rsquo;re looking to install a different version of Iceweasel/Firefox or have a different version of Debian, check there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Running Firefox&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We can now run the browser we installed with the command &lt;code&gt;firefox&lt;/code&gt; or &lt;code&gt;iceweasel&lt;/code&gt;, but it will shut down because there is no display. To run the browser without a display (that is, to run it &amp;ldquo;headlessly&amp;rdquo;), we&amp;rsquo;re going to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the display server Xvfb (X virtual framebuffer) with the command &lt;code&gt;sudo apt-get install xvfb&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Start the display server with the command &lt;code&gt;sudo Xvfb :10&lt;/code&gt;. Here, :10 is the server number we chose for the virtual display we&amp;rsquo;re creating.&lt;/li&gt;
&lt;li&gt;Now run &lt;code&gt;export DISPLAY=:10&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You should now be able to run &lt;code&gt;firefox&lt;/code&gt; or &lt;code&gt;iceweasel&lt;/code&gt; successfully.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;d like to see what&amp;rsquo;s on your virtual display, 
&lt;a href=&#34;http://en.wikipedia.org/wiki/Xvfb#Usage_examples&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Wikipedia provides an example of how to take a screenshot of your virtual display&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Installing Selenium&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We can now run a headless version of Firefox. Since there&amp;rsquo;s no user interface to click around in, the next logical step is to learn how to drive the browser. For this, we&amp;rsquo;re going to set up Selenium in Python. When dealing with packages in Python, I recommend using pip inside a virtualenv with virtualenvwrapper. There are instructions for setting up these packages below.&lt;/p&gt;
&lt;p&gt;Once you have virtualenvwrapper installed and you&amp;rsquo;re safely working in a virtual environment, just issue the command &lt;code&gt;pip install selenium&lt;/code&gt; to install selenium. If you&amp;rsquo;re not in a virtualenv, you may need to issue the command with sudo.&lt;/p&gt;
&lt;p&gt;Try running this Python script to see if it&amp;rsquo;s all working. It should take you to Google Images, search for cute kittens, and then save a screenshot to the file &amp;lsquo;adorable.png&amp;rsquo;.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/dbieber/1c4a7bdca8f9820b3612.js&#34;&gt;&lt;/script&gt;
&lt;p&gt;&lt;strong&gt;Installing virtualenvwrapper&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As promised above, here is a section about virtualenvwrapper.&lt;/p&gt;
&lt;p&gt;Managing Python packages effectively is difficult, and I can&amp;rsquo;t do the topic justice here. I recommend using virtualenvwrapper to help, and 
&lt;a href=&#34;http://virtualenvwrapper.readthedocs.org/en/latest/install.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;you can read more about how to do this here&lt;/a&gt;. For completeness, I&amp;rsquo;ll briefly run through the steps of getting set up with pip, virtualenv, and virtualenvwrapper.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pip&lt;/code&gt; is Python&amp;rsquo;s package manager. Once you have pip set up, you can run &lt;code&gt;pip install package-name&lt;/code&gt; to install the package called package-name.&lt;/p&gt;
&lt;p&gt;To set up pip, run the following commands:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;curl https://bootstrap.pypa.io/get-pip.py &amp;gt; get-pip.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo python get-pip.py&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;virtualenv&lt;/code&gt; is a tool that let&amp;rsquo;s you have different versions of Python and Python packages for different projects that you work on. Combined with &lt;code&gt;virtualenvwrapper&lt;/code&gt;, it will help you avoid headaches of keeping track of where your various Python versions live and where they can find their packages on your machine.&lt;/p&gt;
&lt;p&gt;To install virtualenv, run the command &lt;code&gt;sudo pip install virtualenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To install virtualenvwrapper, run the command &lt;code&gt;sudo pip install virtualenvwrapper&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Add the following three lines to your .bashrc or another startup script:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;export WORKON_HOME=$HOME/.virtualenvs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;export PROJECT_HOME=$HOME/Devel&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;source /usr/local/bin/virtualenvwrapper.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;re now ready to go. 
&lt;a href=&#34;http://virtualenvwrapper.readthedocs.org/en/latest/command_ref.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Learn more about how to use virtualenvwrapper effectively here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Last thoughts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I hope you find this helpful. You can now install Firefox on Google Compute Engine, run it using a virtual display, and drive it using Selenium with Python. If something isn&amp;rsquo;t working, or if something is working really well and has you super excited, I&amp;rsquo;m happy to talk it through with you. Email&amp;rsquo;s the best way to contact me. Go forth and automate your life!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Why a Code@Night is not a Hackathon</title>
      <link>https://davidbieber.com/post/2013-09-06-code-at-night-vs-hackathon/</link>
      <pubDate>Fri, 06 Sep 2013 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2013-09-06-code-at-night-vs-hackathon/</guid>
      
      <description>&lt;p&gt;I&amp;rsquo;ve written in the past about 
&lt;a href=&#34;http://blog.davidbieber.com/post/31367374857/code-at-night&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;what a Code@Night is&lt;/a&gt;. And they&amp;rsquo;re wonderful. One of my favorite topics, really. People come to these things and they just code all night. Great experience. Tons of fun. Fruit. PacMan. Yadda, yadda, yadda.&lt;/p&gt;
&lt;p&gt;I tell people this all the time, and a pretty common response is &amp;ldquo;so, it&amp;rsquo;s like a hackathon?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So I think for a bit. There&amp;rsquo;s coding. There&amp;rsquo;s minimal sleeping. There are tech talks. There are pizzas, snacks, and refreshments. Fruit is hard to come by at a hackathon, but it can be there too. So I concede, &amp;ldquo;yeah, it&amp;rsquo;s a bit like a hackathon.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;But I know we&amp;rsquo;re wrong. Code@Night doesn&amp;rsquo;t &lt;em&gt;feel&lt;/em&gt; like a hackathon at all.&lt;/p&gt;
&lt;p&gt;Here, I made you an analogy. It&amp;rsquo;s not a good one, but you can read it anyway. &amp;ldquo;A hackathon is to a Code@Night as a track race is to a stroll through a park.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Or for you symbolically minded folks&amp;hellip;&lt;/p&gt;
&lt;center&gt;Hackathon : Code@Night :: Track race : Evening stroll&lt;/center&gt;
&lt;p&gt;How can they feel so different when the only core difference is that we&amp;rsquo;ve removed the competitive aspect?&lt;/p&gt;
&lt;p&gt;Well, in some ways Code@Night is a lot like a hackathon, but in others it&amp;rsquo;s not at all. It&amp;rsquo;s a hackathon minus the competition, plus the assortment of non-hackathon things that happen at Code@Nights. e.g. Pacman, the occasional contest, the tight-knit community, lots of fruit, a tech talk smack in the middle. Also, Code@Night runs for 12 hours in the worst case (if people stay for breakfast, we&amp;rsquo;ll feed them), but more realistically only goes from 9 pm to 3-4 am. A hackathon usually lasts 24 hours or more.&lt;/p&gt;
&lt;p&gt;It sounds like these are small differences from a hackathon, but it&amp;rsquo;s really a completely different atmosphere. There&amp;rsquo;s no start and end time for your project. You can work on the same thing Code@Night to Code@Night if you want. And people don&amp;rsquo;t cluster into teams the way they do at hackathons. You also end up getting large group discussions going, which you rarely get at hackathons because people are rushed to work on their 24 hour product. No one&amp;rsquo;s worrying about prizes or judges. You don&amp;rsquo;t have to demo anything to feel accomplished (but we love demos! they&amp;rsquo;re sure encouraged!) You can build what you want to build, not the thing you think will win over an audience.&lt;/p&gt;
&lt;p&gt;Code@Night isn&amp;rsquo;t about starting. It isn&amp;rsquo;t about finishing. It&amp;rsquo;s about doing. It&amp;rsquo;s about learning. It&amp;rsquo;s about being a part of a community. By removing the artificial competitive aspects and time constraints that hackathons impose on coding, we&amp;rsquo;re left with a really rewarding experience. People step back from their coding in ways they don&amp;rsquo;t at hackathons. They teach about Python, about Hadoop. They 
&lt;a href=&#34;https://www.facebook.com/photo.php?fbid=10151919494461654&amp;amp;set=a.407128786653.201033.728066653&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;sit in boxes&lt;/a&gt; (
&lt;a href=&#34;https://www.facebook.com/media/set/?set=a.10200583989428067.206255.1409114395&amp;amp;type=1&amp;amp;l=844baf75c2&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;or put boxes on their heads&lt;/a&gt;). They share their projects. They debate bacterial growth characteristics on the surface of 
&lt;a href=&#34;http://www.butterfinger.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;crisp orange-colored, peanut butter-flavored flakiness coated in compound chocolate&lt;/a&gt;. They talk about technology. And sometimes 
&lt;a href=&#34;http://princetonacm.com/2013/poem/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;they write poetry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, the next time someone asks if a Code@Night is like a hackathon, I&amp;rsquo;ll be ready with my &amp;ldquo;kinda, but not really, actually no&amp;rdquo; answer. And I&amp;rsquo;ll be ready to say how they&amp;rsquo;re different, and how I actually prefer the Code@Night. But hey, I&amp;rsquo;m biased, I organize the things.&lt;/p&gt;
&lt;p&gt;
&lt;a href=&#34;http://whenisthenextcodeatnight.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Next Code@Night?&lt;/a&gt; It&amp;rsquo;s 
&lt;a href=&#34;https://www.facebook.com/events/176020992583310/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Sept 13, 9 pm. Sherrerd, Third Floor&lt;/a&gt;. Open to all Princetonites. Hope to see you there!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>MathCounts Coach Q&#43;A</title>
      <link>https://davidbieber.com/post/2013-08-16-mathcounts-coach-q-and-a/</link>
      <pubDate>Fri, 16 Aug 2013 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2013-08-16-mathcounts-coach-q-and-a/</guid>
      
      <description>&lt;p&gt;Last week I did a Q+A for the 
&lt;a href=&#34;http://life.khanacademy.org/post/58251244282/im-a-belieber-a-khanversation-with-sw-dev-intern&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Life at KA&lt;/a&gt; blog. The last time I did an education-centric online Q+A was more than three years earlier. Let&amp;rsquo;s take a look.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally posted at 
&lt;a href=&#34;https://www.mathcounts.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;mathcounts.org&lt;/a&gt; in March 2010.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How long have you been a MATHCOUNTS coach?&lt;/strong&gt;&lt;br&gt;
This is my second year coaching the Van Antwerp MATHCOUNTS team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is your favorite part about being a MATHCOUNTS coach?&lt;/strong&gt;&lt;br&gt;
It&amp;rsquo;s great to see my students having a good time with math. I love seeing their expressions when an idea becomes clear to them (an Aha! moment, as Martin Gardner would have it) or when they solve a challenging problem. I also like seeing their reactions to the problems I write. They seem to enjoy when I put their names into our practice problems. On some occasions, I&amp;rsquo;ll have one idea in mind when I write a problem, but I&amp;rsquo;ll make a typo that makes the question quite absurd. That&amp;rsquo;s always fun to laugh about.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do you prepare your students for MATHCOUNTS competitions?&lt;/strong&gt;&lt;br&gt;
Leading up to the Chapter Competition we met once a week after school. These meetings lasted 90 minutes, with a snack break (often called &amp;ldquo;Scoff Time!&amp;rdquo; in excited voices by the students) in the middle. After the team qualified for the State Competition we added a second shorter (45 minute) practice day to the week. On some days we practice with old MATHCOUNTS rounds. Other times we&amp;rsquo;ve played mathematical games such as variations of 
&lt;a href=&#34;http://www.math.usu.edu/~schneit/CTIS/GreedyPig/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Greedy Pig&lt;/a&gt; and mathified versions of Jeopardy, Tic-Tac-Toe and Hangman. We&amp;rsquo;ve also experimented with a variety of practice formats including relays, topic-specific stations, and something akin to 
&lt;a href=&#34;https://www.hmmt.co/archive/problems/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;HMMT&amp;rsquo;s Guts Round&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before the Chapter Competition we also hosted a MATHCOUNTS scrimmage at 
&lt;a href=&#34;http://www.union.edu/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Union College&lt;/a&gt; for six local MATHCOUNTS teams, so that the students got a chance to practice in a competition setting before the official contest date. At the scrimmage we also ran an unofficial Countdown Round for all the students interested in participating, which I had a lot of fun emceeing, and I think the students enjoyed as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What interesting techniques have you used in the classroom or after school with your students to get them excited about math?&lt;/strong&gt;&lt;br&gt;
When our final practice before the Chapter Competition was cancelled due to snow, I put together a set of problems for the team to solve before the competition. Each member of the team received one question by email. What was interesting about these problems was that each one depended on the answer to another. This encouraged the team members to work together in the days before the competition to find a set of solutions that satisfied all of the problems simultaneously.&lt;/p&gt;
&lt;p&gt;Another technique is to encourage the students to share their methods of solving MATHCOUNTS problems with the rest of the team. By sharing their ideas, they frequently spot errors in their own work. Teaching is also a great way to become better versed in the material (that&amp;rsquo;s one of the reasons I do this!), so its a great learning tool for everyone in the room. Often it is easier for someone on the team to explain a difficult concept to another student than for a coach to do this because the students have a better perspective on what parts of the idea are challenging or confusing. It helps a bit that I&amp;rsquo;m only a few years older than my team!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why do you think math is so important for middle school students?&lt;/strong&gt;&lt;br&gt;
To start, it can be fun. The problem solving skills that stem from practicing math are useful in all areas of life. Activities like MATHCOUNTS also help you to think faster and be more creative. And to fully appreciate the world around you or the daily news, it&amp;rsquo;s really important to be comfortable around numbers.&lt;/p&gt;
&lt;p&gt;Speaking as a high school student, I can also tell you that having a solid foundation in mathematics can be a big stress reducer in school, so it&amp;rsquo;s great to get interested in math early!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do you have any advice for Mathletes® hoping to advance in the Competition series?&lt;/strong&gt;&lt;br&gt;
Practice, ask questions, and develop a good sleep habit.&lt;/p&gt;
&lt;p&gt;Attending MATHCOUNTS practices is a great place to start, but there are so many ways to go beyond that in becoming a better problem solver. If you&amp;rsquo;re clever with a search engine, you&amp;rsquo;ll find there are tons of resources available including 
&lt;a href=&#34;https://www.mathcounts.org/programs/competition-series/past-competitions&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;problems on the MATHCOUNTS website&lt;/a&gt;, these 
&lt;a href=&#34;http://mathcounts.saab.org/mc.cgi&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;MATHCOUNTS drills&lt;/a&gt;, 
&lt;a href=&#34;http://www.unidata.ucar.edu/staff/russ/mathcounts/diaz.html&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Mr. Diaz&amp;rsquo;s MATHCOUNTS Bible&lt;/a&gt;, and 
&lt;a href=&#34;http://mathweb.scranton.edu/monks/courses/ProblemSolving/MathCountsPlaybook.pdf&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Coach Monks’s MATHCOUNTS Playbook&lt;/a&gt;. Get together with members of your team to practice outside of school. Use problems you find online or, better yet, write your own, and don&amp;rsquo;t forget to ask your parents to provide you with a snack.&lt;/p&gt;
&lt;p&gt;If ever there are problems you don&amp;rsquo;t understand (and this should happen very often), ask about them. At practice it can be difficult to get to all of them because there is so much to do in so little time, but don&amp;rsquo;t limit your questions to your practices. Talk to or email your coach or teachers or siblings or parents or friends or friends&#39; parents. Anyone might have the knowledge or insight to answer/explore your questions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What advice would you give to new coaches just starting a MATHCOUNTS program?&lt;/strong&gt;&lt;br&gt;
Have fun with it, and your students will have fun too (at least in principle, anyhow). Chances are your students will know things and ask things you don&amp;rsquo;t know or can&amp;rsquo;t answer, and that&amp;rsquo;s good. Being a coach is an opportunity to learn as well as to teach.&lt;/p&gt;
&lt;p&gt;The most important advice I can leave you with (keep in mind I have more experience as a student than as a coach) to get your MATHCOUNTS team off the ground, though, is to provide snacks. I recommend grapes, as they&amp;rsquo;re healthy, tasty, easy to distribute, and avoid the majority of allergy problems.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Life at KA: A Khanversation with David Bieber</title>
      <link>https://davidbieber.com/post/2013-08-14-khanversation/</link>
      <pubDate>Wed, 14 Aug 2013 01:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2013-08-14-khanversation/</guid>
      
      <description>&lt;p&gt;
&lt;a href=&#34;http://life.khanacademy.org/post/58251244282/im-a-belieber-a-khanversation-with-sw-dev-intern&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;em&gt;Originally posted at life.khanacademy.org&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll dive straight into the questions for our latest Khan Academy Khanversation, this time featuring rising Princeton senior David Bieber.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;David, let&amp;rsquo;s begin with the question everyone is dying to hear you answer. Are you related to Justin Bieber?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s funny you should ask that. People tell me all the time &amp;ldquo;sorry about your last name&amp;rdquo; or &amp;ldquo;I hope you&amp;rsquo;re not related to &lt;em&gt;that singer&lt;/em&gt;&amp;rdquo; or &amp;ldquo;can you get me free tickets to the Bieber concert?&amp;rdquo; And because of that, I&amp;rsquo;ve started feeling a real connection to the guy. I root for his success. I feel down when people trash his music. I think we could become genuinely good friends if we weren&amp;rsquo;t both vying for the top spot on Google for the search term &amp;ldquo;Bieber.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;And no, I don&amp;rsquo;t listen to his music (much).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;david-bieber-no-beber.png&#34; alt=&#34;No Bieber!&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, Bieber, how did you come to work at Khan Academy?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sounds like life story time.&lt;/p&gt;
&lt;p&gt;I had some pretty incredible teachers growing up. Lots of them. And not just the grown-up kind &amp;ndash; the peer kind too. Throughout middle and high school I was involved in this fantastic math community, the 
&lt;a href=&#34;http://albanyareamathcircle.blogspot.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Albany Area Math Circle&lt;/a&gt;. We practiced loads of competition math, and most of what I learned, I learned from other students. Some a few years older than me, some a few years younger.&lt;/p&gt;
&lt;p&gt;Here I got involved in coaching MathCounts, and I quickly realized that I love teaching. I ran a math camp for middle school students one summer and I had a blast doing it. Whatever I was going to do with my life, it was going to involve teaching in some capacity.&lt;/p&gt;
&lt;p&gt;Fast forward a couple years, and I&amp;rsquo;m sitting in a dining hall in some castle in Princeton. I&amp;rsquo;m talking with some friends about startup ideas &amp;ndash; I have a lot of friends interested in entrepreneurship &amp;ndash; and I have a brilliant idea for a company. (I love getting lost in the excitement of a good idea.) &amp;ldquo;We&amp;rsquo;ll take classes, and we&amp;rsquo;ll put them online. Just Princeton classes to start &amp;ndash; it&amp;rsquo;ll be like MIT&amp;rsquo;s OpenCourseWare &amp;ndash; but we&amp;rsquo;ll make it scalable. And any student with their professor&amp;rsquo;s permission will be able to really easily get their classes&#39; lectures online.&amp;rdquo; Sounds a bit like Coursera. Or YouTube. From there on out, I gave a lot of thought to teaching at scale.&lt;/p&gt;
&lt;p&gt;Which brings us to Khan Academy. I&amp;rsquo;m interested in programming and technology and education, and I&amp;rsquo;m all for making the world a better place. What better way than the KA way to leverage my interests to facilitate teaching at scale?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wait&amp;hellip; You said you were sitting in a castle?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yeah, half the buildings on campus are castles. But that&amp;rsquo;s not the best thing about Princeton. The best thing about Princeton is Code@Night (though I&amp;rsquo;m biased &amp;ndash; I organize them). Every two to three weeks there&amp;rsquo;s a Code@Night where people gather in this beautiful non-castle building called Sherrerd to code at night. Usually a student or professor or an engineer from a sponsor company will give a tech talk or a demo. People will code all night, they&amp;rsquo;ll play PacMan and eat pizza. Sometimes they&amp;rsquo;ll write poetry. It&amp;rsquo;s really an incredible experience. There are even whiteboards all along the walls making it an ideal environment for puzzles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do you have a puzzle you could share with us?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sure. Here&amp;rsquo;s one that was going around the KA office recently.&lt;/p&gt;
&lt;p&gt;There are one hundred prisoners in a prison, and the warden is a bit too much of a puzzle enthusiast. She finds an empty jail cell, and into it puts one hundred boxes. Under each box she puts the name of one of the prisoners, so every prisoner&amp;rsquo;s name appears under precisely one box.&lt;/p&gt;
&lt;p&gt;Then she tells the prisoners this:&lt;/p&gt;
&lt;p&gt;Each one of you will be led individually into the jail cell with one hundred boxes. Under one of these boxes is your name. You will each be allowed to look under 50 boxes, but if even one of you doesn&amp;rsquo;t find your own name, all one hundred of you will be locked away forever. That said, if all of you find your own name then you will be freed immediately.&lt;/p&gt;
&lt;p&gt;The prisoners are terrified, but also excited. They love puzzles too. Help them form a strategy to maximize the chance that they&amp;rsquo;ll be granted their freedom.&lt;/p&gt;
&lt;p&gt;Some caveats: Once the prisoners start going into the cell with the boxes, no communication between the prisoners is allowed. And the prisoners cannot make any modifications to the room with the boxes either &amp;ndash; no leaving boxes turned over, no writing on the walls, and no taking boxes out of the room or moving them, even the one with your name under it.&lt;/p&gt;
&lt;p&gt;Good luck.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sounds tough. What&amp;rsquo;s the answer?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pshhh, don&amp;rsquo;t give up yet. It&amp;rsquo;s a really good puzzle. Who&amp;rsquo;s asking these questions, anyway?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That&amp;rsquo;d be me. David Bieber.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So what you&amp;rsquo;re telling me is that I&amp;rsquo;m involved in a virtual interview&amp;hellip; with myself?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yeah.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And to think I thought the Turing test was hard&amp;hellip; why are you writing all this anyway?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Well, it&amp;rsquo;s good to take a break from coding every now and again. Plus, I really like having people read the things I write &amp;ndash; I found this out after starting my blog a year ago (
&lt;a href=&#34;http://blog.davidbieber.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;take a look!&lt;/a&gt;) &amp;ndash; it makes the writing process a lot more fun. It also gives you all a better feel for life at Khan Academy, and that transparency is really valuable. We’re real people who make KA run! Seeing all the awesome stuff on this blog is part of why I came to work here. But most of all, the writing really is fun! Especially the Bieber bit &amp;ndash; I had a good time writing that. And no, we’re not related.&lt;/strong&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>ssh Passwordlessly</title>
      <link>https://davidbieber.com/post/2013-08-14-ssh-passwordlessly/</link>
      <pubDate>Wed, 14 Aug 2013 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2013-08-14-ssh-passwordlessly/</guid>
      
      <description>&lt;p&gt;You&amp;rsquo;re used to ssh-ing into machine &lt;strong&gt;B&lt;/strong&gt; from machine &lt;strong&gt;A&lt;/strong&gt; by typing:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssh username@B.com&lt;/code&gt;&lt;br&gt;
&lt;code&gt;password&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now you wish to ssh from machine &lt;strong&gt;A&lt;/strong&gt; into machine &lt;strong&gt;B&lt;/strong&gt; without having to enter your password. Just follow these steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a public/private &lt;a href=&#34;http://en.wikipedia.org/wiki/RSA_(algorithm)&#34;&gt;RSA&lt;/a&gt; key pair by issuing the unix command &lt;code&gt;ssh-keygen&lt;/code&gt;. Do not enter a passphrase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Put the private key on machine &lt;strong&gt;A&lt;/strong&gt; (the machine you wish to ssh from). Usually the best place to put it is in &lt;code&gt;~/.ssh&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On machine &lt;strong&gt;B&lt;/strong&gt;, create the file &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;. Add the public key as a line to this new file. You can do this with the command &lt;code&gt;cat public_key &amp;gt;&amp;gt; authorized_keys&lt;/code&gt; or by copy/pasting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On machine &lt;strong&gt;A&lt;/strong&gt;, create the file &lt;code&gt;~/.ssh/config&lt;/code&gt;. Add the following to this file.
&lt;code&gt;Host anyname&lt;/code&gt;&lt;br&gt;
&lt;code&gt;HostName B.com&lt;/code&gt;&lt;br&gt;
&lt;code&gt;User username&lt;/code&gt;&lt;br&gt;
&lt;code&gt;IdentityFile ~/.ssh/private_key&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;rsquo;s it. You can now passwordlessly ssh into machine &lt;strong&gt;B&lt;/strong&gt; from machine &lt;strong&gt;A&lt;/strong&gt; just by typing:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssh anyname&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And as a bonus, you can now &lt;a href=&#34;http://en.wikipedia.org/wiki/Secure_copy&#34;&gt;&lt;code&gt;scp&lt;/code&gt;&lt;/a&gt; to and from machine &lt;strong&gt;B&lt;/strong&gt; passwordlessly as well.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s 
&lt;a href=&#34;http://nerderati.com/2011/03/simplify-your-life-with-an-ssh-config-file/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;loads&lt;/a&gt; 
&lt;a href=&#34;http://linux.die.net/man/5/ssh_config&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;more&lt;/a&gt; that you can do with an ssh config file. So have a Google, and see what you can see.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I&amp;rsquo;m posting this online because it&amp;rsquo;s something I wish I had known sooner. Hopefully you can benefit from it.&lt;/p&gt;
&lt;p&gt;This post isn&amp;rsquo;t the whole story though! Using an ssh key without a passphrase (a la step 1) is dangerous; if anyone gains access to your private key then they&amp;rsquo;ll be able to ssh into machine B as you. The solution is to (back in step 1) create a passphrase for your private key, and then &lt;a href=&#34;http://en.wikipedia.org/wiki/Ssh-agent&#34;&gt;ssh-agent&lt;/a&gt; will remember your decrypted private key so that you don&amp;rsquo;t need to enter your password too often.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Teach at Scale</title>
      <link>https://davidbieber.com/post/2013-08-07-teach-at-scale/</link>
      <pubDate>Wed, 07 Aug 2013 01:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2013-08-07-teach-at-scale/</guid>
      
      <description>&lt;p&gt;The best way to learn is to teach. It&amp;rsquo;s also, most of the time, the most fun way to learn. And as an added bonus, the person you&amp;rsquo;re teaching learns too.&lt;/p&gt;
&lt;p&gt;I strongly believe that you, as someone who has found themselves reading strange blogs on the internet, have a lot to offer the world in the way of teaching. So I want you to teach at scale. Yes, I&amp;rsquo;m looking at you Mom and Dad, former Facebook and Twitter interns, my fellow interns here at Khan Academy. I&amp;rsquo;m looking at you, my Princeton and Nisky friends, and my former MathCounts students. Yes, even you, HackerNews readers and Google searchers and friends of friends. I want you to give teaching at scale a try.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s hard. Don&amp;rsquo;t mislead yourself into thinking this is an easy task that I&amp;rsquo;ve laid out for you. It takes courage and confidence. When you teach at scale, you make yourself vulnerable. You have to be ready for people to catch your mistakes and challenge your teachings. People will out-think you, and you should be ready to embrace that.&lt;/p&gt;
&lt;p&gt;What, then, should you teach? I offer two suggestions. 1) Teach what you want to learn, and 2) teach what you know best. Sometimes the best teachers are not those with 10 years of teaching experience in a subject, but rather the people who remember what it&amp;rsquo;s like not to know. Teach something while the Aha! moment that helped you understand it is still fresh in your mind. The second suggestion, teach what you know best, sounds obvious, but the reason behind it has some subtlety. Don&amp;rsquo;t teach what you know best because you&amp;rsquo;ll probably avoid mistakes on that topic &amp;ndash; do it because it&amp;rsquo;s something you can speak passionately about. If your teachings get a student genuinely excited about a topic, that is worth so much more than having them quickly master it.&lt;/p&gt;
&lt;p&gt;So how should you get started? The simplest way is to blog. You can set up a blog and be started writing in minutes by signing up at 
&lt;a href=&#34;http://www.tumblr.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;tumblr.com&lt;/a&gt;. Once you&amp;rsquo;ve written something educational, share it with your friends and with online communities. Related to technology? Post it on HackerNews.&lt;/p&gt;
&lt;p&gt;DIY method two of teaching at scale is to make YouTube videos. I gave this a try making videos teaching Item Response Theory and Bloom Filters. Here&amp;rsquo;s my setup.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;featured.jpg&#34; alt=&#34;Personal ping-pong recording studio at the Khan Academy office&#34;&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a piece of cardboard with a hole cut out of it for a camera to look through, mounted above some paper. I used my iPhone as the camera.&lt;/p&gt;
&lt;p&gt;Here are the resulting Bloom Filter videos, mistakes and stuttering and blurriness and all. I&amp;rsquo;m pretty embarrassed by them, and I&amp;rsquo;m hoping future videos I produce will come out better.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/playlist?list=PLTFg4cPjhl-2nrkIR1z_MSTcxNeyPk4PO&#34;&gt;https://www.youtube.com/playlist?list=PLTFg4cPjhl-2nrkIR1z_MSTcxNeyPk4PO&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to continue to make videos as I learn awesome things over the years, and I hope you do the same.&lt;/p&gt;
&lt;p&gt;How you teach at scale is up to you. Blog. Make videos. Post code. Start a math circle.&lt;/p&gt;
&lt;p&gt;Be creative. The most effective teachers-at-scale are not always those who leverage technology to reach a wide audience. They are those who teach passionately, and in doing so infect others with a love for learning and teaching.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Automate Your Life: Sending Emails</title>
      <link>https://davidbieber.com/post/2013-03-13-automate-your-life-sending-emails/</link>
      <pubDate>Wed, 13 Mar 2013 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2013-03-13-automate-your-life-sending-emails/</guid>
      
      <description>&lt;p&gt;&lt;em&gt;In a hurry? Scroll down and skip to the code. That&amp;rsquo;s the good part anyway.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of the beauties of programming is the elimination of redundant aspects of life that you find yourself doing over and over again. When you find yourself calling home every weekend, logging hours at your job daily, or getting your hair cut every month, it&amp;rsquo;s time to learn to program. Write a short script to do these repetitive behaviors for you. Just kidding. Don&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;These are joke examples &amp;ndash; I strongly advise against automating discussions with your mother &amp;ndash; but they aren&amp;rsquo;t so farfetched. Automating processes - shopping, shipping, server maintenance - save countless hours and dollars. Here, we&amp;rsquo;ll do a thought experiment: imagine a team of people running Amazon.com without automation. As a crude estimate, let&amp;rsquo;s suppose they sell a mere billion books each year. Let&amp;rsquo;s imagine they only handle 1% of all Internet traffic. And let&amp;rsquo;s pretend they only store 1 trillion objects in their S3 storage system. Then with their team of about ninety thousand, each Amazon employee would have to be selling and shipping 30 books a day on top of processing and transmitting over 100 gigabytes of Internet traffic daily, all while handling requests for a hundred thousand S3 objects that they&amp;rsquo;ve had to memorize. Maybe &lt;a href=&#34;http://en.wikipedia.org/wiki/Jeff_Bezos&#34;&gt;Jeff Bezos&lt;/a&gt; could pull it off. But you&amp;rsquo;re not Jeff Bezos, so I&amp;rsquo;d say it&amp;rsquo;s worth your time to learn to automate.&lt;/p&gt;
&lt;p&gt;Even education is being automated these days. Will the next century&amp;rsquo;s robotic overloads prove to be better professors than the ones we grew up with? To which you may reply: &amp;ldquo;Oh gee whiz! I sure hope we get better robotic overloads in the future! The ones I grew up with were just miserable professors.&amp;rdquo; But that&amp;rsquo;s not what I meant. I&amp;rsquo;m asking whether &lt;a href=&#34;http://khanacademy.org/&#34;&gt;Khan Academies&lt;/a&gt; and &lt;a href=&#34;http://duolingo.com/&#34;&gt;Duolingos&lt;/a&gt; will replace our real life Ms. Frizzles and Mrs. Gorfs. A brief excursion with Duolingo makes me suspect it will be a major part of all future language educations. This seems counterintuitive. Languages are inherently about communication. The best way to learn a language should necessarily involve communicating (and the very best way will probably forever remain immersion, but often that&amp;rsquo;s not an option). Conversation and human interaction are so fundamental to language learning that it seems almost absurd that a computer could be a good language teacher, at least until we have &lt;a href=&#34;http://en.wikipedia.org/wiki/Strong_AI&#34;&gt;strong AI&lt;/a&gt;. But the computer manages to do one thing extraordinarily well that isn&amp;rsquo;t scalable in a traditional classroom; the computer focuses entirely on me. So, yes, it lacks creativity and the personal touch of a great teacher. It doesn&amp;rsquo;t tell funny stories or crack foreign language jokes. It doesn&amp;rsquo;t show cute movies and it can&amp;rsquo;t relate to me at all. Yet despite all these shortcomings, it&amp;rsquo;s remarkably good at helping me learn the basics of a foreign language, because it can focus on me, going at my pace and not having to devote its efforts to maintaining the attention of 30 other students.&lt;/p&gt;
&lt;p&gt;Automation has clearly been taking over everywhere for years. You have to join it. &lt;a href=&#34;http://www.youtube.com/watch?v=nKIu9yen5nc&#34;&gt;It&amp;rsquo;s a real life superpower.&lt;/a&gt;  So let&amp;rsquo;s learn to automate something basic. We&amp;rsquo;ll go with software: a Python script to send emails.&lt;/p&gt;
&lt;p&gt;I present: the code. You can run it as is. Save it as a file and run it with Python, and it should just work out of the box. Go ahead, try it.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/dbieber/5146518.js&#34;&gt;&lt;/script&gt;
&lt;p&gt;Awesome. You just sent me an email. What good is this code? Tweak it a bit and you can use it for personal alerts &amp;ndash; systematically check when you&amp;rsquo;re low on milk and email yourself a reminder &amp;ndash; have your computer email you daily with upcoming birthdays; you can even have it buy the gifts. You can email yourself automatically when a course opens up on your registrar&amp;rsquo;s website. We&amp;rsquo;ll talk about auto-enrolling another time. Or you can set up a script to send different versions of emails to different people, like those spam emails that pretend to be personal by including your first name.&lt;/p&gt;
&lt;p&gt;With great power comes great responsibility. Don&amp;rsquo;t send spam.&lt;/p&gt;
&lt;p&gt;With a new Python library in your toolbox, I hope you save yourself thousands of hours. Until next time, try to stay &lt;a href=&#34;http://en.wikipedia.org/wiki/Don&#39;t_repeat_yourself&#34;&gt;dry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=5371430&#34;&gt;Discuss on Hacker News.&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Lorenz Attractors and Chess</title>
      <link>https://davidbieber.com/post/2013-03-13-lorenz-attractor-pawns/</link>
      <pubDate>Wed, 13 Mar 2013 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2013-03-13-lorenz-attractor-pawns/</guid>
      
      <description>&lt;p&gt;Every pawn is an inverted Lorenz attractor. Not every Lorenz attractor is an inverted pawn.&lt;/p&gt;
&lt;img src=&#34;lorenz-pawns.png&#34;/&gt;</description>
    </item>
    
    <item>
      <title>Blokus Life Lessons</title>
      <link>https://davidbieber.com/post/2013-01-31-blokus-life-lessons/</link>
      <pubDate>Thu, 31 Jan 2013 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2013-01-31-blokus-life-lessons/</guid>
      
      <description>&lt;p&gt;&lt;i&gt;In this post, clicking the bold word &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Life&lt;/span&gt;&lt;/strong&gt; will replace it with the bold word &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Blokus&lt;/span&gt;&lt;/strong&gt;.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Life&lt;/span&gt;&lt;/strong&gt; is simple. It gets more complex over time, but nothing insurmountable. In the beginning, your path is pretty much laid out. You have some control over what directions you go, and these are important decisions. They&amp;rsquo;ll shape your future. They&amp;rsquo;ll change what opportunities you have down the road. But as important as they are, there are relatively few wrong decisions. Sure, you can screw up at any point, including early on. And sometimes the damage is irreparable. As my dad always says, &amp;ldquo;avoid the big mistakes.&amp;rdquo; Aim high, and lay a foundation for yourself to grow. Act with a focus on making your situation the best it can be, always with an eye toward the future. It&amp;rsquo;s the most important thing you can focus on with so much still ahead of you, with so much still unseen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Life&lt;/span&gt;&lt;/strong&gt; is all about people. It&amp;rsquo;s about interactions. It&amp;rsquo;s important to realize this, that what you decide to make of a situation has a wide impact, bigger than yourself. When you change the world, you change it for everyone. And when you think to yourself, &amp;ldquo;what should I do now?&amp;rdquo; you also have to think about how others will respond. How will things be different down the road? How will things be different just a few minutes from now? The person sitting across from you: she has her own mind, her own motives. Your intentions and hers- sometimes they align nicely, fitting together perfectly and making both of you better off. But often they don&amp;rsquo;t mesh and they get in each other&amp;rsquo;s way. One must always be wary of this.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s interesting that children don&amp;rsquo;t understand this at first. People don&amp;rsquo;t develop a theory of mind until the age of three or four. Until then, they don&amp;rsquo;t grasp that people have different experiences, different thoughts, and different aspirations. Some people with autism spectrum disorders experience a deficit in their theory of mind that lasts beyond childhood, making &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt; supremely difficult. It&amp;rsquo;s very unfortunate. It takes time before children begin grasp the complexities of &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt;, and at first they cannot puzzle out how others think and how others will behave. Over time, one develops a more analytic mind and the puzzles of behavior become simultaneously more understood and more mysterious.&lt;/p&gt;
&lt;p&gt;Take any individual piece of &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt; and you&amp;rsquo;ll find it mostly transparent. It&amp;rsquo;s a beautiful, colorful thing, completely understandable, devoid of mystery and yet full of wonder. With thoughtful reasoning, you can pretty much figure out how it fits into the bigger picture. Yet the bigger picture itself, like a stained glass window, is so full of intricacies that even with a remarkable attention to detail it seems there is always more to explore. There is always, it seems, another possibility to consider.&lt;/p&gt;
&lt;p&gt;When the pieces of &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt; stack up against each other, the transparency fades. It becomes hard to distinguish one thing from another. Important decisions are more difficult to make, yet remain even more consequential than any before. Sometimes you may look out and see something that really excites you: a thought so fun and so novel it drives you to action; you get the idea that everything fits together so nicely. But there&amp;rsquo;s always something missing. That one piece of the puzzle that you can&amp;rsquo;t find, and nothing will fill this void. With everything looking so good, like everything was where it was meant to be, the only way to go from here is down. Your choices are no longer clear, and you can&amp;rsquo;t tell one from another. What to do? What to do?&lt;/p&gt;
&lt;p&gt;There are tough decisions with trade-offs and you simply cannot please everyone. Know what situation you&amp;rsquo;re in, your strengths and your weaknesses. Lay out your possible decisions clearly in front of you. Organize. Evaluate. You mustn&amp;rsquo;t rush into big decisions.&lt;/p&gt;
&lt;p&gt;When you&amp;rsquo;re in a tough spot, as you surely will be, do not despair. You may be tempted to look back on your past with regret. Hindsight is 20/20. You couldn&amp;rsquo;t have known about that poor decision, that wasted opportunity. It&amp;rsquo;s a sunk expenditure and you must make the best of your current situation. Sometimes you&amp;rsquo;ll feel like you don&amp;rsquo;t fit in, and that&amp;rsquo;s just part of &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt; progresses, you will have more decisions to make with higher impact. You take on more responsibility with your actions and you have more meaningful interactions with other people. Setting goals, and having them mesh with the goals of others is important. My dad and I agreed to each do 180 days of push-ups and sit-ups this year. Whether or not this sort of goal will lead to success in &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt; is questionable, but having an objective like this is useful. Goals must not be unrealistic, and you should be aware that sometimes it may feel like the world is conspiring against you, and perhaps it is. But often it&amp;rsquo;s not, and when things don&amp;rsquo;t go as planned there is no one to blame but yourself. I&amp;rsquo;ve set out to become a better storyteller this year. Stories, to me, have a lot of value, and they certainly make &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt; more interesting.&lt;/p&gt;
&lt;p&gt;It is often said a goal should be objective and measurable. For a goal such as storytelling, though, that seems absurd. Progress here can only be measured by a good tale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Life&lt;/span&gt;&lt;/strong&gt;, we all know, eventually comes to an end. And as it does so, think about what you want to leave behind. Where do you want your possessions to go? You can&amp;rsquo;t take them with you. So be charitable, and get rid of as much as you can afford. As much as the world is ready for. Think of those who will follow you, and ask yourself how will they feel about your end. Did you leave them in a good situation? Perhaps you set them up to finish soon, just as you did.&lt;/p&gt;
&lt;p&gt;Think of everything. It&amp;rsquo;s all about strategy. &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Life&lt;/span&gt;&lt;/strong&gt; is a game.&lt;/p&gt;
&lt;p&gt;Sorry, have I been writing &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;life&lt;/span&gt;&lt;/strong&gt;? I meant &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Blokus&lt;/span&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;a href=&#34;http://en.wikipedia.org/wiki/Blokus&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Blokus&lt;/span&gt;&lt;/strong&gt; is a game.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the gist. You can click the bold words to go between &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Blokus&lt;/span&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;span class=&#34;blokus&#34;&gt;Life&lt;/span&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/dbieber/4693513.js&#34;&gt;&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>Get your new Django based web app live with AppFog in minutes</title>
      <link>https://davidbieber.com/post/2012-12-25-django-appfog/</link>
      <pubDate>Tue, 25 Dec 2012 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2012-12-25-django-appfog/</guid>
      
      <description>&lt;p&gt;You&amp;rsquo;ll have your Django web app up and running live on the internet with AppFog in a matter of minutes.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what you have to do.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;a href=&#34;#setting-up-appfog&#34;&gt;Start a project on AppFog.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href=&#34;#cloning-this-repository&#34;&gt;Clone the django-appfog-helloworld repository.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href=&#34;#pushing-to-appfog&#34;&gt;Push to AppFog.&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The repository is available at 
&lt;a href=&#34;https://github.com/dbieber/django-appfog-helloworld&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;https://github.com/dbieber/django-appfog-helloworld&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what comes with this repo.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a Django project that works out of the box (just download it and &lt;code&gt;python manage.py runserver&lt;/code&gt;. If you have Django installed, it&amp;rsquo;ll work!). It&amp;rsquo;s already setup to serve static files. It&amp;rsquo;s all ready to use MySQL or sqlite3. It ships with Bootstrap and jQuery. Django&amp;rsquo;s admin app is enabled and ready to go. It has templates for 404 and 500 errors so you can quickly see how to get error handling working. And it has one app, named &lt;code&gt;app_name&lt;/code&gt;, that demonstrates the core functionality of Django: models, views, templating, and forms, by collecting email address of early users to your site. It&amp;rsquo;s a rudimentary version of LaunchRock.&lt;/p&gt;
&lt;p&gt;My hope is that it will work well both as start code and as reference code for how to get Django working quickly.&lt;/p&gt;
&lt;h5 id=&#34;this-template-is-great-for-quick-and-dirty-websites-its-perfect-for-a-hackathon-or-for-prototyping-an-idea-with-friends-and-its-a-great-template-for-getting-started-with-djangohttpswwwdjangoprojectcom-bootstraphttptwittergithubcombootstrap-and-jqueryhttpjquerycom-but-its-not-production-quality-in-two-key-respects-security-and-scalability-keep-that-in-mind-as-you-start-building&#34;&gt;This template is great for quick and dirty websites. It&amp;rsquo;s perfect for a hackathon or for prototyping an idea with friends. And it&amp;rsquo;s a great template for getting started with 
&lt;a href=&#34;https://www.djangoproject.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Django&lt;/a&gt;, 
&lt;a href=&#34;http://twitter.github.com/bootstrap/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Bootstrap&lt;/a&gt;, and 
&lt;a href=&#34;http://jquery.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;jQuery&lt;/a&gt;. But it&amp;rsquo;s not production quality in two key respects, security and scalability. Keep that in mind as you start building.&lt;/h5&gt;
&lt;p&gt;Now, let&amp;rsquo;s dive deeper into getting your Django app running.&lt;/p&gt;
&lt;h4 id=&#34;setting-up-appfog&#34;&gt;Setting up AppFog&lt;/h4&gt;
&lt;p&gt;Register for an account at 
&lt;a href=&#34;http://appfog.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;AppFog.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Create a new &amp;ldquo;Django Python&amp;rdquo; app. Host it on AWS. And give it a name. I called mine &amp;ldquo;helloworld-django&amp;rdquo;. Let&amp;rsquo;s say you call yours &lt;code&gt;name_of_app&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll want to set up the &lt;code&gt;af&lt;/code&gt; command line utility to make deploying your site easier later. On Mac or Unix this is as simple as &lt;code&gt;gem install af&lt;/code&gt;. Windows instructions are simple too, and are available in the &amp;ldquo;Update Source Code&amp;rdquo; tab of your app on AppFog.&lt;/p&gt;
&lt;p&gt;(Optional) Make sure you have a MySQL database service provisioned. To do this, log in to AppFog, choose your app, and choose the &amp;ldquo;Services&amp;rdquo; tab. You can either bind an existing MySQL database service to your app or create a new one. If you don&amp;rsquo;t have a MySQL database provisioned, the app we deploy in a moment will use sqlite3 as a fallback and should work just fine.&lt;/p&gt;
&lt;h4 id=&#34;cloning-this-repository&#34;&gt;Cloning this repository&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;git clone https://github.com/dbieber/django-appfog-helloworld.git&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The web app is already all ready to be pushed to AppFog.&lt;/p&gt;
&lt;p&gt;Before you go and push this website live, there are a few things you should do, all marked with &lt;code&gt;TODO&lt;/code&gt; in the source code you just downloaded. These are the most important:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set your &lt;code&gt;SECRET_KEY&lt;/code&gt; in &lt;code&gt;settings.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set up the &lt;code&gt;ADMINS&lt;/code&gt; variable in &lt;code&gt;settings.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Set default admin account credentials in &lt;code&gt;views.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Always be aware of whether &lt;code&gt;DEBUG = True&lt;/code&gt; or &lt;code&gt;DEBUG = False&lt;/code&gt; in &lt;code&gt;settings.py&lt;/code&gt;. This changes how errors are handled and you definitely want &lt;code&gt;DEBUG&lt;/code&gt; set to false when your site is public.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now you&amp;rsquo;re ready to go live!&lt;/p&gt;
&lt;h4 id=&#34;pushing-to-appfog&#34;&gt;Pushing to AppFog&lt;/h4&gt;
&lt;p&gt;Be sure to either run &lt;code&gt;python manage.py collectstatic&lt;/code&gt; or place all your static files your &lt;code&gt;static&lt;/code&gt; directory before pushing.&lt;/p&gt;
&lt;p&gt;Log in to AppFog with the command &lt;code&gt;af login&lt;/code&gt;. (Find instructions for setting up the command line tool in the &amp;ldquo;Update Source Code&amp;rdquo; tab of your app on 
&lt;a href=&#34;http://appfog.com&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;AppFog.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From inside the &lt;code&gt;django-appfog-helloworld&lt;/code&gt; directory (which, by the way, you are free to rename) run &lt;code&gt;af update name_of_app&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it. Your site is live! You&amp;rsquo;re already collecting email address of beta-testers. Huzzah!&lt;/p&gt;
&lt;h3 id=&#34;testing-locally&#34;&gt;Testing Locally&lt;/h3&gt;
&lt;p&gt;To test locally, you&amp;rsquo;ll need to 
&lt;a href=&#34;https://docs.djangoproject.com/en/dev/topics/install/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;install Django&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To test your app locally, navigate to the &lt;code&gt;django-appfog-helloworld&lt;/code&gt; directory containing &lt;code&gt;manage.py&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;python manage.py runserver&lt;/code&gt; runs your app locally. View it at &lt;code&gt;localhost:8000&lt;/code&gt;. &lt;br&gt;
&lt;code&gt;python manage.py syncdb&lt;/code&gt; sets up your local database by default in a file called &lt;code&gt;dev.db&lt;/code&gt;. &lt;br&gt;
&lt;code&gt;python manage.py collectstatic&lt;/code&gt; copies all your static files into your static files directory. &lt;br&gt;
&lt;code&gt;python manage.py help&lt;/code&gt; for more.&lt;/p&gt;
&lt;p&gt;Django is a Python based web framework with excellent documentation and tutorials. To learn more about Django, visit 
&lt;a href=&#34;https://www.djangoproject.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;www.djangoproject.com&lt;/a&gt;. Happy hacking!&lt;/p&gt;
&lt;p&gt;
&lt;a href=&#34;http://news.ycombinator.com/item?id=4965184&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Comments? Head over to HackerNews.&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>CRM&#39;s: Not Just for Sales Folk</title>
      <link>https://davidbieber.com/post/2012-11-29-crms-not-just-for-sales-folk/</link>
      <pubDate>Thu, 29 Nov 2012 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2012-11-29-crms-not-just-for-sales-folk/</guid>
      
      <description>&lt;p&gt;In this blog post I recommend &lt;a href=&#34;http://streak.com&#34;&gt;Streak&lt;/a&gt;, a customer relationship management (CRM) tool that lives inside Gmail.&lt;/p&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://news.ycombinator.com/item?id=4847437&#34;&gt;Discuss on Hacker News.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What do you use email for? I’m curious, so feel free to &lt;a href=&#34;https://docs.google.com/spreadsheet/viewform?formkey=dFU1WjZ4eDh2TktzSjBlel83cDFwM3c6MQ&#34;&gt;let me know&lt;/a&gt; before reading further (&lt;a href=&#34;https://docs.google.com/spreadsheet/viewform?formkey=dFU1WjZ4eDh2TktzSjBlel83cDFwM3c6MQ&#34;&gt;Google form&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Personally, there are a just a handful of use cases that make up the vast majority of my email usage. Let’s list them.&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Organizing talks and events for ACM and Splash!&lt;/li&gt;
&lt;li&gt;Applications for summer internships&lt;/li&gt;
&lt;li&gt;Read-only-ish mail (Mailing lists announcing talks and events, class related announcements, discussions I minimally partake in, spam, etc.)&lt;/li&gt;
&lt;li&gt;Talking with friends&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;For about three months now, I’ve been thinking that email is woefully insufficient for managing my communication needs, particularly use cases 1 and 2. When I’m organizing a &lt;a href=&#34;http://david-bieber.tumblr.com/post/31367374857/code-at-night&#34;&gt;Code@Night&lt;/a&gt; I’ll often have 3 or more email threads related to the event. In one I’ll be discussing logistics with a company sponsor. In another I’ll be coordinating with other ACM officers. In still another I’ll announce the event to groups around campus. Often even more form when people start replying about how awesome it is that there will be fruit at the event or asking how they can contribute to making the world a better place.&lt;/p&gt;
&lt;p&gt;I thought about how I could improve email so that managing all these communications could be better. Better ways of grouping conversations, by person or by event, would be nice. Having an indicator of what still had to be done and who I still needed to contact would be nice. I realized I could do all of this with Gmail already, combining labels and searches in clever ways. But that would be clunky, and so I was pleasantly surprised when I stumbled upon Streak.&lt;/p&gt;
&lt;p&gt;Streak seems to be targeting sales and recruiting folks, and I think that’s the wrong audience for them. Their product was clearly designed with the sales-recruiting person in mind, but I think it’s equally well suited for the hacker, and not just for tracking bugs. To demonstrate this, I’ll write a bit about what the product does and how I’m using it.&lt;/p&gt;
&lt;p&gt;I have two use cases for Streak so far, corresponding to email uses 1 and 2 in the list above. I use Streak for organizing events and for managing my applications for summer internships. I don’t know that either of these are use cases intended by the folks over at Streak. For organizing events (primarily tech talks and Code@Nights), I have a “Pipeline” in Streak called ACM Events. Into a Pipeline go boxes, one box per event. And a box contains all the emails, documents, comments, and other information pertaining to the event. It’s simple to add an email thread to a box, so I’m always able to keep everything relevant to a particular event in one easily accessible place in the Gmail web client. As the event goes from being just an idea to a complete plan to being successfully executed, its box moves through the pipeline. In this way I can quickly get at all events in a particular part of the planning process or event lifecycle, and I have a convenient archive of all past events.&lt;/p&gt;
&lt;p&gt;For managing my summer internship applications, Streak has also proved useful. I have a box for each company to which I’m applying. And I put all the emails and documents for that company’s application into the appropriate box. On the company’s end, I’m probably being moved through a hiring pipeline in a similar CRM tool, and now the company is moving through my CRM’s pipeline in parallel.&lt;/p&gt;
&lt;p&gt;The box and pipeline model took me a bit of tinkering with to really grok. It’s a paradigm shift that takes some getting used to. The email primitive is no longer the conversation, but instead is the thing you are trying to accomplish through the conversation. Say I need to reply to a Facebook recruiter about planning a tech talk here at Princeton. Now rather than using Gmail search to look for a particular email thread I vaguely recall having (perhaps by guessing at words that were in the thread or by scrolling through my email to the date I remember the email having been sent), I simply go to the box I created for the event, and all of the relevant emails are right there. This sounds a lot like labels, but has the distinct advantage that boxes are much more light weight than labels. Once you have more than a dozen labels or so, they become clunky and start cluttering up the Gmail navigation bar.&lt;/p&gt;
&lt;p&gt;Don’t just take my word for it. &lt;a href=&#34;http://streak.com&#34;&gt;Give Streak a try.&lt;/a&gt;</description>
    </item>
    
    <item>
      <title>Thoughts on Education</title>
      <link>https://davidbieber.com/post/2012-10-30-thoughts-on-education/</link>
      <pubDate>Tue, 30 Oct 2012 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2012-10-30-thoughts-on-education/</guid>
      
      <description>&lt;p&gt;That concludes day one of the 
&lt;a href=&#34;https://svtigertrek.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;TigerTrek&lt;/a&gt;. I flew out of Princeton yesterday with a group of 20 to spend the week visiting technology companies, startups and venture capitalists in Silicon Valley. We&amp;rsquo;re going company to company hearing founder&amp;rsquo;s stories, discussing entrepreneurship, and as we do this I am forced to introspect. What do I want to do with my life? How can I make a difference?&lt;/p&gt;
&lt;p&gt;My passion is education. I was fortunate enough to have some amazing mentors growing up, and to learn in a lot of diverse ways most people never have a chance to experience. I tried out gifted programs, nerd camps, one-on-one classes, online self paced learning, the works. I got involved in math circles, I took summer classes, I really was blessed to have every educational opportunity one could hope for. This, and the diversity of amazing teachers I&amp;rsquo;ve had has gotten me thinking about how people learn best, and how I learn best. Complementing this interest well, I&amp;rsquo;ve found I really love teaching- particularly math and computer science to younger students. It&amp;rsquo;s always a joy helping someone to tackle the Bessie the Cow problem or 
&lt;a href=&#34;http://en.wikipedia.org/wiki/Nim&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;the game of Nim&lt;/a&gt; for the very first time.&lt;/p&gt;
&lt;p&gt;Today a lot of young companies bring together my interests of education, computer science and technology. Websites like 
&lt;a href=&#34;https://www.coursera.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Coursera&lt;/a&gt;, 
&lt;a href=&#34;https://www.edx.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;edX&lt;/a&gt;, and 
&lt;a href=&#34;http://www.udacity.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Udacity&lt;/a&gt; virtualize the university experience, and Khan Academy joins them in making quality education accessible everywhere. Startups like 
&lt;a href=&#34;https://www.crunchbase.com/organization/lore&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Lore&lt;/a&gt; and 
&lt;a href=&#34;https://piazza.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Piazza&lt;/a&gt; foster discussions around classes, allowing the learning experience to extend more seamlessly beyond the walls of the classroom.&lt;/p&gt;
&lt;p&gt;Today we trekked over to the new office of Khan Academy where our group got to sit down and discuss education with Sal Khan, and then we were off to Piazza&amp;rsquo;s office where we did Q+A with the question-answer site&amp;rsquo;s founder, Pooja Sankar.&lt;/p&gt;
&lt;p&gt;Sal Khan is a really inspirational person. His collection of tutorials - which now spans a wide range of topics including mathematics and organic chemistry and art history - It all began with Sal tutoring his cousins over the phone back in 2004. Hearing him tell his founding story - how he went from tutoring one cousin, to many, to posting videos on YouTube and being well received all over the world. how he came to tutor the Gates children through his videos, and how after discussions with venture capitalists in the valley Sal made the choice to incorporate Khan Academy as a non-profit - Hearing this story from Sal Khan really drove home how empowering technology is and how tech is shaping the future of education. One smart man with a gift for explaining things clearly now teaches millions of students, an unprecedented feat since most lecture halls barely seat one hundred.&lt;/p&gt;
&lt;p&gt;With the resources Khan Academy is producing, the purpose of the classroom is shifting. Lecture time in classes is becoming less and less valuable, and some classes have adopted a flipped classroom model. Students watch the Khan Academy videos at home, and then come to class with an understanding of the material. Students then use the class time to try out problems that would previously have been homework, now in a setting where they can get immediate help from their peers and their teacher. The flipped classroom model has been highly successful so far.
One of Khan Academy&amp;rsquo;s ambitions is to, when someone comes to the site to learn about multiplication, be able to first offer an education on addition or any other prerequisites of the subject. Or after learning about one topic, they&amp;rsquo;d love to provide an education on the next logical thing to study. They&amp;rsquo;ve done a fine job of this with their knowledge map of mathematics. Each skill from 1-digit addition to calculus and linear algebra is connected with its prerequisites. Where this problem gets interesting, and where I&amp;rsquo;m excited to see what Khan Academy does in this space, is with more specialized subject matter. It will be interesting to see how this can be presented when the relationships between material stops being [understanding X is required to know Y] and starts being [X is related to Y] or [X is a good tutorial to follow for people who struggled with Y].&lt;/p&gt;
&lt;p&gt;The data that Khan Academy is receiving about its millions of users puts the organization in a fantastic place for improving education. By the sheer number of users, there will always be groups of students studying the same material. In order to achieve this with a traditional education, students were forced to learn at the same rate as their twenty classmates. The class would drag slower students forward, and hold the brightest back. With Khan Academy students can devour mathematics at whatever rate they please, and there will still be folks online learning the same things as them. Self paced learning isn&amp;rsquo;t new. John Hopkin&amp;rsquo;s has offered it through their CTY programs for a long time, and MIT&amp;rsquo;s OpenCourseWare has provided content for free for years, but the content has never been open, free, and as community based as it can be today. Khan Academy could bring these students, all learning the same material at the same time, together, perhaps even offline. Study groups like this are popping up all over the world out of Coursera classes. People meet up to watch the lectures together or help each other through homework problems. That&amp;rsquo;s a fantastic way to learn.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s so much to be gained from this sort of interaction. And there&amp;rsquo;s so much to be learned from a group of students working on mathematics together, even when they&amp;rsquo;re all at different skill levels. Traditional classrooms cluster students by grade; they&amp;rsquo;re all learning the same content at the same time, and they have been their whole lives following the same sequence through mathematics as each other. With students learning at their own pace on Khan Academy, it will be great to see a classroom with a wide spectrum of mathematical ability. The sort of learning that takes place when a fast-paced student, studying algebra ahead of his class, works with a peer struggling to master long division is rare and really valuable. This is the type of interaction I expect will arise naturally out of making K-12 mathematics a self paced endeavor while maintaining the traditional 5 days a week classroom setting.&lt;/p&gt;
&lt;p&gt;One other amazing thing that Khan Academy, Coursera, and these other online education websites will be doing is individualizing the teaching experience. I already mentioned how Khan Academy could recommend different tutorials based on your performance or ability or interests. The individualization that can be done can also take place at a finer level. Coursera co-founder Daphne Koller came to Princeton to speak recently and she explained some of the work being done in this area. The data that Coursera is receiving from students is tremendous in quality and volume. With classes of tens of thousands of people, a single numerical answer question will garner hundreds of different answers. There will be clusters in these answers, with thousands of people submitting the same incorrect response. This gives professors insights into the minds of their students they never had before. With this information, we can figure out exactly what sorts of mistakes are common, and how to avoid them. People likely to make a particular type of mistake can be shown content in a way tailored to them. There may be groups of people who tend to share patterns in which mistakes they make, or which types of material they&amp;rsquo;re good at. The scale at which these courses are being offered and at which the feedback is being received will allow for insights into teaching that were never possible before.&lt;/p&gt;
&lt;p&gt;That said, I think that while online education is young and still in its stage of constant iteration, feedback should be collected much more explicitly. The value of people&amp;rsquo;s behavior on Khan Academy, Coursera, edX, Udacity, etc should not be understated, but these companies could learn so much more about their content and how students and &amp;ldquo;users&amp;rdquo; respond to their content it they merely asked. If Coursera put up two buttons next to their videos, one labeled &amp;ldquo;boring&amp;rdquo; and one labeled &amp;ldquo;interesting&amp;rdquo; (or &amp;ldquo;simple&amp;rdquo; and &amp;ldquo;confusing&amp;rdquo;, or &amp;ldquo;easy&amp;rdquo; and &amp;ldquo;hard&amp;rdquo;), or if they placed a feedback box underneath their video lectures, then I think the professors offering the courses would have a much better understanding of their audience. As it is, with both online lectures and live lectures, the professor gets very little feedback. They wonder Are my student&amp;rsquo;s understanding what I&amp;rsquo;m saying? Am I going to fast? Too slow? Is that kid in the orange shirt snoring?. This is one area where over the next year we have to leverage technology better. We need to get this feedback cycle right soon. These are the days that content is being produced at an incredible rate. The Khan Academy videos that go up this year, the Coursera classes being recorded every day - this is content that will last forever, and as we produce more of it, it&amp;rsquo;s so important that we maintain the highest possible standards, and for that feedback is critical.&lt;/p&gt;
&lt;p&gt;While quality is important, so too is the opportunity for everyone to become a teacher. More on those thoughts another day. We won&amp;rsquo;t all produce lectures with the quality of Sal Khan or Kevin Wayne. It is primarily at the top that we must ensure the content is the best it can possibly be. Still, feedback from students is so important to anyone teaching in any capacity.&lt;/p&gt;
&lt;p&gt;This notion, allowing anyone to teach, is so fundamental to the way education has to be. People learn from other people. Lore understands this. Piazza understands this. This is the philosophy that makes 
&lt;a href=&#34;http://albanyareamathcircle.blogspot.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;Albany Area Math Circle&lt;/a&gt; such an incredible group. This is why college campuses thrive and produce innovation. During our visit with Piazza, founder Pooja Sankar spoke about (in addition to teaching us some great lessons in entrepreneurship from the company&amp;rsquo;s founding story) Piazza&amp;rsquo;s mission to take down barriers to learning. Piazza is really succeeding in this. They&amp;rsquo;ve empowered people so that everyone in the class can be the teacher in a way that&amp;rsquo;s never worked in lecture classes before.&lt;/p&gt;
&lt;p&gt;Going forward, only good can come of these trends toward moving education online. Khan Academy&amp;rsquo;s mission seems inevitable. A world class education is becoming available to anyone, anywhere. The best ways to make this happen are still being discovered, and there&amp;rsquo;s so much to explore in the world of online pedagogy. This is something to be excited about. This is something I want to be a part of.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Do you take online courses?</title>
      <link>https://davidbieber.com/post/2012-09-19-do-you-take-online-courses/</link>
      <pubDate>Wed, 19 Sep 2012 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2012-09-19-do-you-take-online-courses/</guid>
      
      <description>&lt;p&gt;If you&amp;rsquo;ve taken any online classes, read on! Online education is growing rapidly, and I want to make the online education experience the best it can be. To that end, I would really appreciate your insight regarding online courses such as those offered at Coursera, Udacity, edX, Khan Academy, and MIT&amp;rsquo;s OpenCourseWare.&lt;/p&gt;
&lt;p&gt;Please help me by 
&lt;a href=&#34;https://docs.google.com/spreadsheet/viewform?formkey=dEFYRE1OVmFaQ1doTEoyZV9teG13eGc6MQ&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;filling out this short 4 question form&lt;/a&gt; regarding your online education experiences:&lt;/p&gt;
&lt;p&gt;The questions asked are: &amp;ldquo;Which platforms have you used?&amp;rdquo; &amp;ldquo;Tell me a bit about yourself.&amp;rdquo; &amp;ldquo;How do you take notes on video lectures?&amp;rdquo; and &amp;ldquo;Tell me more!&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Pretty open ended, I recognize. And I&amp;rsquo;d love to hear what you have to say.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>What&#39;s a Code@Night?</title>
      <link>https://davidbieber.com/post/2012-09-18-whats-a-code-at-night/</link>
      <pubDate>Tue, 18 Sep 2012 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2012-09-18-whats-a-code-at-night/</guid>
      
      <description>&lt;p&gt;&lt;a href=&#34;http://www.youtube.com/watch?v=7l_yY4_-QPM&#34;&gt;Check out this video showing what Code@Nights are all about!&lt;/a&gt;&lt;/p&gt;&lt;/p&gt;
&lt;p&gt;Since you’re reading my blog, you should know a little bit about myself. I’m a big fan of Scrabble. I love math, puzzles, and playing tennis. I spend a lot of my would-be-free time on &lt;a href=&#34;https://www.coursera.org/&#34;&gt;Coursera&lt;/a&gt;. And I’m &lt;strike&gt;vice&lt;/strike&gt; chair of &lt;a href=&#34;http://princetonacm.com/&#34;&gt;Princeton’s ACM Group&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ACM stands for Association for Computing Machinery, but that’s not important. I think that name is silly.&lt;/p&gt;
&lt;p&gt;One of ACM’s main functions on campus is “promoting interest in computing and it’s applications.” That’s from the ACM Constitution, and it’s a beautiful goal, a goal helped along by Code@Nights, and a goal that I encourage you to participate in. Let’s start with Code@Nights, then I’ll point out how you can make a difference.&lt;/p&gt;
&lt;p&gt;In short, a Code@Night is a fun-filled night of coding, powered by enthusiasm, extension cords, pizza and drinks. That’s right! Enthusiasm :D!&lt;/p&gt;
&lt;p&gt;Personally, I find all nights of coding are fun-filled, but having a Code@Night guarantees it. Here’s how a typical Code@Night goes. Step 1, the ACM Officers (lovely people by the way) pick a date for the Code@Night and we book a room in the best building on Princeton’s campus, Sherrerd Hall. Sometime we’ll pick a theme (like quines or stenography!) as well.&lt;/p&gt;
&lt;p&gt;Sherrerd Hall:
&lt;img src=&#34;https://66.media.tumblr.com/tumblr_ma7iztLbrp1rwq1pu.jpg&#34; alt=&#34;Sherrerd Hall&#34;&gt;&lt;/p&gt;
&lt;p&gt;We send out an email (and in this case a blog post) to the ACM Group at Princeton, and everybody marks the date on their calendar, because nobody likes to miss a Code@Night.&lt;/p&gt;
&lt;p&gt;Then, hours before the Code@Night begins, the ACM Officers (did I mention how wonderful they are?) scramble to get snacks and beverages ready for the Code@Night, and they inevitably end up with a bit too much Red Bull and Mountain Dew, and never enough fresh fruit.&lt;/p&gt;
&lt;p&gt;The fruit is very important to the success of a Code@Night. Sure, hackers will show up because you advertise pizza and sugary things, but after a long night of coding, fresh fruit will bring a smile to any developer’s face.&lt;/p&gt;
&lt;p&gt;Around 9pm, the hackery folks begin to arrive. They’ll keep on arriving until well after midnight, so we’re sure to keep the pizza coming and the drinks flowing. Sometimes we’ll plan a tech talk or demos to liven up the night. They come, they code, they conquer. They discuss the latest hacker news. They make the latest hacker news. They share their ideas and expertise with one another, and build pretty cool stuff. And when they’re tired, or realize they have to wake up early the next day for that big Dropbox interview, they leave at 1, 2, 3 in the morning, sometimes later.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Our next Code@Night is this Friday, September 14, and is open to the Princeton Community.&lt;/b&gt; Located on the 3rd Floor of Sherrerd.&lt;/p&gt;
&lt;p&gt;Most of you probably aren’t Princeton students, but still want to attend a Code@Night. To you, I say “make one!” Find a room and shoot an email out to your computer sciencey and hacker friends. Order some pizza and be enthusiastic. And don’t forget the fruit. And when the event is a roaring success, let me know &lt;a href=&#34;http://www.twitter.com/@Bieber&#34;&gt;@Bieber&lt;/a&gt; or by email: &lt;a href=&#34;mailto:dbieber@princeton.edu?subject=Code@Night%20a%20roaring%20success!&#34;&gt;dbieber@princeton.edu&lt;/a&gt;! Try it, you’ll like it.&lt;/p&gt;
&lt;p id=&#34;can_students&#34;&gt;Princeton students: I hope to see you there.&lt;/p&gt;
&lt;p id=&#34;_can_students&#34;&gt;Friday Sept 14, 2012, 9pm - whenever, 3rd Floor Sherrerd Hall</description>
    </item>
    
    <item>
      <title>Collapsible Blog Posts</title>
      <link>https://davidbieber.com/post/2012-09-04-collapsible-blog-posts/</link>
      <pubDate>Tue, 04 Sep 2012 00:00:00 +0000</pubDate>
      
      <guid>https://davidbieber.com/post/2012-09-04-collapsible-blog-posts/</guid>
      
      <description>&lt;p id=&#34;cbp_hello&#34;&gt;This is my Hello World blog post of sorts. It&#39;s my introduction to the world of blogging, the blog post likely only read by my family and a few techie friends. It&#39;s the post in which I make an unrealistic commitment to blog frequently, which I later fail to live up to. And it&#39;s the post in which I introduce a new form of blogging, &lt;u&gt;Collapsible Blogging&lt;/u&gt;. Here&#39;s the tl;dr.&lt;/p&gt;
&lt;p id=&#34;_cbp_hello&#34;&gt;Hello World!&lt;/p&gt;
&lt;p id=&#34;cbp_tldr&#34;&gt;&lt;b&gt;Click any part of this post to see it in condensed form.&lt;/b&gt;&lt;br&gt;
Source is here: &lt;a href=&#34;https://gist.github.com/3616675&#34;&gt;https://gist.github.com/3616675&lt;/a&gt;
&lt;/p&gt;
&lt;p id=&#34;_cbp_tldr&#34;&gt;&lt;b&gt;Click stuff to collapse. Click again to expand.&lt;/b&gt;&lt;/p&gt;
&lt;p id=&#34;cbp_commit&#34;&gt;Before we get into the details of Collapsible Blogging, let&#39;s start with that commitment to blog. Every half-baked blog has it, so here&#39;s mine. Once a month. I know my life is sufficiently not boring that something blog-worthy will come along each month. Why, just last Tuesday I ran with &lt;a href=&#34;http://en.wikipedia.org/wiki/Tyson_Mao&#34;&gt;Tyson Mao&lt;/a&gt; (yeah, the Rubik&#39;s Cube guy) and had a near encounter with &lt;a href=&#34;http://en.wikipedia.org/wiki/Richard_Hammond&#34;&gt;Top Gear&#39;s Richard Hammond&lt;/a&gt; (he came by Twitter while I was interning there). This post counts for September. So you&#39;ll be hearing from me next at least before Halloween&#39;s end. Here&#39;s to a happy, healthy, blogging career!&lt;/p&gt;
&lt;p id=&#34;_cbp_commit&#34;&gt;I&#39;ll blog once a month. And I like name dropping.&lt;/p&gt;
&lt;p id=&#34;cbp_skim&#34;&gt;
So, how&#39;s this whole Collapsible Blogging thing work, why is it awesome, and how can you try it out? Collapsible Blogging works well with my own internet content consumption habits, which I believe I share with many of the other Hacker News obsessed entrepreneurial spirited folks out there on the internet. Possibly you. I choose an interesting Hacker News article, I read a bit, and I quickly lose interest in favor of another well titled article. My goal is really just to consume quickly the highlights of the web. Collapsible Blogging let&#39;s me skim faster. But maybe that&#39;s a not a good thing. I&#39;m rereading Fahrenheit 451- and there&#39;s a passage this reminds me of:&lt;br&gt;&lt;br&gt;
&lt;p&gt;&amp;ldquo;I sometimes think drivers don&amp;rsquo;t know what grass is, or flowers, because they never
see them slowly,&amp;rdquo; and&lt;br&gt;&lt;br&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Have you seen the two-hundred-foot-long billboards in the country beyond town? Did you know that once billboards were only twenty feet long? But cars started rushing by so quickly they had to stretch the advertising out so it would last.&amp;rdquo;
&lt;br&gt;&lt;br&gt;
I don&amp;rsquo;t want Collapsible Blogging to be those two-hundred-foot-long billboards.&lt;/p&gt;
&lt;/p&gt;
&lt;p id=&#34;_cbp_skim&#34;&gt;
Use Collapsible Blogging to make your content easier to skim.
&lt;/p&gt;
&lt;p id=&#34;cbp_expand&#34;&gt;
So with that in mind, I introduce the second use of Collapsible Blogging. Diving deeper into a topic. If you&#39;d like to learn more about diving deeper into a topic, click this passage.
&lt;/p&gt;
&lt;p id=&#34;_cbp_expand&#34;&gt;
Collapsible Blogging let&#39;s people who aren&#39;t interested in the topic read quickly, while people like yourselves who want the full blog post can read more in a single click, as you just discovered. 
&lt;br&gt;&lt;br&gt;
One application of this is resumes. On my resume I list that I have experience with Java, Python, etc. An employer may care just about my &lt;a href=&#34;http://www.quora.com/Have-I-fallen-in-love-with-Python-because-she-is-beautiful&#34;&gt;lovely Python&lt;/a&gt; experience, but my resume doesn&#39;t go into details about each technical skill and where I&#39;ve used them. If I build my resume with Collapsible Blogging, I can make it so that with a single click potential employers can learn more about my experience with just the skills that interests them. They can learn what matters to them without me cluttering my resume with every project I&#39;ve ever worked on.
&lt;br&gt;&lt;br&gt;
Likewise, say I write a blog post about my experiences at Twitter and Facebook. You might be interested in reading about the technical challenges I faced and the programming concepts I learned, while another reader might be more curious about the pool party I attended at Zuck&#39;s house. If I write my post using Collapsible Blogging, you, the reader, can choose to read about the parts of my experience that interest you most, or you can choose to just skim the collapsed form of the post.
&lt;/p&gt;
&lt;p&gt;Some paragraphs don&#39;t have collapsed forms. You can only collapse the passages with a left border. There isn&#39;t really a shorter way to say that!
&lt;/p&gt;
&lt;p&gt;
If you&#39;re interested in using Collapsible Blogging yourself, it&#39;s remarkable easy. I include the &lt;a href=&#34;https://gist.github.com/3616675&#34;&gt;source as a gist&lt;/a&gt; below. Each passage that I want to collapse, I give an id. Then I create an element with the same id but preceded by an underscore. The CSS below hides elements with ids starting with underscores, and the JS below uses JQuery to handle the swapping in and out of elements. If you&#39;re blogging with Tumblr, just paste the code into your theme and you&#39;re good to go! Anywhere else, it should be as simple as adding the JS and CSS to your page.
&lt;/p&gt;
&lt;p id=&#34;cbp_tryit&#34;&gt;Try it. You&#39;ll like it.&lt;/p&gt;
&lt;p id=&#34;_cbp_tryit&#34;&gt;Inspired to try blogging by a few of my friends: &lt;a href=&#34;http://www.dskang.com&#34;&gt;Dan Kang&lt;/a&gt; and &lt;a href=&#34;http://www.harvestzhang.com&#34;&gt;Harvest Zhang&lt;/a&gt;. Thanks guys! Keep on writing!&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://news.ycombinator.com/item?id=4477488&#34;&gt;Discuss on hacker news.&lt;/a&gt;&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/dbieber/3616675.js&#34;&gt;&lt;/script&gt;
</description>
    </item>
    
  </channel>
</rss>