1
\$\begingroup\$

I have a super simple function that returns a text label for a range of the input

function fn(amount) {
  if (amount >= 1 && amount <= 10) return 'small'
  if (amount >= 11 && amount <= 20) return 'medium'
  if (amount >= 21 && amount <= 30) return 'large'
}

And this is an "over-engineered" version of the function but I think it scales better


const labelByAmounts = [
  { min: 1, max: 10, label: 'small' },
  { min: 11, max: 20, label: 'medium' },
  { min: 21, max: 30, label: 'large' },
]

function fn2(amount) {
  return labelByAmounts.find(
    (labelByAmount) =>
      amount >= labelByAmount.min && amount <= labelByAmount.max
  ).label
}

The idea is that the second approach is a table-driven method (learned from the book code complete or also called strategy pattern?) which is more modifiable and extensible. But I find the table lookup is still bit awkward since you have to loop through the list and compare the min and max.

Looking for suggestions to:

  1. simplify the lookup process
  2. improve the time complexity if the list grows to have thousands of records? Right I think the time complexity for both approaches is o(1) but I guess there might be ways to improve the constant factor?
  3. improve the readability
  4. maybe a different but better approach?
  5. Lastly, is there a way to benchmark these different variants against large dataset to see which one is the fastest (in a specific environment, i.e. browser)?
\$\endgroup\$
3
  • 1
    \$\begingroup\$ What should happen if you're outside the range? My approach would probably be ['small','medium','large'][ Math.int( (amount-1)/10 ) ]. \$\endgroup\$
    – Teepeemm
    Commented Apr 17, 2022 at 0:39
  • 1
    \$\begingroup\$ Interesting question, but this appears to be missing review context: "generic best practices are outside the scope of this site". \$\endgroup\$
    – ggorlen
    Commented Apr 17, 2022 at 3:26
  • \$\begingroup\$ This is a weird one for code review: the case you actually presented screams that you just go back to your super-clear, super-fast original solution. But in your theoretical "I have thousands of ranges" problem it's impossible to say because now what those ranges are and what classification you actually need from them (and how they're stored) can affect what approaches make sense. Also your assumption about O(1) complexity for both approaches is wrong: both are currently O(n) where n is the number of possible gradations within the range. @Teepeemm's solution is O(1) but inscrutable. \$\endgroup\$
    – dlev
    Commented Apr 21, 2022 at 22:58

1 Answer 1

2
\$\begingroup\$

Test on max only

Or min only, but both is not necessary. Here, I'm coding for "max":

  • Array.find returns on the first element meeting the expression
  • Data must be sorted by max property, ascending
  • min property can be removed from the object
  • table lookup is still bit awkward since you have to loop through the list
    • The data and function should be wrapped as an object with the grunt work encapsulated in clearly named and parametrized (as needed) method(s). Half the benefit of "data-driven" is lost otherwise; yeah, half sounds right.

    const rangeLabels = [
      { min: 1, max: 10, label: 'small' },
      { min: 11, max: 20, label: 'medium' },
      { min: 21, max: 30, label: 'large' },
    ]

    function labelOf (amount) {
      return rangeLabels.find( x => amount <= x.max ).label
    }
    
   console.log( labelOf(13));
   console.log( labelOf(1));
   console.log( labelOf(30)); 
    


Catch out of range values as if part of the data set

const rangeLabels = [
      { min: Number.MIN_SAFE_INTEGER, max: 10, label: 'small' },
      { min: 11, max: 20, label: 'medium' },
      { min: 21, max: Number.MAX_SAFE_INTEGER, label: 'large' },
    ]
\$\endgroup\$

Your Answer

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

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