Detect Rectangular Text with Rectangular Code

19

2

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.

Mego

Posted 2018-05-02T13:25:31.040

Reputation: 32 998

Sandbox – Mego – 2018-05-02T13:25:47.230

2Related. – AdmBorkBork – 2018-05-02T13:41:16.840

9So, a one-liner without any space is a valid submission, correct? – Arnauld – 2018-05-02T13:45:36.903

1related – Rod – 2018-05-02T14:34:00.190

1Can 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 – 2018-05-02T14:46:25.283

@Arnauld Yes, that is correct. – Mego – 2018-05-02T22:42:18.653

@BradC Either is fine, so long as it is unambiguous. – Mego – 2018-05-02T22:42:30.983

@Laikoni, the question says we'll be given a string that contains at least one character that is neither a newline nor a space. That implies we don't need to handle an empty string. – Toby Speight – 2018-05-03T10:56:54.783

Is the tab character (ASCII value 9) considered printable? – Jonathan Frech – 2018-05-03T15:00:58.963

@JonathanFrech No – Mego – 2018-05-03T15:02:15.763

Answers

12

C (gcc), 127 125 124 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!

Jonathan Frech

Posted 2018-05-02T13:25:31.040

Reputation: 6 681

10+1 for r,e,c,t – Magic Octopus Urn – 2018-05-02T15:07:20.987

4

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).

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.

Kevin Cruijssen

Posted 2018-05-02T13:25:31.040

Reputation: 67 575

3

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();});}",})
        ;
}

Toby Speight

Posted 2018-05-02T13:25:31.040

Reputation: 5 058

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 – 2018-05-03T16:16:49.540

3

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

BradC

Posted 2018-05-02T13:25:31.040

Reputation: 6 099

2

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!

Try it online!

Angs

Posted 2018-05-02T13:25:31.040

Reputation: 4 825

2

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!

l4m2

Posted 2018-05-02T13:25:31.040

Reputation: 5 985

@KevinCruijssen sorry :( – DanielIndie – 2018-05-02T14:48:22.607

I love your NOR operator! – Neil – 2018-05-02T16:27:36.450

2

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).

Laikoni

Posted 2018-05-02T13:25:31.040

Reputation: 23 676

Errors on "\n\n" – Angs – 2018-05-03T11:18:24.447

@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 – 2018-05-03T12:57:21.043

Oh nice, didn't notice that being added – Angs – 2018-05-03T13:02:57.860

2

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").

Lynn

Posted 2018-05-02T13:25:31.040

Reputation: 55 648

2

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

Luis Mendo

Posted 2018-05-02T13:25:31.040

Reputation: 87 464

11 bytes: cO6Lt&(32=~ Try it online! Just nulls out the non-border parts, then checks if there are any spaces.

– sundar - Reinstate Monica – 2018-07-10T18:16:36.203

@sundar Good idea! That's different enough, post it yourself – Luis Mendo – 2018-07-10T18:38:43.773

1Nah, 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 - Reinstate Monica – 2018-07-12T08:40:32.583

1

Canvas, 17 15 bytes

4[↷K;}┐){SL]∑4≡

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

dzaima

Posted 2018-05-02T13:25:31.040

Reputation: 19 048

4I 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 – 2018-05-02T14:07:32.757

1@Arnauld fullwidth characters do that. And that's why I made a font for my interpreter to make them prettier :p – dzaima – 2018-05-02T14:13:59.710

1

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!

Arnauld

Posted 2018-05-02T13:25:31.040

Reputation: 111 334

1

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.

Xcali

Posted 2018-05-02T13:25:31.040

Reputation: 7 671

1

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.

Galen Ivanov

Posted 2018-05-02T13:25:31.040

Reputation: 13 815

0

Jelly, 17 bytes

Ỵµ.ịЀ;ịɗẎ⁶e<L€E$

Try it online!

Erik the Outgolfer

Posted 2018-05-02T13:25:31.040

Reputation: 38 134

@JonathanFrech Ah, fixed. >_> – Erik the Outgolfer – 2018-05-02T14:26:39.677

@MagicOctopusUrn Huh? Can you please link to an input where this doesn't behave correctly? – Erik the Outgolfer – 2018-05-02T14:47:26.317

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 – 2018-05-02T14:57:39.887

Doesn't seem to work for " \n " Try it online!

– Angs – 2018-05-02T15:19:57.263

1

@Angs Try quoting it. It's apparently parsed as nothing if you put it like that.

– Erik the Outgolfer – 2018-05-02T15:21:35.677

0

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)

Jonathan Allan

Posted 2018-05-02T13:25:31.040

Reputation: 67 804

@Mnemonic - Jelly-fied :) – Jonathan Allan – 2018-05-02T19:11:13.237

0

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.

Nit

Posted 2018-05-02T13:25:31.040

Reputation: 2 667

0

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

Hille

Posted 2018-05-02T13:25:31.040

Reputation: 349

No spaces in the first line. – FrownyFrog – 2018-05-03T08:28:21.890

@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 – 2018-05-03T09:53:20.287

2I mean the challenge, your code has the same restriction, so you can’t have spaces in the first line. – FrownyFrog – 2018-05-03T10:41:26.237

@FrownyFrog You mean the return? Must I include it? Sry I'm new to Code Golf and I don't know the proper way of showing code on tio in C#. What must I do to get it right? – Hille – 2018-05-03T10:52:16.190

1Your code must return True when passed to your program. It’s an additional restriction in this challenge. – FrownyFrog – 2018-05-03T11:14:19.230

@FrownyFrog it's returning true, C# compiles 1>0 to true and 0>1 to false. Through this way, I can save some bytes. Otherwise, it is case sensitive, then I must go with a string. – Hille – 2018-05-03T11:19:03.113

1

Let us continue this discussion in chat.

– Hille – 2018-05-03T11:26:14.853

0

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.

Kirill L.

Posted 2018-05-02T13:25:31.040

Reputation: 6 693

0

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!

gastropner

Posted 2018-05-02T13:25:31.040

Reputation: 3 264