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:
- The first line and the last line contain no spaces.
- The first and last character of each line is not a space.
- 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 code-golf, so the shortest solution in bytes wins.
-
\$\begingroup\$ Sandbox \$\endgroup\$user45941– user459412018年05月02日 13:25:47 +00:00Commented May 2, 2018 at 13:25
-
2\$\begingroup\$ Related. \$\endgroup\$AdmBorkBork– AdmBorkBork2018年05月02日 13:41:16 +00:00Commented May 2, 2018 at 13:41
-
9\$\begingroup\$ So, a one-liner without any space is a valid submission, correct? \$\endgroup\$Arnauld– Arnauld2018年05月02日 13:45:36 +00:00Commented May 2, 2018 at 13:45
-
1\$\begingroup\$ related \$\endgroup\$Rod– Rod2018年05月02日 14:34:00 +00:00Commented May 2, 2018 at 14:34
-
1\$\begingroup\$ 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? \$\endgroup\$BradC– BradC2018年05月02日 14:46:25 +00:00Commented May 2, 2018 at 14:46
19 Answers 19
C (gcc), (削除) 127 (削除ここまで) (削除) 125 (削除ここまで) (削除) 124 (削除ここまで) 118 bytes
- Saved two bytes by golfing
r*=!e&(!t|t==c);
tor>>=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 thec++
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;}
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)`
Java 10, (削除) 214 (削除ここまで) (削除) 176 (削除ここまで) (削除) 169 (削除ここまで) (削除) 152 (削除ここまで) (削除) 144 (削除ここまで) 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
).
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.
-
1
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++, (削除) 199 (削除ここまで) (削除) 183 (削除ここまで) (削除) 181 (削除ここまで) 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();});}",})
;
}
-
\$\begingroup\$ This can be further golfed to 183 bytes with a line width of 22, using
.find(' ')+1==0
, ands.back()
instead of*s.rbegin()
. \$\endgroup\$Erroneous– Erroneous2018年05月03日 16:16:49 +00:00Commented May 3, 2018 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())
-
\$\begingroup\$ @KevinCruijssen sorry :( \$\endgroup\$DanielIndie– DanielIndie2018年05月02日 14:48:22 +00:00Commented May 2, 2018 at 14:48
-
\$\begingroup\$ I love your
NOR
operator! \$\endgroup\$Neil– Neil2018年05月02日 16:27:36 +00:00Commented May 2, 2018 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])
Invoke as f("abcd", "e fg", "hijk")
.
Haskell, (削除) 106 (削除ここまで) (削除) 102 (削除ここまで) (削除) 98 (削除ここまで) (削除) 110 (削除ここまで) (削除) 109 (削除ここまで) 102 bytes
(\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!
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).
-
\$\begingroup\$ Errors on "\n\n" \$\endgroup\$Angs– Angs2018年05月03日 11:18:24 +00:00Commented May 3, 2018 at 11:18
-
\$\begingroup\$ @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. \$\endgroup\$Laikoni– Laikoni2018年05月03日 12:57:21 +00:00Commented May 3, 2018 at 12:57 -
\$\begingroup\$ Oh nice, didn't notice that being added \$\endgroup\$Angs– Angs2018年05月03日 13:02:57 +00:00Commented May 3, 2018 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
-
\$\begingroup\$ 11 bytes:
cO6Lt&(32=~
Try it online! Just nulls out the non-border parts, then checks if there are any spaces. \$\endgroup\$Sundar R– Sundar R2018年07月10日 18:16:36 +00:00Commented Jul 10, 2018 at 18:16 -
\$\begingroup\$ @sundar Good idea! That's different enough, post it yourself \$\endgroup\$Luis Mendo– Luis Mendo2018年07月10日 18:38:43 +00:00Commented Jul 10, 2018 at 18:38
-
1\$\begingroup\$ 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. \$\endgroup\$Sundar R– Sundar R2018年07月12日 08:40:32 +00:00Commented Jul 12, 2018 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))
Canvas, (削除) 17 (削除ここまで) 15 bytes
4[↷K;}┐){SL]∑4≡
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
-
4\$\begingroup\$ 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.) \$\endgroup\$Arnauld– Arnauld2018年05月02日 14:07:32 +00:00Commented May 2, 2018 at 14:07
-
1\$\begingroup\$ @Arnauld fullwidth characters do that. And that's why I made a font for my interpreter to make them prettier :p \$\endgroup\$dzaima– dzaima2018年05月02日 14:13:59 +00:00Commented May 2, 2018 at 14:13
Perl 5, 70 bytes
$f||=$_;$l||=y///c;,ドル||=/^\s|\s$/||$l-y///c;$e=$_}{$\="$f$e"=~/\s/||,ドル
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]
I put a lot of otherwise not necessary parentheses in the first and last rows.
-
\$\begingroup\$ @JonathanFrech Ah, fixed. >_> \$\endgroup\$Erik the Outgolfer– Erik the Outgolfer2018年05月02日 14:26:39 +00:00Commented May 2, 2018 at 14:26
-
\$\begingroup\$ @MagicOctopusUrn Huh? Can you please link to an input where this doesn't behave correctly? \$\endgroup\$Erik the Outgolfer– Erik the Outgolfer2018年05月02日 14:47:26 +00:00Commented May 2, 2018 at 14:47
-
\$\begingroup\$ Oh, no, you called mine out for the
Does not seem to enforce equal line length
too is all I was saying. \$\endgroup\$Magic Octopus Urn– Magic Octopus Urn2018年05月02日 14:57:39 +00:00Commented May 2, 2018 at 14:57 -
\$\begingroup\$ Doesn't seem to work for
" \n "
Try it online! \$\endgroup\$Angs– Angs2018年05月02日 15:19:57 +00:00Commented May 2, 2018 at 15:19 -
1\$\begingroup\$ @Angs Try quoting it. It's apparently parsed as nothing if you put it like that. \$\endgroup\$Erik the Outgolfer– Erik the Outgolfer2018年05月02日 15:21:35 +00:00Commented May 2, 2018 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ȧt6ドルZUƊ4¡=
A monadic link accepting a list of characters which returns 1 or 0.
How?
ỴμL€Eȧt6ドル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:
t6ドル - trim spaces (6) 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)
-
\$\begingroup\$ @Mnemonic - Jelly-fied :) \$\endgroup\$Jonathan Allan– Jonathan Allan2018年05月02日 19:11:13 +00:00Commented May 2, 2018 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ÌÊéUeo4o)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
o4o) // 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]}
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:
- If there is only 1 unique line size
- If there are any spaces on line boundaries
- 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;}
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;
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
-
\$\begingroup\$ No spaces in the first line. \$\endgroup\$FrownyFrog– FrownyFrog2018年05月03日 08:28:21 +00:00Commented May 3, 2018 at 8:28
-
\$\begingroup\$ @FrownyFrog
S[0].IndexOf(" ")
is searching for a space in the first line andS[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
. \$\endgroup\$Hille– Hille2018年05月03日 09:53:20 +00:00Commented May 3, 2018 at 9:53 -
2\$\begingroup\$ I mean the challenge, your code has the same restriction, so you can’t have spaces in the first line. \$\endgroup\$FrownyFrog– FrownyFrog2018年05月03日 10:41:26 +00:00Commented May 3, 2018 at 10:41
-
1\$\begingroup\$ Your code must return
True
when passed to your program. It’s an additional restriction in this challenge. \$\endgroup\$FrownyFrog– FrownyFrog2018年05月03日 11:14:19 +00:00Commented May 3, 2018 at 11:14 -
1\$\begingroup\$ Let us continue this discussion in chat. \$\endgroup\$Hille– Hille2018年05月03日 11:26:14 +00:00Commented May 3, 2018 at 11:26