18
\$\begingroup\$

Pip is an imperative golfing language with infix operators. It also borrows some features from functional and array programming.

What general tips do you have for golfing in Pip? I'm looking for approaches and tricks that are commonly useful for code golf and are specific to Pip (e.g. "remove comments" is not an answer).

Please post one tip per answer.

\$\endgroup\$
3
  • 5
    \$\begingroup\$ "Pip" "Tip" they rhyme perfectly! \$\endgroup\$
    – user96495
    Aug 31, 2020 at 2:42
  • 2
    \$\begingroup\$ Nice. I am ready to abuse all the answers from this thread. \$\endgroup\$
    – Razetime
    Aug 31, 2020 at 4:05
  • 3
    \$\begingroup\$ I thought it was PIP (Python Package Installer) \$\endgroup\$
    – pavi2410
    Sep 1, 2020 at 7:09

10 Answers 10

8
\$\begingroup\$

Use preinitialized variables

Pip has many global variables that are pre-initialized so you can avoid manually writing a number/string/something else out yourself.

Here are some of them (complete list):

  • _ Identity function (== {a})

  • h 100

  • i 0

  • k ", "

  • l Empty list

  • m 1000 (mnemonic: Roman numeral M)

  • n Newline character

  • o 1

  • s Space character

  • t 10

  • v -1

  • w Matches whitespace \s+

  • x Empty string

  • z Lowercase alphabet a to z

  • B Block that returns its second argument ({b})

  • G Block that returns its argument list ({g})

  • AZ Uppercase alphabet A to Z

  • CZ Lowercase consonants b to z

  • PA All Printable ASCII characters, 32 through 126

  • PI \$\pi\$ (3.141592653589793)

  • VW Lowercase vowels a to u

  • VY Lowercase vowels a to y

  • XA Matches one Latin letter [A-Za-z] (there are more X* commands)

Link to documentation

\$\endgroup\$
3
  • \$\begingroup\$ Should I make this community wiki? I’m not sure know what tips are supposed to look like \$\endgroup\$
    – user
    Aug 31, 2020 at 3:10
  • 5
    \$\begingroup\$ This is your tip. You are not required to make it community wiki. Just give it a decent title, and it'll be all good. Feel free to link to the docs here for more detailed info as well. \$\endgroup\$
    – Razetime
    Aug 31, 2020 at 4:04
  • \$\begingroup\$ @Razetime Thanks for the clarification. I'll also edit my answer in a while to make it a little better. \$\endgroup\$
    – user
    Aug 31, 2020 at 12:56
5
\$\begingroup\$

Use the Y operator

From a quick survey, it looks like I use Y (or one of its variants, YP and YO) in one out of every three Pip answers--more so as the answers get more complex.

Storing a value

Y stands for "yank," which will be familiar to Vim users as a command that copies the current line or selection into a buffer. The unary Y operator in Pip does something similar: it saves a copy of its operand in the y global variable and returns the operand unchanged. Essentially, Y<expr> is equivalent to y:<expr>, but shorter.

This alone makes Y (and the y variable) useful in many cases. Need to store something in a variable and don't care which one you use? Y saves a byte from the assignment. Need to use an expression twice? Yank it and use y twice instead. If the expression is longer than two bytes, you'll save.

But Y is also useful in another way:

Manipulating operator precedence

Suppose we want to count the number of 0s in the input and then tack the count onto the end of that input. (For example, input of 1001101 should result in 1001101 3.) Counting the 0s is 0Na, and so we would like to do a.s.0Na. But that won't work because N is lower precedence than ., and the expression would parse as ((a.s).0)Na. To enforce precedence, we can use parentheses: a.s.(0Na). This always works, and sometimes it's the only option.

But often, we can use Y instead. The trick is that Y has very low precedence--the lowest, in fact, together with P and O. So any expression to the right of Y will parse as Y's operand, while the whole Y expression will in turn be the right operand of whatever comes to its left. And Y will pass its operand through unchanged (plus assign it to y, but we don't care about that as long as we're not using y for something else). If we write a.s.Y0Na, it parses as (a.s).(Y(0Na)), just as if we had parenthesized 0Na. But it only costs one byte, while the parentheses cost two.

Caveats

A Y expression can only be used on the right side of a binary operator, not the left, because Y will take everything to the right of it as its operand. For instance, if we wanted to prepend the count of 0s instead of appending it, we couldn't do Y0Na.s.a--that would parse as Y(0N(a.s.a)). Instead, we'd have to fall back on parentheses or another strategy.

There is only one y variable, so you can't yank two different values in the same program (unless you can structure your code so that you don't need both of them at the same time). You'll have to pick one to assign to a different variable. Try it both ways and see which one saves you more bytes.

Binary operators in Pip always evaluate their left-hand side first, which means that you generally can't use the new value of y in the same expression where you yanked it: If you want to calculate the square of a+1, you can't do y*Ya+1 (y won't be a+1 when it's evaluated, because a+1 hasn't been yanked yet); and you can't do Ya+1*a (which would parse as Y(a+(1*a))). In situations like this, you'll probably want to yank the value first, in a separate expression: Ya+1y*y. If you absolutely need to do it in one expression, you can parenthesize the Y part: (Ya+1)*y. This works because the left-hand side of * is evaluated first, so y has the correct value when the right-hand side is evaluated.

\$\endgroup\$
2
\$\begingroup\$

Replace simple functions with lambdas

Lambdas are a generally good way to save 2 bytes(at the very least), as compared to a regular function that requires braces.

Pip's lambdas, however, do not mimic the general attributes of a lambda in say, Python. You cannot shorten all standalone functions into lambdas.

They're an extension of the identity function _, where _ = {a}. This means that you can create a lambda out of a function only if you are writing a function with a single expression.

Lambdas in Pip have some interesting uses other than single expressions. For example, you can reference the main function's arguments without yanking/copying them.

From Dlosc's tutorial on functions:

The function _+a adds its argument to the program's first command-line argument. a still has its top-level value because it's not inside curly braces.

The catch is that you cannot use commands(If, Else if, Loops) and everything must come down to one statement, as it is generally with applying operators on functions.

Due to those restrictions, Pip lambdas are generally better suited for small mapping or filtering functions. Happy golfing!

If you do find an interesting use case, do tell me.

\$\endgroup\$
2
\$\begingroup\$

Manipulating operator precedence with :

The : meta-operator turns any regular operator into a compute-and-assign version that modifies its left-hand side (LHS) in place. For example, x+:5 adds 5 to x and stores the result back into x. The expression evaluates to the result of the computation: if x was originally 2, then x+:5 would set x to 7 and also evaluate to 7.

Besides adding the compute-and-assign semantics, the : meta-operator changes the operator's precedence and associativity to match the precedence and associativity of the assignment operator: very low precedence, right-associative. The main reason is so that the new compound operator behaves like an assignment operator. However, we can also take advantage of this behavior in many situations by treating : as a "lower the precedence" meta-operator.

Example

Suppose we want to subtract 2 from each digit of a number. The straightforward way would be to split the argument into characters (digits) and subtract:

(^a)-2

Because ^ has lower precedence than -, we have to wrap it in parentheses, costing two extra bytes. Instead, we can use : to make the precedence of - lower, costing only one byte:

^a-:2

Now -: has lower precedence than ^, and the expression parses as (^a)-:(2) without needing parentheses.

Caveat

If the LHS of your higher-precedence operator is an lvalue (an assignable value such as a variable or an item in a list), adding the : meta-operator will modify its value. Sometimes this is fine (e.g. if the expression is at the top level and you just want to output it and halt). Other times, you need the value to stay unmodified; in those cases, you'll probably need to go back to parentheses.

In the above example, the LHS of -: is the expression ^a, which is not an lvalue. Therefore, nothing is modified (if you use the -w flag, you'll see a warning: Attempting to assign to non-lvalue). The expression still evaluates to the result of the computation, as desired.

\$\endgroup\$
1
\$\begingroup\$

M can map a value instead of a function

Normally, you would use M like this:

UC_M"abc"

with the left-hand side being a function and the right-hand side being some iterable you want to map it to. If you need to swap the arguments, "abc"MUC_ works too.

But in the case where you just want to replace every item in the iterable with a constant value, M can do that too--just give it a non-function as its left-hand side:

42M"abc"

This will return [42;42;42]: one 42 for every character in "abc". If you don't mind that you're getting a list and not a string, this is one byte shorter than 42X#"abc", and it's two bytes shorter than the list equivalent 42RL#"abc".

The value can be literally anything* that's not a function: Scalar, Pattern, List, Range, or even Nil.

This trick also works with some other map operators:

  • aMJb joins the results into a string after mapping (may be particularly useful when a is not a Scalar to begin with)
  • aMMb will give you a list of lists of a, the lengths of the sublists matching the lengths of the items in b
  • aMCb will give you a bxb grid (nested list) of a

That last one is a useful alternative to ZG when you want a value other than 0 (for example).


* Technically, it'll behave differently if given a list that contains a function.

\$\endgroup\$
1
  • \$\begingroup\$ Ah, the asterisk program. \$\endgroup\$
    – Razetime
    Sep 4, 2020 at 9:41
1
\$\begingroup\$

Shortcuts for certain lists

Normally, if you need a list of constants, you just enclose the items in square brackets, with separators as necessary ([4 8 15 16 23 42]). But there are shorter ways to construct some commonly used lists:

  • First, don't forget you can use preset variables if you haven't reassigned them. The empty list can be l instead of []. Using i for 0 and o for 1 in a list can save a byte (or even two) if they are next to another number, since lowercase variables don't require a separator: [0 5][i5]. Using v for -1, t for 10, h for 100, or m for 1000 also saves bytes directly, since the variable names are shorter than the numbers they represent.
  • Use a range if possible: [0 1][io],2. This still saves a byte if it's 1-based ([o2]\,2) or starts at some other number ([v0]v,1).
  • For a list of digits, split a number: [1]^1; [3o4o5 9]^314159; [oi]^t. This works for characters, too: ['#]^'#; ['h'i]^"hi".

Note that the latter two methods involve operators with different precedences, which you'll need to take into account. If working around the precedence is too much trouble, you can always fall back on the standard list syntax.

\$\endgroup\$
1
\$\begingroup\$

Use a list instead of concatenating

The default output format for Lists is to concatenate all of their items together. If you need to output more than three strings concatenated, you save bytes by throwing them all in a List instead of actually concatenating them:

m//60." hours, ".m%60." minutes"
[m//60" hours, "m%60" minutes"]

You may be able to save even more in specific cases by using one of the list-formatting flags:

[m//60"hours,"m%60"minutes"]   ; (with -s flag to join on spaces)

This trick works even better if one of your items is actually a List, or involves an operator with lower precedence than concatenation:

"The arguments are ".(gJk).'!
["The arguments are "gJk'!]

If you're doing something with the result other than just outputting it, this approach may not work for you. One context where it does work, however, is with the Replace operator: if the second argument is a Scalar or Pattern, a third argument that is a List will be converted to a string before being substituted.

\$\endgroup\$
1
\$\begingroup\$

Use unary operators whenever possible

A lot of binary operators in Pip have unary versions. The concept should be familiar: many programming languages have binary - for subtraction and unary - for negation. Pip extends the idea to a lot of other operators:

  • Binary / for division, unary / for reciprocal
  • Binary ^ to split on a separator, unary ^ to split into a list of characters
  • Binary TB to convert to a given base, unary TB to convert to binary
  • Binary || to strip characters from a string, unary || to strip whitespace from a string
  • Binary Z to zip two iterables together, unary Z to zip all the items of a list together

and many others.

The obvious benefit of unary operators is that they don't require two arguments. Instead of 1/x, /x will do the trick for -1 byte. The less-obvious benefit comes when we start combining operators:

Unary operators let you ignore precedence, sometimes

In an expression with two binary operators, the higher-precedence operator will be evaluated first: vEa%2 is (vEa)%2 because E has higher precedence than %. If we wanted vE(a%2) instead, we would need to use parentheses or some other trick to manipulate the precedence.

However, in an expression like vE%a, the only way to evaluate is vE(%a): E is evaluated first, but its right argument can only be the subexpression %a; it can't "steal" an operand from the lower-precedence operator like it could from binary %.

In a sense, the precedence of a unary operator doesn't matter on its left, only on its right. An expression like %vEa is still going to parse as %(vEa) because E is higher precedence than unary %. But if you can set up your expression so that a unary operator is the right-hand side of a binary operator, you can drop the parentheses and save more bytes.

Here's a real-life example. We want to group (<>) the longer string before we weave (WV) it with the shorter string, but WV is higher precedence than <>.

"NESW"WV("NESESWNW"<>2)  Original version, 23 bytes
"NESW"WV:"NESESWNW"<>2   Using a precedence trick, 22 bytes
"NESW"WV<>"NESESWNW"     Using a unary operator, 20 bytes
\$\endgroup\$
1
\$\begingroup\$

Output in pieces rather than concatenating

Say you have two expressions that you want to concatenate together and autoprint at the end of your program. The best-case scenario is that you simply use .:

a+b.'!

But sometimes the precedence doesn't cooperate:

(aJb).'!

You could lower the precedence of . using the : meta-operator, but that still costs an extra byte:

aJb.:'!

If this expression is the last expression in your program (i.e. you want it to be autoprinted and your program doesn't do anything else afterwards), and if you're concatenating Scalars (i.e. you're not depending on the vectorizing behavior of the . operator), then you can instead simply output the first expression using O and autoprint the second expression:

OaJb'!

This outputs the result of aJb without a trailing newline, then immediately outputs an exclamation point (with a trailing newline)--which produces the same output as concatenating them.

\$\endgroup\$
0
\$\begingroup\$

How to crash the interpreter

Pip is designed to be pretty forgiving. Operations that cause errors in other languages, such as dividing by zero, usually just return nil (and print a warning message, but only if you enable warnings using the -w flag). But sometimes in golf, you want the program to crash. For example, it's the only way to break out of an infinite loop.

There are a couple of ways to get a fatal error, but the golfiest is to trigger a runtime parse error by evaluating a string as code. Evaluating any single character that's an operator will crash the interpreter, since the operator needs (at least one) operand. Shorter still, the variable k is preset to ", ", which works just as well: Vk, 2 bytes.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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