1

I worked my way up (or down, if you want) to the best score of Remember FizzBuzz? at VimGolf, but I have a hard time interpreting the solution:

33o<CR> Fizz<CR><C-H><Esc><C-V>A4k<C-H>.@.<C-U>Buzz<Esc>@.<C-V>GI0<Esc>gvg<C-A>ZZ

I understand

the beginning part of adding lines with "Fizz" – 33o<CR> Fizz<CR><C-H><Esc> – and the end where the preceding line numbers are added – <C-V>GI0<Esc>gvg<C-A>ZZ

but I don't understand

the middle part where the "Buzz" lines are added, i.e. <C-V>A4k<C-H>.@.<C-U>Buzz<Esc>@.. 4k<C-H> moves the cursor to the correct place and the last @. executes the content of the . register, but that's as much as I can fathom.

Can someone explain the Vim magic used here? ‍♂️

Andreas Nasman
  • 187
  • 2
  • 10
  • well a small hint removes all content from the position of the cursor to the beginning of the line. Like usually the deleted content is stored into the `.` register, which is later executed – Doktor OSwaldo Aug 31 '22 at 12:10
  • Yes, although in this case, `` does "Delete all entered characters before the cursor in the current line." (from `:help i_CTRL-U`). – Andreas Nasman Aug 31 '22 at 13:36
  • I'm quite confused about what exactly `@.` vs `.` executes in this context. After running the middle part, `4k^H.@.^UBuzz` is stored in `".` (checked with `:registers`) so `@.` should execute that. – Andreas Nasman Aug 31 '22 at 13:37
  • Oh, wait, I might have understood it now! Inserting `Buzz` is stored in the dot command (not sure you can check this anyway) so that's what `.` does here. The last part of `^UBuzz` is never actually run when invoking `@.` since the recursive call to `@.` comes before it. – Andreas Nasman Aug 31 '22 at 13:40

1 Answers1

3

The first part:

33o<CR> Fizz<CR><C-H><Esc>

puts Fizz on every line that is a multiple of 3, solving the first requirement of FizzBuzz. It's done with 33 iterations of:

  1. jump over an empty line,
  2. put Fizz on next line,
  3. open an empty line,
  4. leave insert mode.

33 blocks of 3 lines are added after line 1 so you get 100 lines in total and the cursor is left on line 100.

See :help o.

The second part:

<C-V>A4k<C-H>.@.<C-U>Buzz<Esc>

essentially creates a recursive macro that appends Buzz to lines that are a multiple of 5, instrumental in solving the second and third requirements of FizzBuzz.

In detail:

  1. <C-v>A to start insertion on column 2, aligned with the Fizzs from part 1,
  2. insert 4k,
  3. do <C-h> to delete the k,
  4. insert .@.,
  5. do <C-u> to delete everything that was inserted on the current line,
  6. insert Buzz,
  7. leave insert mode with <Esc>.

That is a lot of work just to insert Buzz on one line but this part actually serves three purposes:

  1. append Buzz to the current line (that is incidentally the last multiple of 5),
  2. record that as one edit, repeatable with .,
  3. record all that as a recursive macro in register ..

The macro in register . is:

  1. 4k, move up 4 lines,
  2. <C-h>, move the cursor back one character,
  3. . repeat last edit, so append Buzz to curent line (if there's Fizz, get FizzBuzz, if not, get Buzz),
  4. @. play back macro in register ..

See :help v_b_A, :help i_ctrl-u, :help ., help "., :help @.

The third part:

@.

plays back the recursive macro described above so it goes up 4 lines, then up 4 lines, and so on, solving the second and third requirements of FizzBuzz.

The fourth part:

<C-V>GI0<Esc>

inserts a 0 at the beginning of each line.

See :help v_b_I.

The fifth part:

gvg<C-A>

reselects the last visual block and then increments each 0 sequentially.

See :help gv and :help v_g_ctrl-a.

The sixth part:

ZZ

writes the file and quits Vim.

See :help ZZ.

romainl
  • 186,200
  • 21
  • 280
  • 313
  • Nice explanation, thanks! One important thing to point out in **The second part** is that the edit action repeatable by the `.` command actually does all of `A4k.@.Buzz`. That is, enters blockwise Visual mode, adds `A4k.@.` as text and then removes it with ``, and finally inserts `Buzz` and exits back to Normal mode. This makes empty lines divided by 5 end up as `Buzz` (with a preceding space) and lines with `Fizz` as `FizzBuzz`. – Andreas Nasman Aug 31 '22 at 17:05
  • I didn't think it was worth pointing out. – romainl Aug 31 '22 at 17:05
  • Ah, I think it's an interesting quirk that `Afoo` always appends on the second column, **even on empty lines**. That's the whole reason `A` is used in this challenge instead of `A` directly. – Andreas Nasman Aug 31 '22 at 17:18
  • Not a quirk and not an accurate description either. `` creates a 1x1 block and `A` appends to that block. The line being empty or not is irrelevant and `Afoo` doesn't "always append on the second column". – romainl Aug 31 '22 at 17:59
  • Sorry, I should be more clear. I meant that if you do `Afoo` with the cursor on the first character of a line, `foo` will always be inserted from the second character (column 2) onward on that line, even if it was empty beforehand. To me, it's interesting that you get a preceding space character "for free" running the above-mentioned command on empty lines. With "for free" I mean that you never explicitly type a space character in the command; it's added implicitly with `` on empty lines. – Andreas Nasman Aug 31 '22 at 18:10
  • If someone wants an explanation of how the recursive execution of `@.` knows when to stop, see [this helpful VimTip](https://vim.fandom.com/wiki/Record_a_recursive_macro). – Andreas Nasman Aug 31 '22 at 18:12