检测由M和S字符组成的ASCII艺术窗口


28

窗口是一种ASCII艺术形式的正方形,其奇数边的长度至少为3,边缘带有单个字符边框,中间有垂直和水平笔触:

#######
#  #  #
#  #  #
#######
#  #  #
#  #  #
#######

MS窗口是仅由字符M和组成边框的窗口S。您的任务是编写一个程序(或函数),该程序接受一个字符串,如果输入是有效的MS Window,则输出一个真实值,否则输入一个虚假值。

技术指标

  • 您可以将输入作为换行符分隔的字符串或代表每一行的字符串数组。
  • MS窗口的边框可能包含M和S字符的混合,但内部始终由空格组成。
  • 您可以选择仅检测带有尾随换行符的窗口,或者仅检测不带尾随换行符的窗口,但不能同时检测这两者。

测试用例

真相:

MMM
MMM
MMM

SMSMS
M M S
SMSMM
S S M
SMSMS

MMMMMMM
M  S  M
M  S  M
MSSSSSM
M  S  M
M  S  M
MMMMMMM

虚假:

Hello, World!

MMMM
MSSM
MS M
MMMM

MMSMM
M S.M
sSSSS
M S M
MMSMM

MMMMMMM
M  M  M
MMMMMMM
M  M  M
MMMMMMM

MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM

MMSSMSSMM
M   M   M
S   S   S
S   S  S
MMSSMSSMM
S   S   S
S   S   S
M   M   M
MMSSMSSMM

3
这是ASCII艺术的一大转折,它是检测特定结构的决策问题。
xnor

4
@xnor我觉得我们可能想要像这样的反向ASCII艺术的不同标签。
硕果累累

2
尽管不是特定于ascii艺术,但模式匹配可能是新标签的不错选择
Destructible Lemon

您可以添加一个或两个不构成矩形数组的测试用例吗?
格雷格·马丁

1
@桅杆,你说得对!也许需要澄清挑战
克里斯M

Answers:


1

Pyke,34 31字节

lei}t\Mcn+it*i\M*+s.XM"QJ\S\M:q

在这里尝试!

lei                              -         i = len(input)//2
   }t                            -        (^ * 2) - 1
     \Mc                         -       "M".center(^)
        n+                       -      ^ + "\n"
          it*                    -     ^ * (i-1)
                 +               -    ^ + V
             i\M*                -     "M"*i
                  s              -   palindromise(^)
                   .XM"          -  surround(^, "M")
                               q - ^ == V
                       QJ        -   "\n".join(input)
                         \S\M:   -  ^.replace("S", "M")


7

污垢39 38字节

感谢Zgarb节省了1个字节。

e`BB/BB/W+ W/+
B=W|B/W\ * W/\ /*
W=[MS

在线尝试!

我不确定是否有比使用递归非终结符更简单的方法来实现单个窗口组件的平方长宽比,但这似乎效果很好。

说明

最好从头开始阅读程序。

W=[MS

这只是定义了一个非终结符(您可以将其视为与矩形匹配的子例程),该非终结符W与an M或an 匹配S]在行的末尾有一个隐式)。

B=W|B/W\ * W/\ /*

这定义了一个非终端B,它匹配大约四分之一的输出,即一个带有左边界和顶边界的窗口面板。像这样:

MSM
S  
M  

为确保此窗口面板为正方形,我们B递归定义。它可以是窗口字符W,也B/W\ * W/\ /*可以在右侧和底部添加一层。要查看它是如何做到的,让我们删除一些语法糖:

(B/W[ ]*)(W/[ ]/*)

这是相同的,因为可以使用AB或写入水平串联A B,但是后者的优先级低于垂直串联,/而前者的优先级更高。所以B/W[ ]*B一个窗口字符和空格下方一排。然后我们水平附加W/[ ]/*一个带有一列空格的窗口字符。

最后,我们将这些非终结符组装成最终的窗口形状:

BB/BB/W+ W/+

那是四个窗口面板,B后面是一行窗口字符和一列窗口字符。请注意,我们没有明确断言四个窗口面板的大小相同,但是如果不是,则不可能将它们连接成矩形。

Finally the e` at the beginning is simply a configuration which tells Grime to check that the entire input can be matched by this pattern (and it prints 0 or 1 accordingly).


5

JavaScript (ES6), 115 113 bytes

a=>(l=a.length)&a.every((b,i)=>b.length==l&b.every((c,j)=>(i&&l+~i-i&&l+~i&&j&&l+~j-j&&l+~j?/ /:/[MS]/).test(c)))

Takes input as a an array of arrays of characters (add 5 bytes for an array of strings) and returns 1 or 0. After verifying that the height is odd, every row is checked to ensure the array is square, and every character is verified to be one of the character(s) that we expect in that particular position. Edit: Saved 2 bytes thanks to @PatrickRoberts.


You can change (...).includes(c) to ~(...).search(c) to save 1 byte
Patrick Roberts

1
Actually, even better you can change it to (...?/ /:/[MS]/).test(c) to save 2 bytes instead of just 1.
Patrick Roberts

@PatrickRoberts Cute, thanks!
Neil

5

Perl, 124 123 119 95 93 84

The following Perl script reads one candidate MS Window from the standard input. It then exits with a zero exit status if the candidate is an MS Window and with a non-zero exit status if it isn't.

It works by generating two regular expressions, one for the top, middle and bottom line and one for every other line, and checking the input against them.

Thanks, @Dada. And again.

map{$s=$"x(($.-3)/2);$m="[MS]";($c++%($#a/2)?/^$m$s$m$s$m$/:/^${m}{$.}$/)||die}@a=<>

I'm not sure giving the result as exit status is allowed (I don't have time to look for the relevant meta post though). Regardless, you can save a few bytes: @a=<>;$s=$"x(($.-3)/2);$m="[MS]";map{$a[$_]!~($_%($./2)?"$m$s$m$s$m":"$m${m}{$.}")&&die}0..--$.
Dada

@Dada: Thanks! That's an impressive improvement: 24 characters. (There was a stray "$m" in your code, so it's even shorter than it looked at first.) I wasn't sure if reporting the result with an exit code was allowed in general but I took the "write a program (or function)" as allowing one to be flexible with how the result is returned in this particular case; exit codes are practically the function return values of the *nix environment. :-)
nwk

Make that 26 characters.
nwk

1
Actually, I'm decrementing $. at the end to avoid using twice $.-1 (especially since the first time it was ($.-1)/2 so it needed some extra parenthesis), so the $m in $m${m}{$.} isn't a mistake. Also, I just realized now, but the regexs should be surrounded with ^...$ (so extra character at the end or the beginning make them fail), or shorter: use ne instead of !~.
Dada

Nevermind, obviously you can't use ne instead of !~ (I shouldn't write messages when I've been awake for just 15 minutes!). So you'll have to use ^...$ in both regex I'm afraid.
Dada

2

Mathematica, 166 bytes

Union[(l=Length)/@data]=={d=l@#}&&{"M","S"}~(s=SubsetQ)~(u=Union@*Flatten)@{#&@@(p={#,(t=#~TakeDrop~{1,-1,d/2-.5}&)/@#2}&@@t@#),p[[2,All,1]]}&&{" "}~s~u@p[[2,All,2]]&

Unnamed function taking a list of lists of characters as input and returning True or False. Here's a less golfy version:

(t = TakeDrop[#1, {1, -1, d/2 - 0.5}] &; 
Union[Length /@ data] == {d = Length[#1]}
  &&
(p = ({#1, t /@ #2} &) @@ t[#1];
SubsetQ[{"M", "S"}, Union[Flatten[{p[[1]], p[[2, All, 1]]}]]]
  && 
SubsetQ[{" "}, Union[Flatten[p[[2, All, 2]]]]])) &

The first line defines the function t, which separates a list of length d into two parts, the first of which is the first, middle, and last entries of the list, and the second of which is all the rest. The second line checks whether the input is a square array in the first place. The fourth line uses t twice, once on the input itself and once on all* of the strings in the input, to separate the characters that are supposed to be "M" or "S" from the characters that are supposed to be spaces; then the fifth and seventh lines check whether they really are what they're supposed to be.


2

JavaScript (ES6), 108 106 bytes

Input: array of strings / Output: 0 or 1

s=>s.reduce((p,r,y)=>p&&r.length==w&(y==w>>1|++y%w<2?/^[MS]+$/:/^[MS]( *)[MS]\1[MS]$/).test(r),w=s.length)

Test cases


2

JavaScript (ES6), 140 138 141 140 bytes

I know this isn't a winning byte count (although thanks to Patrick Roberts for -3, and I realised it threw false positives for 1 instead of M/S: +3), but I did it a slightly different way, I'm new to this, and it was fun...

Accepts an array of strings, one for each line and returns true or false. Newline added for clarity (not included in byte count).

f=t=>t.every((e,i)=>e.split`S`.join`M`==[...p=[b='M'.repeat(s=t.length),
...Array(z=-1+s/2|0).fill([...'MMM'].join(' '.repeat(z)))],...p,b][i])

Instead of checking input against a generalised pattern, I construct an 'M' window of the same size, replace S with M on input, and compare the two.

Ungolfed

f = t => t.every( // function returns true iff every entry in t
                  // returns true below
  (e, i) => e.split`S`.join`M` // replace all S with M
                                 // to compare to mask
  == [ // construct a window of the same size made of Ms and
       // spaces, compare each row 
      ...p = [ // p = repeated vertical panel (bar above pane)
               // which will be repeated
              b = 'M'.repeat(s = t.length),
                  // b = bar of Ms as long as the input array
              ...Array(z = -1 + s/2|0).fill([...'MMM'].join(' '.repeat(z)))],
              // z = pane size; create enough pane rows with
              // Ms and enough spaces
      ...p, // repeat the panel once more
      b][i] // finish with a bar
)

console.log(f(["111","111","111"]))

console.log(f(["MMMMM","M S M","MSSSM","M S M","MSSSM"]))

Test cases

f=t=>t.every((e,i)=>e.split`S`.join`M`==[...p=[b='M'.repeat(s=t.length),
...Array(z=-1+s/2|0).fill([...'MMM'].join(' '.repeat(z)))],...p,b][i])


truthy=`MMM
MMM
MMM

SMSMS
M M M
SMSMS
M M M
SMSMS

MMMMMMM
M  S  M
M  S  M
MSSSSSM
M  S  M
M  S  M
MMMMMMM`.split('\n\n')

falsey=`Hello, World!

MMMM
MSSM
MS M
MMMM

MMSMM
M S.M
sSSSS
M S M
MMSMM

MMMMMMM
M  M  M
MMMMMMM
M  M  M
MMMMMMM

MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM`.split('\n\n')

truthy.forEach(test=>{
  console.log(test,f(test.split('\n')))
})

falsey.forEach(test=>{
  console.log(test,f(test.split('\n')))
})


1
For future reference, unless the function is recursive, f= doesn't need to be included in the byte count, so this is actually a 138 byte submission.
Patrick Roberts

You can replace z=-1+s/2|0 with z=(s-3)/2 to save 1 byte
Patrick Roberts

You can also replace e.replace(/S/g,'M')==... with e.split`S`.join`M`==... to save another byte
Patrick Roberts

Thanks! z=-1+s/2|0 is there to return a positive integer for s==1 and even s, i.e. the function returns false without Array() crashing it. Otherwise the necessary logic made it longer. Great tip on split/join, thanks
Chris M

Good catch, I didn't consider the s=1 case, since my invalid regex just silently fails.
Patrick Roberts

1

JavaScript (ES6), 109 107 106 105 99 bytes

s=>!s.split`S`.join`M`.search(`^((M{${r=s.search`
`}})(
(M( {${w=(r-3)/2}})M\\5M
){${w}}))\\1\\2$`)

Edit: Whoa, Arnauld saved me 6 bytes by changing s.split`\n`.length to s.search`\n`! Thanks!

This takes a single multiline string and constructs a RegExp-based validation using the length of the input string. Returns true or false. Assumes a valid window has does not have a trailing newline.

Demo

f=s=>!s.split`S`.join`M`.search(`^((M{${r=s.search`
`}})(
(M( {${w=(r-3)/2}})M\\5M
){${w}}))\\1\\2$`);
`MMM
MMM
MMM

SMSMS
M M M
SMSMS
M M M
SMSMS

MMMMMMM
M  S  M
M  S  M
MSSSSSM
M  S  M
M  S  M
MMMMMMM

Hello, World!

MMMM
MSSM
MS M
MMMM

MMSMM
M S.M
sSSSS
M S M
MMSMM

MMMMMMM
M  M  M
MMMMMMM
M  M  M
MMMMMMM

MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM
M M M M
MMMMMMM`.split`

`.forEach(test=>{console.log(test,f(test));});


Nice approach! Could you use r=s.search('\n') instead of split / length?
Arnauld

@Arnauld awesome suggestion, thanks!
Patrick Roberts

The parenthesys on s=>!s.split`S`.join`M`.search([...]) can be removed, without causing syntax errors.
Ismael Miguel

@IsmaelMiguel correct, but then the string gets passed as a template, which invalidates the implicit RegExp
Patrick Roberts

That sucks... I really wasnt expecting that...
Ismael Miguel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.