Detect Rectangular Text with Rectangular Code

Given a string of printable ASCII text (including newlines and spaces) that contains at least one character that is neither a newline nor a space, output a truthy value if the string is rectangular, and a falsey value otherwise. Additionally, the source code for your solution must be rectangular.

A string is rectangular if it meets all of the following conditions:

1. The first line and the last line contain no spaces.
2. The first and last character of each line is not a space.
3. All lines have the same number of characters.

For example, the following text is rectangular:

abcd
e fg
hijk


This text, however, is not rectangular (requirement #3):

1234
567
8900


Test Cases

Truthy:

sdghajksfg

asdf
jkl;

qwerty
u i op
zxcvbn

1234
5  6
7890

abcd
e fg
hijk


Falsey:

a b c

123
456
7 9

12
345

qwerty
uiop
zxcvnm

1234
567
8900


This is , so the shortest solution in bytes wins.

• Sandbox – Mego May 2 '18 at 13:25
• – AdmBorkBork May 2 '18 at 13:41
• So, a one-liner without any space is a valid submission, correct? – Arnauld May 2 '18 at 13:45
• related – Rod May 2 '18 at 14:34
• Can we take input as an array of strings, one for each line? Or must we input a single long string that includes the line breaks? – BradC May 2 '18 at 14:46

C (gcc), 127125124 118 bytes

• Saved two bytes by golfing r*=!e&(!t|t==c); to r>>=e||t&&t-c;. (This golf was the inspiration for my recent C tips answer Inverse flag update.)
• Saved a byte by golfing *(_-2) to _[~1].
• Saved six bytes by golfing *_++-10||(...) to *_++<11?...:0 and utilizing the placeholder zero ...:0 (which is not used constructively) to golf the c++ increment. Those golfs allowed some further loop reshuffling.
• When one can use multiple falsey values, 114 bytes could be possible.
r,e,c,t;_(char*_){for(r=1,t=c=0;*_;*_++<11?r*=(t||(t=c,!e))&*_>32&_[~1]>32&t==c,c=e=0:c++)*_-32||(e=1);r>>=e||t&&t-c;}


Try it online!

Source layout achieving a taller rectangle.

Explanation

The following explains the 124 bytes long version.

r,e,c,t;_(char*_){     // r is the boolean result flag, e a boolean flag if the current line contains
//  a space, t the first line's width, c the current line's current width
for(r=1,t=c=0;*_;c++) // initialize, loop through entire string
*_-32||              // if the current char is a space,
(e=1),              //  the current line contains a space
*_++-10||            // if the current char is a newline (char pointer _ now incremented)
(r*=(t||(t=c,!e))   // if t is not yet set, the current line is the first line; set it
//  to this line's length, check that no spaces where found
&*_>32             // the next line's first char should not be a space
&_[~1]>32          // this line's last char should not have been a space
&t==c,c=~0,e=0);   // the line lengths should match, reset c and e to zero
//  (~0 == -1, countering the loop's increment of c)
r>>=e||t&&t-c;}       // return boolean flag, check that the last line does not contain spaces,
//  there was either no newline or line lengths match
//  (here) equivalent to r*=!e&(!t|t==c)


Try it online!

• +1 for r,e,c,t – Magic Octopus Urn May 2 '18 at 15:07

Java 10, 214176169152144 139 bytes

s->{String[]a=s.split("\n")
;int r=1,i=0,R=a.length;for
(;i<R;i++)if(i<1|i>R-2?a[i]
.contains(" "):a[i].trim( )
!=a[i])r=0;return-r<0;}////


-5 bytes thanks to @Neil.

Uses String[]a instead of var a; return-r<0; instead of return r>0;; and added a comment // at the very end, so there aren't spaces on the first and last rows.

Note that this rectangle is shorter than a single-line input, because int r=1,...; should be replaced with int[]v{1,...};, and all uses of the integers would then becomes v[n] (where n is the index of the variable in the array v).

Try it online.

Explanation:

s->{                        // Method with String parameter and boolean return-type
String[]a=s.split("\n");  //  Input split by new-lines
int r=1,                  //  Result-integer, starting at 1
i=0,                  //  Index i, starting at 0
R=a.length;           //  Amount of rows R
for(;i<R;i++)             //  Loop i over the rows
if(i<1                  //   If it's the first row,
|i>R-2?              //   or the last row:
a[i].contains(" ")  //   And the current row contains a space
:a[i].trim()!=a[i])  //   Or either column of the current row contains a space
r=0;                  //    Set the result r to 0
return-r<0;}             //  Return whether r is still 1
////                        // Comment to comply to the rules of the challenge


Here is the same base program with spaces (128 126 bytes):

s->{var a=s.split("\n");int r=1,i=0,R=a.length;for(;i<R;i++)if(i<1|i>R-2?a[i].contains(" "):a[i].trim()!=a[i])r=0;return r>0;}


-2 bytes thanks to @Neil.

Try it online.

T-SQL, 237 207 bytes

SELECT(SELECT(IIF(max(len(v))=min(len(v)),1,0)*IIF(SUM(len(v+'x')-len
(trim(v))-1)=0,1,0))FROM t)*(SELECT(IIF(SUM(charindex(' ',v))=0,1,0))
FROM[t]WHERE[i]IN(SELECT(min(i))FROM[t]UNION(SELECT(max(i))FROM[t])))


Outputs 1 for rectangular, 0 otherwise. I had to use tons of extra parens and brackets to eliminate spaces, I'm sure there is vast room for improvement.

Explanation:

Per our allowed I/O options and clarification in the question comments, input is taken as separate rows in a pre-existing table t. Because data in SQL is inherently unordered, that table includes a "row number" identity field i:

CREATE TABLE t (i INT IDENTITY(1,1), v VARCHAR(999))


Basically my SQL performs 3 subqueries, each of which returns 0 or 1 based on the 3 criteria of "rectangular" code. Those 3 values are multiplied together, only returning 1 for code that satisfies all 3.

EDIT: Combined criteria 2 and 3 into the same SELECT to save space

SELECT(
SELECT(IIF(max(len(v))=min(len(v)),1,0)                  --All rows same length
*IIF(SUM(len(v+'x')-len(trim(v))-1)=0,1,0))FROM t) --no leading or trailing spaces
*(SELECT(IIF(SUM(charindex(' ',v))=0,1,0))               --No spaces at all in
FROM[t]WHERE[i]IN(SELECT(min(i))FROM[t]                  --   first row or
UNION(SELECT(max(i))FROM[t])))               --   last row


The TRIM(v) function is only supported by SQL 2017 and above. Earlier versions would need LTRIM(RTRIM(v)), which would require rebalancing the rows.

One random note: the LEN() function in SQL ignores trailing spaces, so LEN('foo ') = 3. To get a "true" length you have to tack a character on to the end then subtract one :P

C++, 199183181 175 bytes

This template function accepts lines as a collection of strings (which may be wide strings), passed as a pair of iterators.

#include<algorithm>//
template<class I>bool
f(I a,I b){return!~+(
*a+b[-1]).find(' ')&&
std::all_of(a,b,[&a](
auto&s){return' '+-s.
back()&&s[0]-' '&&a->
size()==s.size();});}


Thanks are due to user Erroneous for reminding me of the back() member of std::string and for pointing out that npos+1 is zero.

Ungolfed equivalent

The only real golfing is to concatenate the first and last lines so we can perform a single find for spaces in those.

#include <algorithm>
template<class It>
bool f(It a, It b)
{
return (*a+b[-1]).find(' ') == a->npos
&& std::all_of(a, b,
[=](auto s) {
return s.back() != ' '
&& s.front() != ' '
&& s.size() == a->size(); });
}


Test program

#include <iostream>
#include <string>
#include <vector>
int expect(const std::vector<std::string>& v, bool expected)
{
bool actual = f(v.begin(), v.end());
if (actual == expected) return 0;
std::cerr << "FAILED " << (expected ? "truthy" : "falsey") << " test\n";
for (auto const& e: v)
std::cerr << "  |" << e << "|\n";
return 1;
}
int expect_true(const std::vector<std::string>& v) { return expect(v, true); }
int expect_false(const std::vector<std::string>& v) { return expect(v, false); }

int main()
{
return
// tests from the question
+ expect_true({"sdghajksfg"})
+ expect_true({"asdf", "jkl;",})
+ expect_true({"qwerty", "u i op", "zxcvbn",})
+ expect_true({"1234", "5  6", "7890",})
+ expect_true({"abcd", "e fg", "hijk",})
+ expect_false({"a b c",})
+ expect_false({"123", "456", "7 9",})
+ expect_false({"12", "345",})
+ expect_false({"qwerty", " uiop", "zxcvnm",})
+ expect_false({"1234", "567", "8900",})
// extra tests for leading and trailing space
+ expect_false({"123", " 56", "789"})
+ expect_false({"123", "45 ", "789"})
// the function source
+ expect_true({"#include<algorithm>//",
"template<class I>bool",
"f(I a,I b){return!~+(",
"*a+b[-1]).find(' ')&&",
"std::all_of(a,b,[&a](",
"auto&s){return' '+-s.",
"back()&&s[0]-' '&&a->",
"size()==s.size();});}",})
;
}

• This can be further golfed to 183 bytes with a line width of 22, using .find(' ')+1==0, and s.back() instead of *s.rbegin(). – Erroneous May 3 '18 at 16:16

JavaScript (Node.js), 85 bytes

x=>(x=x.split\n).some(s=>s.length-x[0].length|s.trim()!=s)<!/\s/.test(x[0]+x.pop())


Try it online!

• @KevinCruijssen sorry :( – DanielIndie May 2 '18 at 14:48
• I love your NOR operator! – Neil May 2 '18 at 16:27

Python 2, 82 bytes

lambda*s:len(set(map(len,s)))<2<'!'<=min(tuple(s[0]+s[-1])+zip(*s)[0]+zip(*s)[-1])


Try it online!

Invoke as f("abcd", "e fg", "hijk").

(\a->all(==[])a||and(e((1<$)<$>a):map(all(>='!').($a))[head,last,map$last,map$head]));e(a:s)=all(==a)s  Thanks to @nimi and @Laikoni for a byte each! Try it online! Haskell, 79 bytes g(x:r)=all((==(0<$x)).(0<$))r&&all(>='!')(x++last(x:r)++(head<$>r)++(last<$>r))  Try it online! Takes input as a list of lines. The pattern g(x:r)= ... binds the first line to x and the (possibly empty) list of remaining lines to r. Then all((==(0<$x)).(0<$))r checks if all lines in r have the same length as x (Using this tip). If not, then the conjunction && short-circuits and returns False, otherwise the right hand side is evaluated. There a string is build which consists of x for the first line, last(x:r) for the last line of r (or the first line again in case r is empty) and (head<$>r) for the first and (last<$>r) for the last character of each line. For this string, all(>='!') checks that it does not contain any spaces (we can't use (>' ') because of the source code restriction). • Errors on "\n\n" – Angs May 3 '18 at 11:18 • @Angs Good catch. Luckily, OP clarified that the input contains at least one character that is neither a newline nor a space, which also allows to drop the empty list case. – Laikoni May 3 '18 at 12:57 • Oh nice, didn't notice that being added – Angs May 3 '18 at 13:02 MATL, 13 bytes ctgF6Lt&()32>  Input is an array of strings, in the format {'abc' 'de'}. Output is an array containing only ones, which is truthy, or an array containing at least a zero, which is falsey. Try it online! Or verify all test cases, including truthiness/falsihood test. Explanation c % Implicit input. Convert to char. This concatenates the % strings of the input cell array as rows of a rectangular % char array, right-padding with spaces as needed tg % Duplicate, convert to logical. Gives a logical array with % the same size containing true in all its entries F % Push false 6L % Push the array [2, j-1], where j is the imaginary unit. % When used as an index, this is interpreted as 2:end-1 t % Duplicate &( % Assignment indexing with 4 inputs: original array, new % value, two indexing arrays. This writes false at the inner % rectangle (2:end-1)×(2:end-1) of the logical array that % initially only contained true. This will be used as a % logical index (mask) into the rectangular char array ) % Reference indexing. This selects the border of the char % array. The result is a column vector of chars 32> % Is each entry greater than 32? (ASCII code for space) % Implicit display  • 11 bytes: cO6Lt&(32=~ Try it online! Just nulls out the non-border parts, then checks if there are any spaces. – sundar Jul 10 '18 at 18:16 • @sundar Good idea! That's different enough, post it yourself – Luis Mendo Jul 10 '18 at 18:38 • Nah, feels too similar to your answer, especially if I write it as cF6Lt&(32=~. Feel free to edit it in, or if not we can just leave it in the comments. – sundar Jul 12 '18 at 8:40 JavaScript (ES6), 88 bytes s=>!s.split\n.some((s,i,a)=>s[L='length']-a[0][L]|(++i%a[L]>1?/^\s|\s$/:/\s/).test(s))


Try it online!

Canvas, 17 15 bytes

４［↷Ｋ；｝┐）｛ＳＬ］∑４≡


Try it here!

Explanation (ASCII-fied for monospace):

4[↷K;}┐){SL]∑4=  full program; pushes the input to the stack.
4[   }           repeat 4 times
↷                rotate ToS clockwise. This also pads the input with spaces
K;              take off the last line and put it below the item
┐          pop the remaining of the input (the center)
)         and wrap the rest (the sides) in an array
{  ]     map over those
S         split on spaces - should result to one item in the array
L        and get the length
∑    sum those lengths together
4=  check if equal 4

• I find it ironic that these UTF8 characters in a monospace-like font give the feeling that there are many spaces in the source. (At least, they do in my browser.) – Arnauld May 2 '18 at 14:07
• @Arnauld fullwidth characters do that. And that's why I made a font for my interpreter to make them prettier :p – dzaima May 2 '18 at 14:13

Perl 5, 70 bytes

$f||=$_;$l||=y///c;$,||=/^\s|\s$/||$l-y///c;$e=$_}{$\="$f$e"=~/\s/||$,


Try it online!

Outputs 0 for truthy, any other number for falsey.

Red, 216 191 bytes

func[s][d:(length?(first(s:(split(s)"^/"))))sp:
func[a][none = find a" "]b: on foreach c s[b: b
and(d = length? c )and(c/1 <>" ")and(" "<> last
c)]res:(sp(first(s)))and(sp(last(s)))and(b)res]


Try it online!

I put a lot of otherwise not necessary parentheses in the first and last rows.

Jelly, 17 bytes

Ỵµ.ịÐ€;ịɗẎ⁶e<L€E$ Try it online! • @JonathanFrech Ah, fixed. >_> – Erik the Outgolfer May 2 '18 at 14:26 • @MagicOctopusUrn Huh? Can you please link to an input where this doesn't behave correctly? – Erik the Outgolfer May 2 '18 at 14:47 • Oh, no, you called mine out for the Does not seem to enforce equal line length too is all I was saying. – Magic Octopus Urn May 2 '18 at 14:57 • Doesn't seem to work for " \n " Try it online! – Angs May 2 '18 at 15:19 • @Angs Try quoting it. It's apparently parsed as nothing if you put it like that. – Erik the Outgolfer May 2 '18 at 15:21 Jelly, 15 bytes Uses a method developed by Mnemonic in a (currently - due to an edge-case failure) deleted Pyth submission. (if it is now fixed up, go give some credit!) ỴµL€Eȧt€⁶ZUƊ4¡⁼  A monadic link accepting a list of characters which returns 1 or 0. Try it online! How? ỴµL€Eȧt€⁶ZUƊ4¡⁼ - Link: list of characters Ỵ - split at newlines (making a list of lists - the rows) µ - start a new monadic chain, call that ROWS L€ - length of €ach row in ROWS E - all equal? (an integer: 1 if so, otherwise 0) 4¡ - repeat four times: Ɗ - last three links as a monad: t€⁶ - trim spaces (⁶) from €ach row in current ROWS Z - transpose that result U - upend (reverse each new row) ȧ - logical AND (0 if L€E was 0 else the result of the repeated transform) ⁼ - equal to X? (the integer 0 is not equal to any listy of characters)  • @Mnemonic - Jelly-fied :) – Jonathan Allan May 2 '18 at 19:11 Japt, 22 bytes Noncompeting answer: there's a known bug in Japt, where two-dimensional array rotations truncate the results. Due to that bug the code below only works on inputs that are square. If the bug wasn't present, though, the code below should work completely correctly. e_Ê¶UÌÊÃ©Ueº4o)r_z)mx}U e_ // Check if every line in the input array Ê¶UÌÊ // has the same length as the last item. Ã© // Also, r_z)mx}U // check if rotating and trimming the input array º4o) // four times Ue // is equal to the input array.  Takes input as an array of strings. Using parentheses instead of spaces makes the rectangular code requirement quite easy. Try it here. Ruby 2.5+, 63 bytes ->a{!a.uniq(&:size)[1]&&a.none?(/^\s|\s$/)&&!(a[0]+a[-1])[?\s]}


Takes input as an array of strings. No test link, since the version on TIO (2.4) is too old for this one. Instead, here is a slightly longer (69 bytes) version for testing:

->a{!a.uniq(&:size)[1]&&a.none?{|l|l=~/^\s|\s\$/}&&!(a[0]+a[-1])[?\s]}


Try it online!

The difference is that since 2.5 Ruby supports directly passing a Regex pattern to all?, any?, none? methods, which saves us a few bytes. The method itself is quite self-explanatory - we test:

1. If there is only 1 unique line size
2. If there are any spaces on line boundaries
3. If there are spaces on the first and last lines.

C (gcc), 119 bytes

Takes input as a list (s) of n strings.

f(s,n,m,r,p)char**s,*p;{for(r=m=n;m--;r*=strlen(*s)==strlen(s[m])&(!p||m&&m^n-1&&p!=s[m]&&p[1]))p=strchr(s[m],32);n=r;}


Try it online!

C# (.NET Core), 145 167 bytes

S[0].Length>1&&S[0].IndexOf
(" ") + S[ S.Count() - 1 ].
IndexOf(" ")<-1&Array.Find(
S,x=>x[0]==' '| x [x.Length
-1]  ==  ' '  | S[0].Length
!=x.Length)==null?11>0:0>1;


Try it online!

S[0].Length>1&                                    // And if the lenght of the first argument is more than 1 char
Array.Find(                                       // Find a string in an array
S,                                            // The array which will be searched in
x=>                                           // For x as the current string from the array
x.Length!=S[0].Length|                        // If the string lenght match not the first argument lenght
x[0]==' '|                                    // Or if the string begins with a spacer
x[x.Length-1]==' '                            // Or if the string ends with a spacer
)==null&                                          // And if there was no string found which matched the conditions
S[0].IndexOf(" ")+S[S.Count()-1].IndexOf(" ")<-1  // And if the first and last string doesn't have a spacer
?                                                 // If all above is true do
1>0                                               // Return True
:                                                 // Else
0>1                                               // Return False

• No spaces in the first line. – FrownyFrog May 3 '18 at 8:28
• @FrownyFrog S[0].IndexOf(" ") is searching for a space in the first line and S[S.Count()-1].IndexOf(" ") is searching in the last line. If there is no space in the first and last line, it is -2 which is then true at -2 < -1. – Hille May 3 '18 at 9:53
• I mean the challenge, your code has the same restriction, so you can’t have spaces in the first line. – FrownyFrog May 3 '18 at 10:41
• Your code must return True when passed to your program. It’s an additional restriction in this challenge. – FrownyFrog May 3 '18 at 11:14
• – Hille May 3 '18 at 11:26