# Crop ASCII Art Challenge

ASCII art is fun. Modern text editors are very good at manipulating text. Are modern programming languages up to the task?

One common task in ASCII art manipulation is to crop text to a rectangle between two characters. This is the task you must implement in this challenge.

## Details

Your program will take 3 inputs:

• the first is the 'start' character of the block - marking the top-left corner
• the second is the 'end' character of the block - marking the bottom-right corner
• the third is some form of multiline text, either a string, or list of strings, or filename, or whatever

The result will be multiline text (again, in any of the above formats) cropped to the rectangle between the given inputs. Note that the first two inputs may not be unique.

## Edge cases

Boxes must always have volume at least 2. Thus these:

()     (
)

are boxes but these:

)(     )      (
(     )

are not (with start=( and end=)).

The input will only contain one box. Thus the start and end characters must only occur once, unless they are the same character in which case they must occur exactly twice.

Additionally each line in the input must be at least as long as the distance from the start of a line to the right edge of the box in the input.

## Rules

Typical code-golf rules apply. Shortest code wins.

## Examples

Sunny-day: start: ( end: ) input:

This is some text
. (but this text
is in a box  ).
So only it is important.

Output:

(but this text
is in a box  )

Note the stripping of horizontal space as well. ASCII art crops are 2d.

Rainy-day: start: ( end: ) input:

This is some text (
But is that even  )
really a box?

Output:

(
)

Same start/end: start: / end: / input:

Oh, I get how this could be useful
/----------------------------\
| All this text is in a box! |
\----------------------------/

Output:

/----------------------------\
| All this text is in a box! |
\----------------------------/

Invalid input: start: ( end: ) input:

Boxes are rectangular ( so this has
0 volume ) which is illegal.

Invalid input 2: start: ( end: ) input:

(The lines must already be square
so this line that is too short
relative to this end, is illegal)
• What about a valid box with a line outside that is shorter than the box? – seadoggie01 Sep 13 at 17:18
• clarified, also invalid input – LambdaBeta Sep 13 at 17:23
• what should be the outcome in case of an invalid input? or are they mentioned so they won't have to be taken care of? – Uriel Sep 13 at 17:26
• The outcome is much like undefined behaviour in C, don't worry about it, anything goes. – LambdaBeta Sep 13 at 17:32
• This is a nasty little challenge: nice job! – seadoggie01 Sep 13 at 17:42

# Vim, 16, 12 bytes/keystrokes

#<C-v>Nj*yggVGp

Try it online! in the V interpreter

Modern text editors are very good at manipulating text. Are modern programming languages up to the task?

I bet old text editors are even better! :D

Even though it doesn't necessarily have to, this answer does work with both of the given "invalid" inputs, outputting

rectangular (
) which is ill

and

(The lines must already be square
so this line that is too short
relative to this end, is illegal)
• Incidentally this is exactly the use-case I was doing to prompt me to write this challenge. I had my q macro as /\/<cr><c-v>nygv$o0dp or something like that for far too long :) – LambdaBeta Sep 13 at 17:34 • Yeah, rectangular is the illest! – AdmBorkBork Sep 13 at 20:03 # APL (Dyalog), 38 30 bytes 4 bytes saved thanks to @EriktheOutgolfer (1-⍨w-⍨⊃⍸⎕=s)↑(w←∊⊃⌽⍸⎕=s)↑s←↑⎕ Try it online! • too complicated. you could accept a matrix instead of vector of vectors, find the two positions with ⍸matrix∊separators, and do take/drop with them – ngn Sep 14 at 11:31 • (⍸a=⎕)↓(1+⍸a=⎕)↑a←⎕ with ⎕io←0 – ngn Sep 14 at 11:45 • @ngn OP said number of characters differs between lines so I assumed input should be vector before processing. Even so, I need the selection parts (first and rotate) in case the delimiter shows a few times (see third test case), but I guess the drop does cut a few bytes, so thanks! I'll update once I get to PC – Uriel Sep 14 at 15:21 • oops... I forgot about the third case, sorry. Then maybe: ⊃{⌽⊖⍵↓⍨⊃⍸⍺=⍵}/⎕⎕⎕ (sic, with 3 trailing quads) which is even shorter. Or ... ⎕⎕(↑⎕) if a pre-mixed matrix is not allowed. – ngn Sep 14 at 16:12 # Jelly, 13 bytes =€SŒṪr/,þ/Zœị A dyadic link accepting a list of start and end characters on the left and a list of lines (as lists of characters) on the right which yields a list of lines (as lists of characters). Try it online! (full program - if inputs are valid Python they will require Python-string-quoting.) ### How? =€SŒṪr/,þ/Zœị - Link: [start, stop], lines € - for each (of [start, stop]): = - equals? (vectorises across the lines) S - sum (vectorises) ŒṪ - multi-dimensional truthy (i.e. non-zero) indices / - reduce by: r - inclusive range / - reduce by: þ - outer product with: , - pair Z - transpose œị - multi-dimensional index-into (the lines) # Jelly, 14 bytes œẹⱮẎQr/Ṛṭþ/œị⁸ Try it online! • I tried running your code on some of the other examples, but only got a crash. Is that a bug, or am I just doing something wrong? – Ilmari Karonen Sep 13 at 19:08 • @IlmariKaronen You haven't quoted the second argument properly (didn't mention that in the post); wrap it in single or double quotes. The way you've invoked it, the second argument is an empty (Python) tuple (()), not '()'. If it's parse-able, it needs to be quoted, however, my // doesn't need to be quoted (integer division operator without operands? hm...). – Erik the Outgolfer Sep 13 at 19:10 • @IlmariKaronen I "think" that () is just being interpreted by Jelly as some kind of special character. Most pairs of characters I try work. I'd love to hear what people more familiar with Jelly think. EDIT: ninja-ed by erik the outgolfer – LambdaBeta Sep 13 at 19:11 • OK, yeah, that works. – Ilmari Karonen Sep 13 at 19:17 # Python 2, 130 bytes def f(s,e,t): a=[(i,l.find(c))for i,l in enumerate(t)for c in s+e if c in l];r,c,R,C=a[0]+a[-1] for l in t[r:R+1]:print l[c:C+1] Try it online! # Canvas, 37 bytes ｛³⁴⁰；ｘ≡‽┐ Ｘ⁸）Ｊ╵⁶；┤ω┤⁵Ｘ⁶⁸⁰Ｋ├；┐┤└∔┘┘∔；＠ Try it here! 36 bytes for getting the coordinates of the characters (and converting them to x,y,w,h because that's what takes) and 1 byte for getting the subsection.. There must be a better approach # JavaScript (ES6), 98 bytes Takes input as two integers and an array of strings. Returns an array of strings. (x,y,a,X=Y=0)=>a.filter(s=>!Y&&(Y=-~s.indexOf(y,X?X-1:X=-~s.indexOf(x)),X)).map(s=>s.slice(X-1,Y)) Try it online! ### Commented ( x, // x = start character y, // y = end character a, // a[] = array of strings X = // X = position of x, plus 1 Y = 0 // Y = position of y, plus 1 ) => // a.filter(s => // for each string s in a[]: !Y && // reject this string if Y is non-zero ( // otherwise, use the 2nd condition: Y = -~s.indexOf( // update Y: y, // by looking for y in s X ? // if X is non-zero: X - 1 // start the search at X - 1 : // else: X = -~s.indexOf(x) // update X and start the search at X ), // end of Y update X // keep this string if X is non-zero ) // end of 2nd condition ) // end of filter() .map(s => // for each remaining string s: s.slice(X - 1, Y) // remove left and right characters outside the box ) // end of map() • filter and map?! Would building a new array with reduce or a recursive solution not work out shorter? On me phone, down the pub or I'd give it a go myself. – Shaggy Sep 13 at 22:25 • @Shaggy There's probably a shorter way indeed, but I think that this method is doomed to use 2 passes: the 2nd loop cannot start before the 1st one terminates and both X and Y are known for sure. – Arnauld Sep 13 at 23:00 # Java 10, 204 bytes (s,e,a)->{int b=-1,i=0;for(;i<a.length;i++)a[i]=(b=b<0?a[i].indexOf(s):b)<0|a[i].length()<b?"":a[i].substring(b);for(b=-1;i-->0;)a[i]=(b=b<0?a[i].indexOf(e):b)<0|a[i].length()<b?"":a[i].substring(0,b+1);} Modifies the input-array instead of returning a new one to save bytes. This does mean that removed lines become "" instead, though. If this is not allowed I will change it. Try it online. Explanation: (s,e,a)->{ // Method with 2 Strings & String-array parameters and no return int b=-1, // Boundaries-integer, starting at -1 i=0;for(;i<a.length;i++) // Loop i in the range [0, amountOfLines) a[i]= // Change the ith line in the array to: (b=b<0? // If b is -1: a[i].indexOf(s) // Set b to the index of s in the current line // (which is still -1 if it's not found) : // Else (starting index already found) b // Leave b unchanged )<0 // Then, if b is -1, |a[i].length()<b? // or the current line-length is too short: "" // Remove the current line : // Else: a[i].substring(b); // Shorten the line by removing every character before b for(b=-1; // Reset b to -1 i-->0;) // Loop i in the range (amountOfLines, 0] a[i]= // Change the ith line in the array to: (b=b<0?a[i].indexOf(e):b)<0|a[i].length()<b?"": // Similar as above (with end e instead of start s), a[i].substring(0,b+1);} // except we remove every character after b this time For example: With the inputs start = "(", end = ")" and lines = ["This is some text", ". (but this text", " is in a box ).", "So only it is important."] the first loop will crop it at the top and left, changing it to this: ["", "(but this text", "is in a box ).", " only it is important."] the second loop will crop it at the bottom and right, changing it to this: ["", "(but this text", "is in a box )", ""] # Retina 0.8.2, 110 bytes ^((.)¶.)(.*¶)+(.*\2)$1¶$4 ^(.)(¶.¶\1)$2
}s(?<=^.¶.+)¶.
¶
s^¶(.)¶(.*\1).*
$2 +m^((.)+).¶((?<-2>.)+)$
$1¶$3

Try it online! Explanation:

^((.)¶.)(.*¶)+(.*\2)
$1¶$4

Delete the input lines preceding the first line of the box.

^(.)(¶.¶\1)
$2 If the start character is in the left column of the input, delete it. }s(?<=^.¶.+)¶. ¶ If the start character hasn't been deleted yet, shift all the input columns left by one and repeat from the beginning. s^¶(.)¶(.*\1).*$2

Delete the end character and everything in the input after the end character.

+m^((.)+).¶((?<-2>.)+)1¶\$3

Truncate all the lines to the length of the next line. This works by counting all but one character on each line, then attempting to match up to that many characters on the next line; if this succeeds then the second line was shorter, so the last character is deleted and the loop repeats.

# C (gcc), 237 bytes

f(c,r,o,p)char*p,*c;{char*_=strchr(p,r),*a,b;*_=0;a=strrchr(p,10);a=(a?a:p);*_=r;r=_-a;p=a;_=strrchr(p,o);*_=0;a=strrchr(p,10);a=(a?a:p);*_=o;o=_-a+1;_[1]=0;for(_=p;_;_=strchr(_+1,10)){b=_[o];_[o]=0;strcat(c,_+r);strcat(c,"\n");_[o]=b;}}

Try it online!

I'm 99% sure this can be shortened using some kind of helper function to find horizontal index of and pointer to a character, as it is repeated twice. Alas I couldn't find a short enough way to do it, I may try again later if I find the time.

### Description

f(c,r,o,p)char*p,*c;{
char*_=strchr(p,r),*a,b;         // find opening char (and declare vars)
*_=0;a=strrchr(p,10);            // find \n before it
a=(a?a:p);                       // deal with single line inputs
*_=r;r=_-a;                      // save left margin width in r
p=a;                             // crop everything before opening line

_=strchr(p,o);                   // find closing char
*_=0;a=strrchr(p,10);            // find \n before it
a=(a?a:p);                       // deal with single line inputs
*_=o;o=_-a+1;                    // save width in o
_[1]=0;                          // crop everything after closing char
for(_=p;_;_=strchr(_+1,10)){       // for each line
b=_[o];_[o]=0;
strcat(c,_+r);
strcat(c,"\n");
_[o]=b;
}
}