2013-01-21

Java Array2List

The challenge is Java Array2List. It's a fun challenge, shorter than it looks, and it's great for showing off certain techniques. If you've never done it, consider doing it now, because this post will have massive spoilers. Your choice. Below the break, I'll demonstrate how to do the challenge in 67 strokes.



What we're doing


Input and output are in split windows, so you can easily see what the challenge is asking you to do. For complicated challenges, a useful tactic in VimGolf is to fail and exit right away, then enter diff mode, so it's easier to understand what exactly you need to do. :help diff.txt
  • Each of the numbers from the input is also in the output, but each on its own line instead of separated by commas, and surrounded by c.add( and );.
  • There's also a d immediately after some numbers, but only those without a decimal point.
  • The curly braces around the numbers need to be removed.
  • The first lines look sort of similar between input and output. The word double might help us type the new header faster, but the capitalization is wrong, and it's followed by some junk [].
  • On each side of the =, you'll find the string List<Double>. Whenever you have to type something that long multiple times, there's sure to be a way to shorten it.

A quick macro


Each number's now on its own line, formatted almost correctly, thanks to a fairly simple macro.
  • 6J joins the lines together, making the gaps between numbers more consistent. Not only that, but it puts us in perfect position to start the macro. :help J
  • We record a macro in the "q register, then run it 12 more times. Each run puts one number on its own line, and surrounds it with C.add( and );. :help q
  • Using ^O, you can do one normal mode command, and come straight back to insert mode. In this case, I can switch between the back and front of a number more quickly than I could with Escape (or ^[). :help i_CTRL-O, :help i_CTRL-[
  • If you want to repeat a macro, you need to end in a position similar to the one you started in. In this macro, I end with ^b. That way, the macro starts one character right of a number, and it ends one character right of the previous number, ready to run again.
  • Although lines with a decimal point shouldn't end with d, I'm adding it to every line at this stage. The extras will be removed later.
  • The first character of each line is deliberately miscapitalized. This will pay off later.

:displaying your registers


No animation here. Just the output of :display after defining the previous macro. :display (which is identical to :registers, but has a shorter abbreviation of :di) is not a useful command for VimGolf entries, but it's great for figuring out what's going on. :help :registers
I recommend reading :help registers to learn about what each individual register does. It's very informative. In this case, I'd like to point out:
  • The unnamed "" register. It's called "unnamed" because when you use a paste command, this is what gets used by default (that is, you didn't specify another register). In this case, the C command deleted some text, and it ended up here.
  • The ". register. It contains the last chunk of text you typed in insert mode. Both these last two registers will come in handy later.
  • Since I started my macro with qq (and not qa, for instance), the macro I just defined is in the "q register.

Unrolling the macro


Using the sleep command gs in your macro, you can slow it down, or "unroll" it. I don't honestly find it that useful, but it makes a neat demonstration. :help gs

Removing the "d"s alternative: :s or g&


Removing the d's after each non-integer is probably the toughest part of this challenge. I'll show you a couple different strategies you might try, before showing the best.
First up is an :s command with a regular expression... or something like that. When you're not actually replacing, but deleting text, you can often save a stroke with a search followed by g&. In VimGolf at least. The problem is that g& is a "synonym for :%s//~/&", which means it replaces matches of the current search string (the "/ register) with the last :s replace string (which starts as nothing, hence "delete"), using the last :s flags (which starts as nothing, including no g flag, so it only hits one match per line) on every line. With all those caveats, g& may not be useful in real life as often as it is in VimGolf. But it's good to know. :help :substitute, :help g&
  • In all these examples, I'll start by removing the in line 1. The cursor's in perfect position after the macro, so might as well do it now.
  • In the regex, you have to be careful to match the decimal point ., not the C.add one. That's why I use \d* instead of .*, to match only digits. :help /character-classes
  • I use \zs to eliminate all characters matched to that point from the match. They still match, but they're not part of the match. Got that? :help /\zs
  • Compare /\.\d*\zsd^Mg& to :%s/\.\d*\zsd^M, and you'll see the former really does save 1 stroke.

Removing the "d"s (bad) alternative: a recorded macro


Another method you can try is a macro. You could make a macro that actually works right (replace ~f. with /\.\d^M), but it still takes more strokes than the last slide. Besides, I'm trying to make a point here.
  • I put ~ in this lousy macro to make it more similar to the next slide. It fixes the case of the first column, and moves the cursor onto the ., so that f. skips straight to the decimal. :help ~
  • f. moves the cursor to the next . in the the line, if it exists. Unfortunately, it often doesn't exist. When f or t can't find the character you ask for, and you're in a macro, the macro fails on the spot. It doesn't matter how many times you asked it to run; it's stopping right there. You could keep running the macro manually over and over, but only at an enormous cost in strokes. The manual doesn't explain this as well as it maybe should: the closest seems to be :help recursive_mapping. :help f

Removing the "d"s properly: with :normal


This is how you do it. Using the :normal command, you can turn macro failure (which was so annoying in the last slide) into a compact conditional statement. :help :normal
  • The typical way to abbreviate :normal is :no<S-Tab>.
  • When you run :normal with a range, it runs the commands you give it on each line, starting in the first column each time. If the macro fails on one of the lines, it stops there, but starts again on the next line. Here, when the f. command fails, it prevents ex from running and causing damage on lines without decimals. It's a conditional statement!
  • Miscapitalizing the beginning of every line finally pays off. The ~ command is normally for changing case, which we needed for the Double in the first line anyway. It also has the bizarre behavior of moving the cursor forward, so f. doesn't catch on the .'s in column 2. No other non-letter command can move the cursor between the 2 .'s reliably. Why is it important not to use a letter? When you use a letter immediately after a : command name, you have to put a space in between, so Vim knows it's not part of the command name. That space costs a stroke. When the first character is a non-letter, you can skip the space.

Line 1: Part 1


The left side of line 1 already contains the word Double (now capitalized correctly), so we'll use that to save strokes. Since everything we enter on the left side will be used verbatim on the right side, we want to do this side entering insert mode only once, so the whole thing will be saved in the ". register.
  • I used cW here, when I could have used cE. cw is an unusual command—a "special case"—according to the manual. Its range doesn't behave the same as dw or yw. :help cw
  • In insert mode, you can insert any register with ^R. Since paste commands use the "" register by default, ^R" is an idiom for pasting from insert mode. This time, it reinserts what we just deleted with cW. :help i_CTRL-R
  • To delete the word (not WORD) before the cursor in insert mode, you use ^W. A word can be a sequence of letters, digits, and the underscore. But, like here, it can also be a sequence of non-underscore punctuation. :h word, :help i_CTRL-W

Line 1: Part 2


Though we get to use the text we typed in the last part, there's still a bunch of typing at the end of this challenge. Lots of stray text inflates the challenge's stroke count, and makes it seem longer than it really is.
  • You could use the cursor key <End> to start this section one stroke "faster". (There's no way that huge reach is faster than ^[A in reality.) VimGolf counts cursor keys as 1 "stroke", which I don't agree with. I think the game is more fun without cursor keys, and it's better to practice patterns that you should actually use.
  • You could certainly insert the ". register with ^R., but ^A is a stroke shorter. :h i_CTRL-@

Put it all together


Here's the finished product. Put all the steps together, type ZZ (in VimGolf, you have to save and quit), and the total is 67 strokes. :help ZZ

3 comments:

  1. Hello, thanks, it is an instructive document, could you explain a little, Chinese Multiplication Table: http://vimgolf.com/challenges/510b1c61e48b7e0002000028
    Best regards,

    ReplyDelete
  2. Amazing, could you explain your solution here http://vimgolf.com/challenges/51459ef6b94aa50002000002 ?
    Thanks !

    ReplyDelete