Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

  [min, num, max].sort()[1]


sort() on JavaScript arrays sorts alphabetically (unless you pass a compareFunction)

https://stackoverflow.com/questions/21019902/why-cant-javasc...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


Good point, for those who need to implement a slick compareFunction for numbers, Math.sign is great:

  [min, num, max].sort((a, b) => Math.sign(a - b))[1]
An entirely different alternative is poor man's match:

  switch (true) {
  case num > max: return max;
  case num < min: return min;
  default: return num;
  }


Why sign though? I always write my sorts like this.

    .sort((a, b) => a - b)


Is there a better way to grab a reference to the subtraction operator?


I assume you're referring to a point-free reference.

Only by defining a `const subtract = (a, b) => a - b;`, which isn't really point-free.

Or by using a different language.


Wait, really? I just had to double-check my one JS project for bugs and either I used to know this gotcha or I got lucky. My sort()-ing needed to handle NaNs carefully so I was already using custom comparators.


"lexicographically" is a better descriptor than "alphabetically", since it's sorting by UTF-8 code point value.


It sorts by UTF-16 code unit.


Of course, my bad.


Very good point. I thought of it more as pseudocode.


imho a very elegant solution and probably works in most languages, I was surprised by JavaScript's behaviour myself


For a real fun headscratcher consider [ '9', '10', '11', '12', '13' ].map(parseInt)


Thank you for this exercise! I figured it out!

[ROT 13] spoiler: cnefrVag frpbaq nethzrag vf onfr, znc frpbaq nethzrag vf vaqrk va neenl. 10 vf gur frpbaq nethzrag, vaqrk 1. cnefrVag(10, 1) unf vyyrtny onfr

[0] https://rot13.com/


[min, num, max].sort()[1]

I really love this! I’m not sure whether to laugh, cry, or applaud, but I love how it makes me feel all those emotions at the same time.


I also think the fact it doesn't work in JS (due to sort defaulting to alphabetical sort) brings in even more appropriate emotions.


    [10, 2, 100].sort()[1] //returns 100


This is why computers are slow.


I was curious how slow this would be, and here is what JavaScriptCore made of this code:

  function clamp(n, min, max) {
      return [min, n, max].sort((a, b) => a - b)[1];
  }
  
  for (var i = 0; i < 10000000; i++) {
      clamp(Math.random() | 0, Math.random() | 0, Math.random() | 0);
  }
However, I was pretty disappointed when it seemed to be calling sort each time :( Perhaps I profiled it incorrectly? jsc's profiling data shows that it never hit FTL and nothing ever got inlined. The bytecode for DFG and Baseline is identical:

  Compilation clamp#CChm91-1-Baseline:
        arg0: predicting OtherObj
        arg1: predicting BoolInt32
        arg2: predicting BoolInt32
        arg3: predicting BoolInt32
          [   0] enter              
          [   1] get_scope          loc4
          [   3] mov                loc5, loc4
          [   6] check_traps        
          [   7] mov                loc11, arg2
          [  10] mov                loc12, arg1
          [  13] mov                loc13, arg3
          [  16] new_array          loc10, loc11, 3, 3
          [  22] get_by_id          loc7, loc10, 0
          [  27] new_func_exp       loc9, loc4, 0
          [  31] call               loc7, loc7, 2, 16
          [  37] get_by_val         loc6, loc7, Int32: 1(const0)
          [  42] ret                loc6

  Compilation clamp#CChm91-2-DFG:
        arg0: predicting OtherObj
        arg1: predicting BoolInt32
        arg2: predicting BoolInt32
        arg3: predicting BoolInt32
          [   0] enter              
          [   1] get_scope          loc4
          [   3] mov                loc5, loc4
          [   6] check_traps        
          [   7] mov                loc11, arg2
          [  10] mov                loc12, arg1
          [  13] mov                loc13, arg3
          [  16] new_array          loc10, loc11, 3, 3
          [  22] get_by_id          loc7, loc10, 0
          [  27] new_func_exp       loc9, loc4, 0
          [  31] call               loc7, loc7, 2, 16
          [  37] get_by_val         loc6, loc7, Int32: 1(const0)
          [  42] ret                loc6


Dude, you are generating random numbers inside the loop. That is going to dominate runtime.


I'm not testing runtime, though, I'm testing whether clamp gets optimized.


No. Just things written by web devs.

(Shots fired)


This is also nice because it gracefully handles the case where `max < min`.


It's not always graceful to say that 1 is both less than 0 and greater than 2.


Not sure what you mean. If I want to clamp 1 between 2 and 0, the most reasonable answer is 1, which is correctly returned by this code.


He's saying that, if you have explicitly defined a max and min such that max < min, it is not graceful for the computer to produce a result as though those values were swapped. In other words, garbage in should produce garbage out.

The array implementation sidesteps this by not semantically defining a max and min, instead sorting three arbitrary numbers.


In practice “max” and “min” often aren’t conceptually important for a clamp function. It’s more that you want to keep a value within a range, which is defined by two end points in arbitrary order.

If that is the version of clamp you need, the sort based solution reveals something profound and unexpected: it’s not just the two end points of the range that are equivalent, but all three numbers. Keeping value A between B and C is the same as keeping B between A and C or C between A and B. It’s completely arbitrary which pair you consider to be a range.


Why do you prefer to leave undefined behavior?


I'm not sure what you mean. The behavior of the max() and min() functions is perfectly well defined. The terms "maximum" and "minimum" are well defined. If I were using those terms I would likely consider the case where max < min to be an error, or have some other meaning, like an empty range.

If I wanted it to automatically flip the values to ensure a sensible range is defined, I would probably use "a" and "b" or "endpoint1" and "endpoint2" or something, because "max" has now become "max or min," which is not the same.


It's cute but if you used this in an innerloop (like a game, simulation or graphics code where 'clamp' is used often), it'll generate a ton of garbage as well as potential slowdown for no good reason.


This should be completely obvious, but largely irrelevant to the topic at hand. The linked tweet talks about how difficult it is to remember the order of the terms when you implement clamp a certain way; I just wanted to point out that with a different solution, the order surprisingly doesn’t matter at all.


I don't think it's obvious though - many people have never optimised for garbage collection so I thought it was worth pointing out.

I still found your approach interesting and it's rare there's a perfect approach so don't take it to heart.


I love this one.


never thought of clamp as precompiled sort




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: