Tips for golfing in dc

19

5

What general tips do you have for golfing in dc?

dc is a calculator utility for UNIX/Linux that predates the C language. I am interested in how to make my dc programs (calculations?) shorter. I'm looking for ideas that can be applied to general that are at least a little bit specific to dc (eg. removing comments is not a helpful answer)

Please post one tip per answer.

mriklojn

Posted 2016-04-12T19:14:03.627

Reputation: 381

7Use Marvel instead. – Magic Octopus Urn – 2017-01-23T20:23:45.143

Answers

6

If-then-else statements

Suppose we want to check the condition a==b (let a and b be stored in their respectively-named registers).

edit:
[         # Everything is wrapped in one big macro
  [         # An inner macro for our *then* part
              # <-- Stuff to execute if a==b here
  2Q          # Then quit the inner and outer macro
]sE       # `E' is for Execution register ;)
la lb =E  # if a==b, execute E
          # if E is executed, it will quit the whole macro, so the rest is never reached:
          # <-- Stuff to execute if a!=b here
]x        # End macro; Execute

Let (foo) be a placeholder, for the purpose of condensing:

[[(then)2Q]sE(condition)E(else)]x

Pretty sure this is the most compact if statement possible (also featured here).

Joe

Posted 2016-04-12T19:14:03.627

Reputation: 895

1Maybe [[thenaction]P][[elseaction]P][r]sI 2 4 =I x sI f is a start? The actions for tehn and else are on the stack, the "If" macro swaps them and is caled conditionally. then the top of stack will executed and the unused macro will be dropped in I to clean up the stack. 2 4 are just the example data to compare. Alternatively the [x]sI part can be moved to the comparison, if considered more readable: [[thenaction]P][[elseaction]P] 4 4 [r]sI =I x sI f. The f in the examples just shall show tat the stack is clean afterwards... – None – 2016-08-02T07:34:05.393

1Rosetta Code's page about Dc mentions 3 flavours of dc and that was the 1st page where I saw OpenBSD dc's if-then-else construct. I think we need a dc fan bundle with all 3 flavours for all major operating systems... o:-) ...and my if-then-else proposal above does not work on the original dc because it lacks the r command... :-( – None – 2016-08-15T13:34:11.917

1What about: [[(if)2Q]si(condition)i(else)]x -- wrapping the whole thing in a macro, and the if-portion inside another macro inside that, so that you can 2Quit your way out of the whole thing before reaching the else-portion. So if you want to do if 1 == 1 then print 1 else print 2, it would be 1[[1P2Q]si1=i2P]x (untested as I don't have access to dc right here and now. Was also sure I had done this trick in an answer here before but couldn't find it) – daniero – 2016-08-17T15:39:25.953

Yup, I did the math, my suggestion is shorter. With the same example and "notation", and removing whitespace it's [/*else*/]sE[[/*then*/]sE]sIlalb=IlEx vs [[/*then*/2Q]sIlalb=I/*else*/]x -- 6 bytes difference. Still untested tho :P – daniero – 2016-08-17T16:09:36.367

1Nice work, @daniero! I'll update the post when I have time, or you can do it if you want. – Joe – 2016-08-17T23:51:46.663

5

0 to the nth power instead of conditionals/macros

Sometimes you might need something like ternary conditional:

A == B ? C : D;

A nice way to handle this is described in @Joe's answer. However we can do better:

0AB-^E*C+

where E is D - C.

This tests for equality by raising 0 to the power of the difference of the two values. This results in 1 if equal and 0 otherwise. The rest just scales the 1 or 0 to the values C or D. This works because dc gives 00 = 1 and 0n = 0 for n != 1.

Digital Trauma

Posted 2016-04-12T19:14:03.627

Reputation: 64 644

5

You can save input with d

By using d, which duplicates the ToS (top of stack) you can move the input out of the way for later use, while still being able to use it.

Rɪᴋᴇʀ

Posted 2016-04-12T19:14:03.627

Reputation: 7 410

@NoOneIsHere oh cool!!! Thanks! – Rɪᴋᴇʀ – 2016-08-14T19:08:52.730

5

Arrays

Although they're a headache for beginners, dc offers arrays. They work like this:

value index :a    # store `value' in array linked to top of stack `a', with index `index'
      index ;a    # push a[index] on (main) stack

As usual, the first element has the index 0. Arrays can be useful when working with sequences, like in the SUDSI sequence, especially in combination with counters. Arrays can reduce the amount of number-shuffling you need to do (and the number of counters and comparisons) if you want to select a particular element without destroying your environment. For example, if you want to move a stackful of numbers into an array, you could write a recursive function that uses z (stack depth) or z 1- as the index, stores the element, and checks whether z == 0 to terminate itself.

[z 1- :a z 0 !=F]dsFx    # or I could just write such a function for you :)

Be aware of the following:

  • Arrays are associated with instances on named stacks. If you push a new value on a stack that has an array associated with it, that array will also be "pushed back", and a "new" array will take its place. The old array won't be usable until the corresponding value on the named stack is also usable (i.e., on top of its stack). This is a complicated concept that would be better explained with a good animation, which is beyond me.
  • You can store stuff in a named array without actually pushing a value into the corresponding named register. However, if you do this, you cannot access the stack/register with that name for the rest of the session. dc will crash.
  • If you pop a value off a named stack, any values in the corresponding array will be lost—no warnings, no safeguards, nothing. Just gone (which can also be useful).

Joe

Posted 2016-04-12T19:14:03.627

Reputation: 895

Nice job with the DC tips! – Rɪᴋᴇʀ – 2016-06-22T04:08:56.913

dc may have been updated recently, and array behavior may have changed slightly with regard to crashing. Can't confirm either right now, but I think something was different last time I used it on Linux. – Joe – 2017-11-29T16:42:43.793

1If you try to read an index from an array that hasn't been set, you get 0 and not an error. Which can be very useful, but is also worth being mindful of if you're potentially putting 0s in arrays... You'll need another way to test that the index has been touched. – brhfl – 2019-06-14T20:59:03.523

4

Digits A to F may be used in substitution for the numbers 10 to 15. However they must still be treated effectively as base 10 digits (assuming input base is 10) when in different places. In other words, with input base 10 FF would not represent 255, it would represent (15 * 10) + 15 or 165.

In fact this works for all digits 0 to F in any input base 2 to 16. So if the input base is 5, then 26E would be (2 * 5^2) + (6 * 5) + 14, or 94.

Note this behaviour is in effect for the unmodified GNU sources. However, as @SophiaLechner points out, RedHat-based distros appear to use bc-1.06-dc_ibase.patch which changes this behaviour so digits >= ibase are treated as ibase - 1, regardless of their actual value. Note the TIO dc appears not to have bc-1.06-dc_ibase.patch (even though its Fedora 28 ¯_(ツ)_/¯ ).

Digital Trauma

Posted 2016-04-12T19:14:03.627

Reputation: 64 644

This isn't quite right - although single digits above the input base are interpreted as you'd hope, if the literal has multiple digits, or even a decimal point, invalid digits for the base are interpreted as (base-1). So in input base 10 FF represents 99, in input base 5 26E is the same as 244, i.e. base 10 74. – Sophia Lechner – 2018-07-18T19:56:26.537

@SophiaLechner Are you sure? https://tio.run/##S0n@/9/QIJ/L0CCTy82tgMs0k8vIzLXg/38A Which dc version are you running? I'm using GNU dc 1.4.1 on ubuntu and GNU dc 1.3 on MacOS

– Digital Trauma – 2018-07-18T20:14:22.593

Interesting. I'm running 1.3.95 on Red Hat, and here's your sample program:

 [slechner@XXX]$ dc -e '10o 10i FFp 5i 26Ep'
 99
 74
 [slechner@XXX]$ dc --version
 dc (GNU bc 1.06.95) 1.3.95
 – Sophia Lechner  – 2018-07-18T21:06:55.750

Argh...can't make the code block work in comment. The point is that FFp outputs 99 in 1.3.95. Could you check this in your MacOS version, then? – Sophia Lechner – 2018-07-18T21:11:51.080

And TIO says it's running 1.4.1 – Sophia Lechner – 2018-07-18T21:12:57.433

The GNU dc manual is silent on this, but the bc manual (for 1.06) says "Input numbers may contain the characters 0-9 and A-F. (Note: They must be capitals. Lower case letters are variable names.) Single digit numbers always have the value of the digit regardless of the value of ibase. (i.e. A = 10.) For multi-digit numbers, bc changes all input digits greater or equal to ibase to the value of ibase-1. This makes the number FFF always be the largest 3 digit number of the input base." Based on my dc --version output, I think it's been implemented as a front end to bc. – Sophia Lechner – 2018-07-18T21:24:32.577

@SophiaLechner Your output appears to be due to bc-1.06-dc_ibase.patch, which seems ubiquitous in RedHat-based distros, but not so much in debian-based. As far as I can tell, patch has not made it upstream to the GNU sources

– Digital Trauma – 2018-07-18T22:29:58.053

1Sure thing! Thank you for all the research. – Sophia Lechner – 2018-07-18T23:03:55.323

4

Sometimes it is necessary to discard a number from the stack. One way to do this is to simply pop it into an unused variable, i.e. st. However in some situations, you can pop it to a couple of other places, e.g. the input base when you have no more numerical input or to the precision specifier if you don't have any more operations to do where precision would make a difference. In the former case, use i. In the latter case, use k.

Digital Trauma

Posted 2016-04-12T19:14:03.627

Reputation: 64 644

If numeric output isn't important, o can be used too. And if any of these things are unimportant, they can be used as storage as well as mere discard - I/K/O recall them respectively, and saves bytes over sa/la etc. Valid values AFAIK: i 2-16; k any nonnegative integer; o any integer greater than 1. – brhfl – 2019-06-14T20:56:27.717

4

Length Calculation: Z, X, and z

Z pops the ToS and pushes the number of digits (decimal) if it's a number or the number of characters if it's a string. This can be useful for detecting the length of a result (for buffering output) or computing string length. Note that for numbers, Z pushes the combined length of the integer part and the fraction part.

X pops the ToS and pushes the number of digits in the fraction part of the number. If the ToS was a string, 0 is pushed.

To find the number of digits in the integer part of the number, one might use dZrX-. If you haven't changed the precision from the default k==0, using 1/Z is shorter, but suppose you need to maintain a particular non-zero precision after the operation: Kr0k1/Zrk is rather an eyesore.

z pushes the number of items on the stack. One of my favourite commands, it does not actually pop any values! It could be used to generate a sequence of numbers or increment a counter. Using zd repeatedly (say, at the start of a macro) could let one test a calculation on each natural or whole number in ascending order.

Joe

Posted 2016-04-12T19:14:03.627

Reputation: 895

Have used z for this and that before, but never occurred to me to use it as a hack of a counter… Excellent… – brhfl – 2016-08-24T02:35:14.887

2

Just discovered this by accident. Yet another way to generate a zero: _.

_ is a signal to dc that the following digits are a negative number. Example:

_3 # pushes -3

But what if we don't follow it with a number?

_ # pushes 0...sometimes

This works when the next non-blank character following the underscore is not a digit. If a digit follows it, even after a newline, it is interpreted as a negative sign.

c4 5_6  # -6,5,4
c4 5_ 6 # -6,5,4
c4 5_
6       # -6,5,4 # still a negative sign since the next thing it sees is a digit
c4 5_z  #  3,0,5,4 # if it's followed by a non-digit, it's a 0
c4 5_p6 #  6,0,5,4
c4 _*   #  0 # 4*0=0

Joe

Posted 2016-04-12T19:14:03.627

Reputation: 895

2

When initialising a function macro (we'll use F) that you want to run immediately, use something like dsFx rather than sFlFx. The same works for variables: dsa rather than sala.

If you need to do other stuff in between the storing and the loading (e.g., sa[other stuff]la), still consider whether the above is viable: If you leave a value on the stack before the other operations, will it be back at the top by the end of those operations?

Joe

Posted 2016-04-12T19:14:03.627

Reputation: 895

1

dc reads input a line at a time. If you need to read in multiple items, doing it one-per line either requires a ? for every line to be read, or a cumbersome macro loop. Instead, if all input items can be put on one space-separated line, then a single ? will read all the input items, pushing each one onto the stack.

For example in seq 10 | dc -e'?f', seq outputs integers 1-10, one per line. the ? will just read the first 1 which will be output when f dumps the whole stack. However in seq 10 | tr '\n' ' ' | dc -e'?f', the tr makes the input integers all space separated. In this case the ? will read all the integers from the line in one go, and f will output them all.

Digital Trauma

Posted 2016-04-12T19:14:03.627

Reputation: 64 644

1

If an operator is restricted from source, make a new one with a

Something that has come in handy for me a couple of times now is to avoid using a specific operator by pushing the ASCII value of the operator, using a to convert it to a string, and storing this in a register to be executed as a macro later on. For example, I need to do division, but am either disallowed from or trying to avoid using the character /. I can, instead do 47asd and then in the future when I need to divide 16 by 4, 16 4 ldx.

  • This will only work for single-character operators (can't build up a string), and will not work for commands like s that need to be postfixed by something.
  • This adds quite a few bytes, and is therefore only suitable when avoiding the specific character is necessary or somehow affords a score bonus.

brhfl

Posted 2016-04-12T19:14:03.627

Reputation: 1 291

1

Avoiding whitespace

Avoiding whitespace comes up in quite a few challenges, and is generally easy in dc. Aside from strings, the one very specific time that whitespace becomes necessary is when pushing multiple numbers in a row: 1 2 3. If this must be avoided:

  • Execute an empty macro in between: 1[]x2[]x3[]x.
  • If brackets are off the table, store a NOP of a macro ahead of time: 35asn and execute it in between: 1lnx2lnx3lnx.

brhfl

Posted 2016-04-12T19:14:03.627

Reputation: 1 291

You can also comma separate numbers, if you are willing to put up with dc: ',' (054) unimplemented warnings. – Digital Trauma – 2019-06-15T00:02:51.720

I hadn’t thought of that — presumably that applies to any given token that doesn’t resolve to a command… interesting… – brhfl – 2019-06-15T00:13:09.410

1

If the contents of the entire stack needs printing at the end of a program, a recursive macro loop could be used to achieve this. However, it is much shorter to simply use the f command.

Digital Trauma

Posted 2016-04-12T19:14:03.627

Reputation: 64 644