Tips for golfing javascript code, especially converting while loop to expression

3

The code:

function L(){N=null;E=[N,N];t=this;h=E;t.u=i=>h=[i,h];
t.o=_=>h==E?N:h[+!(h=h[1])];t.e=(i,l)=>h=(l||h)==E?[i, 
E]:[(l||h)[0],t.e(i,(l||h)[1])];t.s=_=>{o="";while(i=t.o())
{o+=i+","}return o}}

For my response to this (old) question: https://codegolf.stackexchange.com/a/196783/72421 (my linked answer has a commented version of the code and some disclaimers about valid inputs and outputs)

I'm looking for suggestions on how to golf it further. I feel the last function is especially verbose. I managed to make every other function a single expression so I didn't need curly brackets or a return statement, but couldn't figure out how to make the last one not require a while loop.

Daniel Franklin

Posted 2019-12-09T23:11:26.970

Reputation: 73

As a side note, I think t.o() fails if the list contains a 0 (or any other falsy value that may actually appear in the list), because it's treated the same way as a null. I assume that you're only asking for a golfed version, bugs included. :) But you may want to clarify this point. – Arnauld – 2019-12-10T00:23:21.670

@Arnauld I mentioned that as a disclaimer on my answer. I'm new, and not sure if that's a violation of the rules. Also wasn't sure if I should copy-paste all that into this question. – Daniel Franklin – 2019-12-10T00:24:39.070

Oh I see. I overlooked the footnote in your answer. – Arnauld – 2019-12-10T00:26:09.303

@Arnauld If someone figured out a better way to do t.o that didn't have that issue (without just replacing +!. with an equality test that adds a few bytes) I'd definitely consider that a valid answer to this question. – Daniel Franklin – 2019-12-10T00:27:40.127

Answers

8

I think the best way to get rid of the curly brackets is to go recursive.

Original function (46 bytes)

t.s=_=>{o="";while(i=t.o()){o+=i+","}return o}

Recursive version (29 bytes)

t.s=_=>(i=t.o())?i+[,t.s()]:i

The i+[,t.s()] trick saves a byte over i+','+t.s().

Because you stop when t.o() returns null, the result of your original version always ends with a comma. In the new version, we actually append null at the end (that's the :i) but it's coerced to an empty string because of the above trick, leading to the same behavior.

Arnauld

Posted 2019-12-09T23:11:26.970

Reputation: 111 334

Thank you! Do you have any suggestions for avoiding h being a global variable without having to replace every usage with t.h? – Daniel Franklin – 2019-12-10T00:31:34.217

1@DanielFranklin You can simply declare function L(h) (an unused parameter) to put h in the local scope. But this is code-golf, so we usually don't worry about that -- unless explicitly specified otherwise but I haven't read the original challenge thoroughly. – Arnauld – 2019-12-10T00:39:39.120

Thank you. Good to know that isn't necessary. – Daniel Franklin – 2019-12-10T00:42:15.353

5

Shortening t.s

Favor recursion over while loop

Given your t.s function

t.s=_=>{o="";while(i=t.o()){o+=i+","}return o}

Observe that o is a simple accumulator, so we can easily convert the loop into a recursive function. There are several ways to write the recursive function:

// explicit accumulator, aka tail-recursion
t.s=(o="")=>(i=t.o())?t.s(o+i+","):o
// implicit accumulator, shorter, saves 15 bytes from OP's code
t.s=_=>(i=t.o())?i+","+t.s():""

Free commas

i+","+t.s() can be shortened to i+[,t.s()], saving 1 byte.

Shortening the rest

Initialize the second parameter of t.e to h

The function t.e is using (l||h) several times, just to handle the initial call. JS's default argument is very permissive, so we can do this:

// instead of
t.e=(i,l)=>h=(l||h)==E?[i,E]:[(l||h)[0],t.e(i,(l||h)[1])];
// do this
t.e=(i,l=h)=>h=l==E?[i,E]:[l[0],t.e(i,l[1])];

Simplify base case

Instead of E=[N,N], simply E=[] works too. This has several golfing opportunities regarding string coercion:

  • h==E is equivalent to h==[], and [] coerces to "". Anything that stringifies into something other than "" is greater than E, so we can rewrite h==E?A:B into h>E?B:A.
  • h is now a linked list where 3::2::1::nil looks like [3,[2,[1,[]]]], which exactly stringifies into "3,2,1,". So we can simply coerce h to string: t.s=_=>h+"", or even better, t.s=_=>h+E.

Array unpacking

We can slightly do better here too:

// instead of
t.o=_=>h>E?h[+!(h=h[1])]:N;
// do this
t.o=_=>h>E?([a,h]=h,a):N;

And this also applies to t.e:

// instead of this
t.e=(i,l=h)=>h=l>E?[l[0],t.e(i,l[1])]:[i,E];
// do this
t.e=(i,[a,l]=h)=>h=l?[a,t.e(i,l)]:[i,E];

I chose l? because whenever h is nonempty, l is a reference so it always evaluates to true. If h is empty, both a and l become undefined.

Side note: I found that the OP's code actually works with falsy list items, because the only real comparison is h==E, that is, comparing stringification reference of whole array. Whatever E is, h==E cannot be true if the list h has at least one item, since its string will have at least one more comma than E's the reference becomes different.

Final result, 125 121 bytes

function L(){h=E=[];t=this;t.u=i=>h=[i,h];t.o=_=>h>E?([a,h]=h,a):null;t.e=(i,[a,l]=h)=>h=l?[a,t.e(i,l)]:[i,E];t.s=_=>h+E}

Try it online!

Bubbler

Posted 2019-12-09T23:11:26.970

Reputation: 16 616

Side side note: JavaScript compares arrays by reference, not by string. You're still right I think. You can verify this from the fact that [1,1]==[1,1] is false but [1,1]+""==[1,1]+"" is true – Daniel Franklin – 2019-12-10T01:07:45.523

*compares two arrays – Daniel Franklin – 2019-12-10T01:22:00.673

why not L=()=>{ * insert L's body here * } – famous1622 – 2019-12-11T18:01:24.297

@famous1622 Because I have to provide a class, and you can't use new to create an instance of an arrow function. Furthermore, this in the arrow function would refer to window because arrow functions don't have their own scope – Daniel Franklin – 2019-12-11T22:30:31.990