23
\$\begingroup\$

If you've ever tried adding labels to a really dense plot, then you'll realise that sometimes labels will overlap one another, making them hard to read. We're going to do something similar but in 1D.

Input will be a sequence of (label, x-coordinate) pairs, and output will be the result of drawing each point and label, in the given order. An asterisk * representing the point should be placed at the given x-coordinate and the label should follow. Any existing characters will be overwritten.

For instance, if the input was

Hello  0
World  8
Fizz   3
Buzz   5
PPCG   16
X      9

Then the following would happen:

*Hello
*Hello  *World
*He*Fizz*World
*He*F*Buzzorld
*He*F*Buzzorld  *PPCG  
*He*F*Buz*Xrld  *PPCG

The final line should then be outputted.

I/O rules

  • Input may consist of any number of pairs. Each label will only consist of uppercase and lowercase letters, and label lengths will be at most 127 chars. Each x-coordinate will be between 0 and 127 inclusive.

  • Input may be in any convenient list or string format such that the pairs are unambiguous and labels/x-coordinates alternate in the input. For example, a format like [("Hello", 0), ("World", 8) ...] or [0 "Hello" 8 "World" ...] is fine. However, you may not assume two separate lists of labels and x-coordinates.

  • Functions and full programs are both okay.

  • Any spots not covered by a label should be represented with a space. However, there may not be any extraneous leading or trailing whitespace aside from a single optional trailing newline.

Examples

Input:

OneLabel   10

Output:

          *OneLabel

Input:

Heathrow   0
Edinburgh  2
London     4
Liverpool  6
Oxford     8

Output:

*H*E*L*L*Oxfordl

Input:

alpha     20
beta       4
gamma     57
delta      3
epsilon   22
zeta      32
eta       53
theta     27

Output:

   *delta           *a*epsi*thetazeta                *eta*gamma

Input:

abc  5
d    5
abc  10
ABCDEFGHIJKLMNOPQRSTUVWXYZ 127

Output:

     *dbc *abc                                                                                                                 *ABCDEFGHIJKLMNOPQRSTUVWXYZ

Note that labels and/or x-coordinates may be repeated.

\$\endgroup\$
  • \$\begingroup\$ If the x coords are [0,127] and the strings are (0,127], can a label run off the far right end of the line, or is it protected? That is, does "foo 127" end the line with "*" or "*foo"? Just checking if the string should have a soft or hard ending. \$\endgroup\$ – PotatoOmeletteSandwich Oct 19 '15 at 13:25
  • 3
    \$\begingroup\$ @PotatoOmeletteSandwich My intention was so that the total length fits within 255, so the maximal output length will occur when there is a length 127 label at x-coordinate 127. The final output should not be truncated in any way, except to remove trailing whitespace. \$\endgroup\$ – Sp3000 Oct 19 '15 at 13:28

10 Answers 10

7
\$\begingroup\$

CJam, 24 23 19 bytes

l~Sf.*'*f*:.{S^+1=}

This reads the input as a CJam array of coordinate-label pairs.

Try this fiddle in the CJam interpreter or verify all test cases at once.

Thanks to @MartinBüttner for helping me save 4 bytes!

How it works

l~                   Read a line from STDIN and evaluate it.
  Sf                 For each pair, push the pair and " "; then:
    .*                 Perform vectorized repetition.
                         [X "label"] " " .* -> [(X spaces) "label"]
      '*f*           Join each resulting pair, using '*' as separator.
          :.{     }  Reduce by the following vectorized operator:
                       Push two characters (A and B).
             S^        Compute the symmetric difference of B and " ".
                       This pushes "B " for a non-space B and "" otherwise.
                +1=    Append and select the second character (with wrap).
                       This selects B for "AB " and A for "A".
\$\endgroup\$
  • 2
    \$\begingroup\$ I just added a test case and thought I'd leave a comment saying that no, it didn't break this submission - CJam's interpreter output just word wraps. Just in case anybody gets confused. \$\endgroup\$ – Sp3000 Oct 19 '15 at 13:37
4
\$\begingroup\$

Pyth, 20 bytes

V.Tmrj" *"d9Qpe+d-Nd

Try it online: Demonstration or Test Suite

Explanation

V.Tmrj" *"d9Qpe+d-Nd
   m        Q         map each pair d of the input to:
     j" *"d             join d by the string " *"
    r      9            range-length encode 
                        (this gives x-coordinate spaces, a star and the label)
 .T                   transpose this table 
V                     for N in ^:
                 -Nd    remove spaces from N
               +d       add a space at the beginning
              e         take the last character
             p          and print it (without newline)
\$\endgroup\$
  • 1
    \$\begingroup\$ This is way better than what I had. \$\endgroup\$ – isaacg Oct 19 '15 at 20:43
4
\$\begingroup\$

JavaScript ES6, 104 bytes

c=>(a=Array(255).fill(" "))&&c.map(([u,v])=>a.splice(u,v.length+1,..."*"+v))&&a.join``.replace(/ +$/,"")

Example Usage

Input into a compliant console:

t = [[0,"Hello"],[8,"World"],[3,"Fizz"],[5,"Buzz"],[16,"PPCG"],[9,"X"]];
(c=>(a=Array(255).fill(" "))&&c.map(([u,v])=>a.splice(u,v.length+1,..."*"+v))&&a.join``.replace(/ +$/,""))(t);

Output from the last statement:

"*He*F*Buz*Xrld  *PPCG"

Explanation

This creates an anonymous function from c to three expressions that have been logically-ANDed together. The first two statements are always truthy, and the JS short-circuit rules say that whenever the first is truthy, return the entire value on the right hand side (without coercing to boolean): so this is formally equivalent to

(function (c) {
    a = Array(255).fill(" ");                    // global variable `a` overwritten
    c.map(function (x) {                         // only side-effects are used here.
       var u = x[0], v = x[1];                   // ES6 destructuring
       a.splice(u, v.length + 1, ..."*" + v));   // main logic
    });
    return a.join("").replace(/ +$/, "");        // postprocessing and trim
})

The first statement has to be wrapped in parentheses above because the assignment operator = has lower precedence than the logical-AND operator &&.

The "rest parameter" assembly ..."*"+v is also part of ES6; it concatenates a leading * to the string and then interprets it as a list-like parameter, splitting it into a bunch of arguments which are provided to Array.prototype.splice, which takes (m, n, ...rest) and modifies its array at position m to remove n elements and then insert all of the rest arguments. To accomplish this before ES6 you would use the more cumbersome:

[].slice.apply(a, [u, v.length + 1].concat(("*" + v).split("")))

The array then gets concatenated with the empty string and trailing whitespace is deleted.

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

Python 2, 67 bytes

z=''
for a,b in input():z=(z+' '*b)[:b]+'*'+a+z[len(a)-~b:]
print z

Takes input like [('Heathrow', 0), ('Edinburgh', 2), ('London', 4), ('Liverpool', 6), ('Oxford', 8)] and prints the result.

Python doesn't allow strings to be modified, and converting to and from a list is expensive. So, this recreates the string z to add in a new word. We take the b characters before the word, padding with spaces if needed, then the new text with an asterisk, then the part of z after the new word. Note that trailing spaces are never added.

The reduce version is 3 chars longer (70):

lambda I:reduce(lambda z,(a,b):(z+' '*b)[:b]+'*'+a+z[len(a)-~b:],I,"")
\$\endgroup\$
3
\$\begingroup\$

Ruby, 94 81 75 bytes

Golfed:

s=" "*128;$<.map{|l|w,p=l.split;p=p.to_i;s[p..w.size+p]="*"+w};$><<s.rstrip

Here's the ungolfed code:

s = " "*128
$<.map{|l|                 # for each line entered via stdin, ctrl+D to stop
  w,p = l.split            # had to move the chomp down here
  p = p.to_i               # there's no 'to_i!'...
  s[p..w.size+p] = "*"+w   # in the range of *foobar, replace the string
}
$><<s.rstrip               # output suggested by w0lf

Thanks @w0lf for the suggestions on mapping the input!

Thanks @w0lf and @Not that Charles for the thought on removing a variable.

\$\endgroup\$
  • \$\begingroup\$ See the Ruby golfing tips. In this case you could apply the $<.map{|l|...} is shorter than while l=gets;...;end tip and probably replace puts with $><< (which does not require the extra space). \$\endgroup\$ – Cristian Lupascu Oct 19 '15 at 14:25
  • \$\begingroup\$ also, I think .chomp can be removed. \$\endgroup\$ – Cristian Lupascu Oct 19 '15 at 14:32
  • \$\begingroup\$ In this case, now that you mention it, I think it's likely very safe to remove, as .to_i will catch that. Good thought. Thanks @w0lf! \$\endgroup\$ – PotatoOmeletteSandwich Oct 19 '15 at 14:34
  • \$\begingroup\$ You're welcome! Here's a shorter version in which I applied the tips above and a few more: ideone.com/BiOvV5. Feel free to post it in your answer if you like it. \$\endgroup\$ – Cristian Lupascu Oct 19 '15 at 14:38
  • 3
    \$\begingroup\$ @PotatoOmeletteSandwich Upgrade your Ruby. 1.8.7 is end-of-lifed! also, you should be able to use the s[int, int] form instead of s[range] for 1 char savings. \$\endgroup\$ – Not that Charles Oct 19 '15 at 16:04
3
\$\begingroup\$

Javascript 121 chars

Using non-standard features, works on Firefox.
x=Array(255).fill(" ");eval(prompt()).map(s=>{s[0].split``.map((n,i)=>x[s[1]+i+1]=n);x[s[1]]="*"});x=x.join``.trimRight()

Older version: x=Array(255).fill(" ");eval(prompt()).map(s=>{s[0].split``.map((n,i)=>x[s[1]+i+1]=n);x[s[1]]="*"});x=x.join``.replace(/ +$/,"")

x=Array(255).fill(" ");      //Creates an array with spaces
eval(prompt())               //Gets some input, has to look like [["Hello",4],["Hi",14],["Oi",0]]
.map(s=>{s[0].split``.map((n,i)=>x[s[1]+i+1]=n);x[s[1]]="*"}); //Main "logic"
x=x.join``.replace(/ +$/,"") //Gets rid of the trailing spaces
\$\endgroup\$
  • 1
    \$\begingroup\$ / +/ makes much more sense than \s disappointed I missed that! Could you save bytes using x=' '.repeat(255); and avoiding the .join? \$\endgroup\$ – Dom Hastings Oct 19 '15 at 19:21
  • 1
    \$\begingroup\$ @DomHastings: JS strings are immutable so you would have to .split('') it into a mutable data structure, but at that point Array(255).fill(' ') is shorter. In my version most of my savings come from (a) using the "you can give either a function or program" rule to delete eval(prompt()) in exchange for c=> and (b) using the builtin Array.prototype.slice method with a rest parameter to shorten the logic part a little bit. \$\endgroup\$ – CR Drost Oct 19 '15 at 20:07
  • 1
    \$\begingroup\$ @ChrisDrost ah of course... I forget it's just an accessor! Shame[].map.call(s[0], doesn't save any either... \$\endgroup\$ – Dom Hastings Oct 19 '15 at 20:24
2
\$\begingroup\$

Python, 85 bytes

def g(p):
 z=[' ']*256
 for a,b in p:z[b:b+len(a)+1]='*'+a
 return''.join(z).rstrip()

Try it online

\$\endgroup\$
  • 1
    \$\begingroup\$ You should be able to do 'z'[2::5] (backticks instead of apostrophes) instead of ''.join(z) to save one byte, and moving z=[' ']*256 to the parameters should save another. Also, I think you can change return to print. \$\endgroup\$ – Kade Oct 19 '15 at 15:59
  • \$\begingroup\$ I think you can save chars by writing a program with p=input() (Python 2) rather than a function, which avoids indentation. Also, b+len(a)+1 can be b-~len(a) \$\endgroup\$ – xnor Oct 19 '15 at 19:27
  • 1
    \$\begingroup\$ Actually, a program just lets you do for a,b in input():. \$\endgroup\$ – xnor Oct 19 '15 at 19:41
2
\$\begingroup\$

Perl, 66 bytes

63 bytes script + 3 bytes for -p

$}||=$"x128;/\s+/,substr$},$',1+length$`,"*$`"}{$_=$};s/\s+$/
/

Nothing too special, utilising the variables $` and $' which are 'before the match' and 'after the match' respectively, instead of splitting the string. I used a $} for the string variable as originally it was saving me a byte, but isn't any more!

Example run:

$perl -p overwritlabels.pl <<< 'Hello  0
World  8
Fizz   3
Buzz   5
PPCG   16
X      9'
*He*F*Buz*Xrld  *PPCG

Perl, 65 bytes

62 bytes script + 3 bytes for -p

Another version that prints out each line (for one less byte!). (Yes, I made this because I didn't read the question properly...)

$}||=$"x128;/\s+/;substr$},$',1+length$`,"*$`";$_=$};s/\s+$/
/

Example run:

$perl -p overwritlabels.pl <<< 'Hello  0
World  8
Fizz   3
Buzz   5
PPCG   16
X      9'
*Hello
*Hello  *World
*He*Fizz*World
*He*F*Buzzorld
*He*F*Buzzorld  *PPCG
*He*F*Buz*Xrld  *PPCG
\$\endgroup\$
2
\$\begingroup\$

PHP -- 84 bytes

<? foreach(array_chunk(array_slice($argv,1),2) as $p) echo "␣[".($p[1]+1)."G*$p[0]";
                                                            ^ ESC character (\x1b)

Uses ANSI escape codes to position the cursor (\x1b[XG, featuring the Escape character and X being the 1-based coordinate), followed by the * then the input string for that row. Accepts input on the command line of the form:

php filename.php Heathrow 0 Edinburgh 2 London 4 Liverpool 6 Oxford 8
php filename.php abc 5 d 5 abc 10 ABCDEFGHIJKLMNOPQRSTUVWXYZ 127

Accepts multi-word entries if they're in quotes, since they're command line arguments.

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

C++11, 95 bytes

Why not?

As a function, receive input as a map<int, string> named v containing the position and the string.

string t(255,' ');for(auto&m:v){int i=m.first;t[i++]='*';for(auto&c:m.second)t[i++]=c;}cout<<t;

Usage

#include <iostream>
#include <map>
using namespace std;
int main(){
    map<int,string> v{{0,"Heathrow"},{2,"Edinburgh"},{4,"London"},{6,"Liverpool"},{8,"Oxford"}};
    string t(255,' ');for(auto&m:v){int i=m.first;t[i++]='*';for(auto&c:m.second)t[i++]=c;}cout<<t;
}

Check it Running Here

\$\endgroup\$

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.