10
\$\begingroup\$

A challenge many developers face when drawing a graph from scratch to plot some data is generating its ticks. In below graph, there are 6 horizontal ticks (1750, 1800, 1830, 1860, 1880 and 1900) and 11 vertical ticks (0, 10, 20, 30, 40, 50, 60, 70, 80, 90 and 100).

Relative share of world manufacturing output

The horizontal bounds of the above graph goes from 1750 to 1900, which we'll call \$x_{min}\$ and \$x_{max}\$, respectively. For any \$x_{min}\$ and \$x_{max}\$ values, using \$n\$ ticks, there is a minimum multiple \$m\$ of a value \$s\$ that satisfies the following:

  1. \$m \geq x_{min}\$

  2. \$(m + s \times (n - 1)) \leq x_{max}\$; and

  3. \$(m + s \times n) \gt x_{max}\$.

Let's suppose \$x_{min} = 0\$, \$x_{max} = 19\$ and \$n=4\$.

  • Consider \$s=4\$. The minimum multiple of \$s\$ that is greater or equal than \$x_{min}\$ is 0, therefore \$m=0\$. While the second condition is satisfied (\$0 + 4 \times 3 = 12 \leq x_{max}\$), the third condition is not (\$0 + 4 \times 4 = 16 \not \gt x_{max}\$).

  • Consider \$s=5\$. The minimum multiple of \$s\$ that is greater or equal than \$x_{min}\$ is 0, therefore \$m=0\$. Both the second (\$0 + 5 \times 3 = 15 \leq x_{max}\$) and the third (\$0 + 5 \times 4 = 20 \gt x_{max}\$) conditions are satisfied, therefore \$s=5\$.

Note that we need to minimize \$s\$: \$s=6\$ also satisfies all of the conditions, but we'll use \$s=5\$. In addition, since there could be more than one multiple of \$s\$ that satisfies all three conditions, we want also to minimize \$m\$ of the minimum found \$s\$.

Finding \$s\$ allows us to create \$n\$ equally-spaced ticks that can be used for our graph. The sequence goes from \$m\$ to \$m + s \times (n - 1)\$ with step \$s\$, so using \$s=5\$ as in the previous example, the generated ticks are \$\{0, 5, 10, 15\}\$. These ticks also gives the impression that our graph is sliding if we scale those properly, since they will only change if \$x_{min}\$ is a multiple of \$s\$. Below a gif that illustrates the generated ticks for \$(x_{min}, x_{max})\$ going from (0, 19) to (10, 29), with \$n=4\$.

gif

Another way of thinking about this problem is generating the ticks beforehand. Let's suppose \$x_{min} = 4\$, \$x_{max} = 21\$ and \$n=3\$.

  • Consider \$s=5\$. This would generate the ticks \$\{5, 10, 15\}\$ and the next tick would be 20, which is not greater than \$x_{max}\$ (breaking the third condition). Note that here, \$m=5\$ since it's the first multiple \$m\$ of \$s\$ such that \$m \geq x_{min}\$.

  • Consider \$s=6\$. This would generate the ticks \$\{6, 12, 18\}\$ and the next tick would be 24, which is greater than \$x_{max}\$ (satisfying all conditions). We could also use \$s = 7\$, but we want to minimize this value.

Input

  • Two real numbers min and max representing the minimum and the maximum values of a range, where max is greater than min; and one positive integer (num) representing the number of ticks.

The input is guaranteed to generate an integer s, so you should not handle min = 0, max = 4, and num = 3 for example.

Output

  • A sequence of equally-spaced numbers with length num representing the ticks of a graph based on the previous explanation.

Test cases

# from gif
0, 19, 4 -> [0, 5, 10, 15]
1, 20, 4 -> [5, 10, 15, 20]
2, 21, 4 -> [5, 10, 15, 20]
3, 22, 4 -> [5, 10, 15, 20]
4, 23, 4 -> [5, 10, 15, 20]
5, 24, 4 -> [5, 10, 15, 20]
6, 25, 4 -> [10, 15, 20, 25]
7, 26, 4 -> [10, 15, 20, 25]
8, 27, 4 -> [10, 15, 20, 25]
9, 28, 4 -> [10, 15, 20, 25]
10, 29, 4 -> [10, 15, 20, 25]
11, 30, 4 -> [15, 20, 25, 30]

# m can be negative
-10, 1, 3 -> [-8, -4, 0]
-9, 2, 3 -> [-8, -4, 0]
-8, 3, 3 -> [-8, -4, 0]
-7, 4, 3 -> [-4, 0, 4]
-6, 5, 3 -> [-4, 0, 4]
-5, 6, 3 -> [-4, 0, 4]
-4, 7, 3 -> [-4, 0, 4]
-3, 8, 3 -> [0, 4, 8]
-2, 9, 3 -> [0, 4, 8]
-1, 10, 3 -> [0, 4, 8]

# num can be 1
0, 3, 1 -> [0]
1, 4, 1 -> [3]
2, 5, 1 -> [3]
3, 6, 1 -> [4]
4, 7, 1 -> [6]
5, 8, 1 -> [6]
6, 9, 1 -> [8]
7, 10, 1 -> [9]
8, 11, 1 -> [9]
9, 12, 1 -> [12]

This is , so shortest code in bytes wins. Default I/O methods apply.

\$\endgroup\$
12
  • 2
    \$\begingroup\$ If s were restricted to integers, this would all make sense, but if s can be rational I'm very confused how it is determined. For example, in the test case 0,4,3, why is s=5/3 chosen, when, say s=3/2 is smaller and would also satisfy all conditions? (In fact, there is no minimum value of s which satisfies that test case since any s in the half-open interval (4/3,2] would work). I'd recommend requiring s to be an integer to make this challenge well-defined. \$\endgroup\$ – kops May 1 at 18:42
  • 2
    \$\begingroup\$ Love the visuals! \$\endgroup\$ – Jonah May 1 at 18:43
  • 1
    \$\begingroup\$ @JonathanAllan Yes, those would be the correct ticks. The first graph is just to illustrate what a tick is. \$\endgroup\$ – enzo May 2 at 17:39
  • 1
    \$\begingroup\$ In the 2nd to last test case, eg, -- 8, 11, 1 -> [9] -- would 8 be a valid result us well, with assumed step size 4. 1. 8 <= 8, check. 2. 8 + 4*(1-1) = 8, which is less than 11. Check. 3. 8 + 4 = 12, which is greater than 11. Check. \$\endgroup\$ – Jonah May 2 at 19:02
  • 1
    \$\begingroup\$ Indeed, by "there is a minimum multiple 𝑚 of a value 𝑠 that satisfies the following", shouldn't the 9 (the expected output) be incorrect, since it is not minimal? 8 is lower while still satisfying the 3 conditions? Is there a constraint I'm missing? \$\endgroup\$ – Jonah May 2 at 19:08
4
\$\begingroup\$

Charcoal, 41 bytes

NθNηNζ≔¹ε≔…·⌈θηδW⁻Lδζ«≦⊕ε≔…·⌈∕θε∕ηεδ»I×δε

Try it online! Link is to verbose version of code. Explanation: I tried and failed to find some sort of formula, so brute force it is.

NθNηNζ

Input the minimum, maximum and ticks.

≔¹ε≔…·⌈θηδ

Start with a step of 1 and ticks from the ceiling of the minimum to (the floor of) the maximum.

W⁻Lδζ«

Repeat until we get the desired number of ticks.

≦⊕ε

Increment the step.

≔…·⌈∕θε∕ηεδ

Divide the ends by the new step size and list the integers in that range.

»I×δε

Multiply those by the step to get the desired output.

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

J, 30 28 38 bytes

1 :'](]*i.@u+>.@%),(]+0 0-:|~)u>.@%~-'

Try it online!

This is a J adverb modifing n, the number of ticks, and taking the max and min as left and right args, respectively. Called like:

19 (4 f) 0
  • u>.@%~- Calculates step size by dividing n into the difference between the max and min.
    • ,(]+0 0-:|~) Adds one to the step size if the step size evenly divides both endpoints.
  • ]* The step size times...
    • i.@u Produces range 0..n-1
    • + Add elementwise to...
    • >.@% The ceiling of >.@ the min divided by the step size % -- adjusts so the first tick starts where we want.
\$\endgroup\$
5
  • 1
    \$\begingroup\$ I think this produces the wrong output for 4 steps from 4 to 20 - I suspect the correct answer is 5, 10, 15, 20 but this gives 4, 8, 12, 16, which leaves room for a fifth step. \$\endgroup\$ – Neil May 2 at 11:47
  • \$\begingroup\$ @Neil After clarification with OP, I've fixed the issue and this now passes all test cases. Thanks again. \$\endgroup\$ – Jonah May 2 at 21:31
  • \$\begingroup\$ I didn't think to check before but I noticed that you don't always minimise s when you could e.g. 4 ticks from 17 to 55 should be 24, 32, 40, 48 but you output 20, 30, 40, 50. \$\endgroup\$ – Neil May 3 at 0:11
  • \$\begingroup\$ Ah, nice spot. I can just try all s ofc if need be, but I'd love to know if there's still a closed form way to solve this. It amounts to solving modular equations in x of the form C <= (((A mod x) + B)/x) < C+1. Do you happen to know if this is possible? \$\endgroup\$ – Jonah May 3 at 5:04
  • \$\begingroup\$ I have no idea sorry, I couldn't find a better way than just trying all values of s. \$\endgroup\$ – Neil May 3 at 9:35
3
\$\begingroup\$

R, 45 69 59 bytes

Edit: +24 +14 bytes to fix bug pointed-out by Neil & ophact

function(l,h,n){while((t=-l%/%T+1)+h%/%T-n)T=T+1
T*(1:n-t)}

Try it online!

Ungolfed version

ticks=
function(l,h,n){        # l=low, h=high, n=n ticks
  d=1:(h-l+1)           # d=range of possible values of s to try
  s=match(n,(floor(h/d)-ceiling(l/d)+1))
                        # calculate s by trying each value of d
                        # until floor(h/d)-ceiling(l/d)+1 is equal to n
                        # (golfed: match(n,h%/%d+-l%/%d+1))
  a=ceiling(l/s)*s      # calculate a=start value (golfed: --l%/%s*s)
  return(a+(1:n-1)*s)   # output ticks (golfed: s*(1:n-1--l%/%s))
}
\$\endgroup\$
4
  • 2
    \$\begingroup\$ I don't think this minimises the step, e.g. 17 to 55 in 4 steps can be done with multiples of 8. \$\endgroup\$ – Neil May 2 at 12:18
  • \$\begingroup\$ I'm not sure floor((h-1)/n)+1 is a correct formula for s. It is not always the "optimum" value. \$\endgroup\$ – Recursive Co. May 2 at 13:09
  • \$\begingroup\$ @Neil & ophact - Thanks for spotting that! I was over-reliant on the original test-cases (that have now been fixed), and should have thought-through the spec better. I think it should be fixed now... \$\endgroup\$ – Dominic van Essen May 2 at 21:19
  • \$\begingroup\$ @ophact - Thanks: see comment above (& please let me know if you find another bug...) \$\endgroup\$ – Dominic van Essen May 2 at 21:20
2
\$\begingroup\$

Jelly, 13 bytes

r/ḍ@ƇⱮJ$L⁼¥ƇḢ

A dyadic Link accepting a pair of numbers, [xmin, xmax], on the left and an integer, n, on the right that yields a list of ticks.

Try it online! Or see the test-suite.

How?

r/ḍ@ƇⱮJ$L⁼¥ƇḢ - Link: list, bounds; integer, n:
 /            - reduce (bounds) with:
r             -   inclusive range -> [xmin..xmax]
       $      - last two links as a monad - f([xmin..xmax]):
      J       -   range of length -> [1..(xmax-xmin)+1]
     Ɱ        -   map across that with:
    Ƈ         -     filter keep those for which:
   @          -       with swapped arguments:
  ḍ           -         divides?
           Ƈ  - filter keep those for which:
          ¥   -   last two links as a dyad - f(potential tick list, n):
        L     -     length (potential tick list)
         ⁼    -     equals (n)?
            Ḣ - head
\$\endgroup\$
2
\$\begingroup\$

Haskell, 63 bytes

(x#y)n=[[i|i<-[x..y],mod i s<1]|s<-[1..],div y s+div(-x)s<n]!!0

Try it online!

The function (#) takes the minimum value x, the maximum value y and the number of ticks n.

How?

The problem is equivalent to finding $$ \min\left\{s\ge 1:\left\lfloor\frac{x_{\text{max}}}{s}\right\rfloor-\left\lceil\frac{x_{\text{min}}}{s}\right\rceil=n-1\right\}. $$

Since we are guaranteed that an answer exists, it is sufficient to find $$ \min\left\{s\ge 1:\left\lfloor\frac{x_{\text{max}}}{s}\right\rfloor-\left\lceil\frac{x_{\text{min}}}{s}\right\rceil<n\right\}. $$

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

JavaScript, 99 97 bytes

(a,b,n)=>(g=s=>(x=s*Math.ceil(a/s))+s*(n-1)<=b&x+s*n>b?[...Array(n)].map((_,i)=>x+s*i):g(s+1))(0)

I will golf this later; could not find a suitable replacement for Math.ceil.

-2 bytes thanks to my own golfing efforts.

Explanation.

This is an implementation which simply follows what is asked in the question. The function is recursive and will call itself with s+1 if the conditions are not satisfied.

commented

(a,b,n)                         // declare parameters: input a, b, n
=>                              // start of arrow function expression
(g=                             // assign function expression to g
s=>                             // declare parameters: s
(x=                             // assignment expression: assign value to identifier x
s*Math.ceil(a/s))               // s multiplied by ceiling of a over s
+s*(n-1)<=b                     // speaks for itself
&                               // bitwise and (saves one byte)
x+s*n>b                         // speaks for itself
?                               // if above conditions satisfied, return
[...Array(n)].map((_,i)=>x+s*i) // array of n elements each filled in with x + s times i
:                               // otherwise return
g(s+1)                          // call with s+1
)                               // closing bracket
(0)                             // IIFE: initial call with s=0.

And "fun fact": when s is 0, we get NaN (Infinity * 0) as the value for x, which makes it quietly fail. However, this is not noticed as the function increments s due to unsatisfied conditions.

\$\endgroup\$
1
  • \$\begingroup\$ ... well you could always start with s=1, could you not? \$\endgroup\$ – Neil May 2 at 19:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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