9
\$\begingroup\$

Sandbox post

In this challenge, you are provided with a series of y values at even x-values as well as a console width and height. The desired output is an ASCII-art representation of a ribbon plot of the y-values scaled to the desired size. If all values are positive or negative, the x-axis should be displayed at the bottom or top respectively. Otherwise, the x-axis should be displayed at the appropriate position with the graph extending to the top and bottom.

The data should be scaled horizontally by splitting the data into sublists of equal (+/- 1) size and taking the arithmetic mean of each sublist. There will never be fewer values than columns, but can be more.

Inspired by this question which was posed as a tips in C question. Most similar question is here but there are some important differences in terms of input (floats versus equation) and output style.

Default loopholes apply.

Input/output

  • Default I/O rules apply.
  • Default loopholes forbidden.
  • The y-values will be a list, array or equivalent of floating point numbers at least as long as the width of the console. These may be positive or negative. Choice of float type is at the discretion of the answerer. Y values should be rounded to the nearest integer after scaling to the relevant size; the default rounding method for 0.5s supported by the language is fine (rounding to even, rounding away from zero, etc.) (See Wikipedia for a discussion of different rounding methods if this is an unfamiliar topic.) However, truncation/floor/ceiling are not permitted approaches.
  • The range of y values will always be non-zero (i.e. there will be more than 1 y value, and there will be at least 2 different values).
  • The console width and height will be two integers, with the height always at least 2.
  • The output can be a return value or be output to STDOUT. If opting for a return value, this can be a single newline-separated string, a list of strings, a list of lists of characters or a matrix of characters.
  • Any distinct character can be chosen for the filled area, but the blank area should be a space character.
  • A separate non-space character should be used to indicate the x-axis.
  • Trailing space on each line is optional.

Worked example of how the data is scaled to width

Floats: 1.0,2.0,3.0,4.0,5.0
Width: 3

Split into three pieces: [1,2],[3,4],[5]
Take arithmetic mean of each: [1.5,3.5,5]

Examples of full input/output

Input

Floats: [1.4330127018922192, 1.4740546219716173, 1.46726631732171, 1.4118917879852302, 1.3095749615197134, 1.1642846441770978, 0.98209964435211, 0.770867389064186, 0.5397574401179837, 0.29873801741462563, 0.05800851721072442, -0.17257624372014382, -0.38405389828888165, -0.5688402657187754, -0.7211228325469043, -0.8371379250265838, -0.915315134687127, -0.9562812209887335, -0.9627248583907679, -0.939132622452629, -0.8914149315594007, -0.8264477348143321, -0.7515611018951998, -0.6740091786833334, -0.6004570095761375, -0.5365184287338843, -0.48637567624364625, -0.45250583664959154, -0.4355319935803172, -0.43420862005149424, -0.445541730745404, -0.46503530191616943, -0.4870470099638535, -0.5052290073886498, -0.5130237273726901, -0.5041809629881258, -0.47326095136808916, -0.41608900827923856, -0.330130352731954, -0.21475893339455088, -0.07140098112303239, 0.09645778693148394, 0.28340343297327375, 0.4823565362517651, 0.6849507176587676, 0.8820035800170399, 1.0640505126834876, 1.2219075413197402, 1.3472273300168627, 1.433012701892219]
Width: 24
Height: 24

Output:

#                       
#                      #
##                     #
##                    ##
##                    ##
##                    ##
###                   ##
###                  ###
###                  ###
###                  ###
####                ####
####                ####
####                ####
####                ####
------------------------
    ###############     
     ##############     
     ##############     
     #############      
     #######   ##       
      #####             
      #####             
       ###              
       ##               

Input

Floats: [0.5, 3, 7.5, 14, 22.5, 33, 45.5, 60, 76.5, 95, 115.5, 138, 162.5, 189, 217.5, 248, 280.5, 315, 351.5, 390, 430.5, 473, 517.5, 564, 612.5, 663, 715.5, 770, 826.5, 885, 945.5, 1008, 1072.5, 1139, 1207.5, 1278, 1350.5, 1425, 1501.5, 1580, 1660.5, 1743, 1827.5, 1914, 2002.5, 2093, 2185.5, 2280, 2376.5, 2475]
Width: 24
Height: 24

Output

                       #
                       #
                      ##
                      ##
                     ###
                    ####
                    ####
                   #####
                  ######
                  ######
                 #######
                ########
               #########
               #########
              ##########
             ###########
            ############
           #############
          ##############
        ################
       #################
     ###################
  ######################
------------------------

Input

Floats: [-4.0,0.0,0.0,4.0,2.0]
Width: 5
Height: 5

Output

   # 
   ##
-----
#    
#    

Input

Floats: [0.0,1.0,2.0,3.0]
Width: 4
Height: 4

Output

    #
   ##
  ###
 ----
\$\endgroup\$
8
  • \$\begingroup\$ @JonathanAllan The input isn't sorted in the first example, nor in the third example. I've provided a worked example of how the splitting of data should work, but in Jelly it would be equivalent to œsÆm€ where the left argument is the floats and the right argument the width. \$\endgroup\$ Dec 11, 2019 at 18:11
  • \$\begingroup\$ Ahha the example makes sense. Thanks. \$\endgroup\$ Dec 11, 2019 at 18:23
  • \$\begingroup\$ I may be missing something but judging by the example with only positive values it looks like maybe you're flooring rather than rounding (I see un-rounded, normalised values like [0.036, 0.229, 0.522, 0.848, 1.254, 1.739, 2.303, 2.946, 3.668, 4.47, 5.35, 6.31, 7.349, 8.467, 9.664, 10.94, 12.296, 13.73, 15.244, 16.837, 18.509, 20.26, 22.09, 24.0]) \$\endgroup\$ Dec 11, 2019 at 19:07
  • \$\begingroup\$ @JonathanAllan thanks, fixed, I think. Could you check please? \$\endgroup\$ Dec 11, 2019 at 19:25
  • \$\begingroup\$ It would be great if anyone who downvotes could post a comment to explain why; I’ve answered lots of questions here but this my first attempt at asking one! \$\endgroup\$ Dec 11, 2019 at 19:59

5 Answers 5

3
\$\begingroup\$

Jelly,  40  33 bytes

-7 thanks to NickKennedy!

œsÆm÷_ṂṀƊ$×⁵’¤ær0r0FṀ‘,_¥ƲṬSz0o⁶Y

A full program printing the result to STDOUT with 1s for bars and 2s for the x-axis.

Try it online!

How?

œsÆm÷_ṂṀƊ$×⁵’¤ær0r0FṀ‘,_¥ƲṬSz0o⁶Y - Main Link: values, width
œs                                - split (values) into (width) chunks
  Æm                              - arithmetic mean (vectorises)
         $                        - last two links as a monad:
        Ɗ                         -   last three links as a monad:
      Ṃ                           -     minimum
     _                            -     (left) subtract (minimum) (vectorises)
       Ṁ                          -     maximum
    ÷                             -   divide (vectorises)
             ¤                    - nilad followed by link(s) as a nilad:
           ⁵                      -   third program argument (height)
            ’                     -   decrement
          ×                       - mutiply (vectorises)
              ær0                 - round to 0 decimal places (vectorises)
                 r0               - inclusive range to zero (vectorises)
                         Ʋ        - last four links as a monad:
                   F              -   flatten
                    Ṁ             -   maximum
                     ‘            -   increment
                        ¥         -   last two links as a monad:
                       _          -     subtract (vectorises)
                      ,           -     pair
                          Ṭ       - untruth (vectorises)  (e.g. 3 -> [0,0,1])
                           S      - sum (vectorises)
                            z0    - transpose with filler zero
                              o⁶  - logical OR with a space character (i.e. replace zeros)
                                Y - join with newline characters
                                  - implicit, smashing print
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Nice. I’ll allow the leading whitespace line. \$\endgroup\$ Dec 11, 2019 at 20:33
  • \$\begingroup\$ Is it ok to help improve an answer to my own question? If so, you can save 7 bytes using an alternative second half: tio.run/##AVwAo/9qZWxsef//… \$\endgroup\$ Dec 13, 2019 at 9:37
  • \$\begingroup\$ I had planned to come back to this as per the "I feel like this is probably a little golf-able (what with all the Zzs)...", that's why I had not written up a code break-down either. Looks like you've done it for me; It's a really nice way to go about it - thanks! \$\endgroup\$ Dec 13, 2019 at 13:27
2
\$\begingroup\$

05AB1E, 41 bytes

äÅAWUZX-I/I<LR*X+©δ@øD®dÏs®d_Ï_¹Å8s)J˜0ð:

Inputs in the order width, [floats], height.
Outputs as a list of lines with 1 for the bars and 8 for the x-axis.
Also doesn't round at all for more accurate scaling (similar output as the Jelly answer, which differs from the test cases for some bars).

Try it online or verify all test cases. (Uses » in the footer to join the resulting list of lines by newlines to pretty-print it. The test suite will have the input-order as height, width, [floats] instead.)

Explanation:

ä                # Split the second (implicit) input-list `floats` into the first (implicit)
                 # input-integer `width` amount of equal-sized parts
 ÅA              # Take the arithmetic mean of every inner sublist
   W             # Push the minimum of this list (without popping the list itself)
    U            # Pop and store it in variable `X`
   Z             # Push the maximum of this list (again without popping the list itself)
    X-           # Subtract the minimum `X` from it
      I/         # Divide it by the third input `height`
        I<L      # Push a list in the range [1, `height`-1]
           R     # Reverse it to [`height`-1, 1]
            *    # Multiply each value by the (max-min)/height we calculated
             X+  # And add the minimum `X`
               © # Store this list of y-axis steps in variable `®` (without popping)
   δ             # Apply double-vectorized on the arithmetic mean and y-step lists:
    @            #  Check >= among the two
     ø           # Zip/transpose this matrix (swapping rows/columns)
      D          # Duplicate it
       ®         # Push the y-steps again from variable `®`
        d        # Check for each whether its non-negative (>= 0)
         Ï       # And only leave those inner sublists of the matrix (top part)
      s          # Swap to get the duplicated list again
       ®d_Ï      # Do the same, but this time for all negative values (bottom part)
           _     # Invert each (0 becomes 1; everything else becomes 0)
      ¹Å8        # Push a list with the first input `width` amount of 8s
         s       # Swap so the stack order is: top-part, x-axis, bottom-part
          )      # Wrap these three into a list
           J     # Join each inner(-most) list of digits to a single string
            ˜    # Flatten it to a single list of string lines
             0ð: # And replace all 0s with spaces
                 # (after which the resulting list of lines is output implicitly as result)
\$\endgroup\$
3
  • 1
    \$\begingroup\$ @Grimmy Although it works for most test cases, I'm afraid it fails for the second: current answer vs your suggestion. Your split-by method will only result in a single inner list instead of two when the bars are only positive or only negative. \$\endgroup\$ Dec 18, 2019 at 13:20
  • \$\begingroup\$ Actually, your answer has incorrect rounding as well. You round away from 0, while the spec requires rounding to nearest integer (only the x.5 case is allowed to round either way). I posted my approach as a separate answer, since it's different enough. \$\endgroup\$
    – Grimmy
    Dec 18, 2019 at 17:35
  • \$\begingroup\$ @Grimmy I'm indeed aware the test cases are slightly different than the one in the challenge description. I actually left a comment about that on the challenge as well. The Jelly answer has (or maybe had - haven't checked his latest version) the same issues as my answer by not rounding at all and simply using the floats for the checks. Regardless, nice 26 byter! :) \$\endgroup\$ Dec 18, 2019 at 17:57
2
\$\begingroup\$

Haskell, 324 312 298 bytes

import Data.List
l=genericLength
(!)=replicate
0#_=[]
w#x=take n x:(w-1)#(drop n x)where n=ceiling(l x/w)
d x=transpose$(++).(!'#')<*>(!' ').(maximum x-).max 0<$>x
w?h=((++).reverse.d<*>((round w)!'-':).d.map(*(-1))).(\x->round.(*(((h-1)/).((-).maximum<*>minimum).(0:))x)<$>x).map((/).sum<*>l).(w#)

Try it online!

A lot longer than the other answers, but I would already be happy to get it under 300 bytes. It also gives a slightly different result for test-case 2 (the 16th column is 11 high instead of 10), but I'm not sure how to fix that. I guess it's the difference in rounding methods? Haskell rounds 0.5 to even as far as I know.

\$\endgroup\$
2
\$\begingroup\$

05AB1E, 30 26 bytes

äÅAÐδ-àI<//òÝMαεðIиāΘyǝ}øJ

Try it online! or validate all test cases.

# scale and round the values
ä                # split the list of values in width sublists
 ÅA              # arithmetic mean of each sublist
   Ð             # triplicate
    δ-           # double-vectorized subtraction
      à          # maximum
       I</       # divide by (height - 1)
          /      # divide the list by the result
           ò     # round to nearest integer

# draw the plot
M                # maximum of the stack
 α               # absolute difference of each value with this max
  ε       }      # for each difference y:
   ðIи           #  a space character, repeated width times
      āΘ         #  list [1, 0, 0, 0, ...] (width elements)
        yǝ       #  replace characters at the indices in y
           ø     # transpose
            J    # join
\$\endgroup\$
1
\$\begingroup\$

Charcoal, 49 bytes

⊞θ⁰≧×∕⊖ζ⁻⌈θ⌊θθ⊟θFη«≔÷Lθ⁻ηιι≦∕ΣEι⊟θιP↑⁺⊘³ιP↓⁻⊘³ι←¹

Try it online! Link is to verbose version of code. Uses | as the drawing character. I'm not sure I've got all of the desired calculations, but I am now using the latest array splitting, which allowed me to save 9 bytes. Explanation:

⊞θ⁰≧×∕⊖ζ⁻⌈θ⌊θθ⊟θ

Temporarily add a 0 to the array so that the values can be scaled to the desired height.

Fη«

Loop over the width. Although this loop counts up, the array is actually processed from right-to-left.

≔÷Lθ⁻ηιι

Calculate how many array elements to pop.

≦∕ΣEι⊟θι

Pop that many elements and calculate the mean.

P↑⁺⊘³ιP↓⁻⊘³ι←¹

Print the desired amount upwards or downwards as applicable, then print the X-axis in a leftward direction.

\$\endgroup\$

Your Answer

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

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