| Bytes | Lang | Time | Link |
|---|---|---|---|
| 104 | Setanta | 240723T210223Z | bb94 |
| 026 | Haskell + hgl | 220617T054340Z | Wheat Wi |
| 021 | Regex Perl / PCRE | 220617T224926Z | Deadcode |
| 022 | Yacc | 220620T170832Z | ais523 |
| 043 | Rust compiletime | 220620T163413Z | ais523 |
| 178 | C | 220618T113138Z | yuval |
| 029 | Charcoal | 220618T090932Z | Neil |
| 023 | Retina 0.8.2 | 220616T220102Z | Neil |
| 077 | Javascript | 220617T232205Z | 2pichar |
| 084 | SNOBOL4 CSNOBOL4 | 220617T195143Z | Giuseppe |
| 023 | Stax | 220616T220225Z | recursiv |
| 031 | Wolfram Language Mathematica | 220617T181125Z | att |
| 031 | Raku | 220617T145508Z | Silvio M |
| 000 | Haskell GHCi | 220616T211153Z | matteo_c |
| 101 | R | 220617T082705Z | Cong Che |
| 010 | 05AB1E | 220617T080444Z | Kevin Cr |
| 005 | APL Dyalog Unicode | 220616T210836Z | Adá |
| 406 | Rust | 220617T063443Z | MG lolen |
| 019 | Python | 220617T005155Z | loopy wa |
| 005 | Factor + json.reader | 220617T021513Z | chunes |
| 029 | Curry PAKCS | 220617T014250Z | alephalp |
| 000 | PARI/GP | 220617T000422Z | alephalp |
| 008 | Vyxal P | 220616T234826Z | lyxal |
| 036 | Whython | 220616T213814Z | DLosc |
| 070 | Python3 | 220616T205023Z | Ajax1234 |
| 010 | JavaScript | 220616T211049Z | Unmitiga |
Setanta, 108 105 104 bytes
gniomh(s){nuair-a s&fad@s>2{t:=athchuir@(athchuir@s("[[],[","[["))("[[]]","[]")s=s!=t|0&t}toradh"[]"==s}
While the string is more than 2 characters long, make the following replacements:
- Remove the first element (if
[]) of a list with more than one element:[[],[→[[ - Remove the first element (if
[]) of a list with one element:[[]]→[]
If we enter a fixed point, then return false. Otherwise, the only valid string with at most 2 characters is [].
Haskell + hgl, 26 bytes
f=opn*>isP f ʃ_*>cls
x1 f
Uses the characters ()_.
Creates a parser object and uses x1 to determine if some input matches the parser.
25 bytes
f('(':')':>x)=mF f$' '|\x
Uses the characters () .
Returns True on valid inputs and errors on invalid ones.
This version is 1 byte shorter but I'm not sure if it's allowed by the rules. If you call the function expecting a bool, e.g.
f x::Bool
It will work. However by default ghci infers () as the type, which does not meet the output specifications of the challenge. So you need to call it either with a type signature or in a context in which ghc can infer it's type to be Bool.
Relfections
- hgl has a
unwordsfunction. It could be made a pattern like in the curry answer:f('(':')':>Uw x)=mF f x - It's rather annoying that
wR, thewordsfunction trims leading spaces. There is a 23 byte answer which would work ifwRdidn't do this.f('(':')':>x)=mF f$wR x mFshould have an infix.isPshould have an infix.isPshould have a flip.
2024-07-23 update, 18 bytes
cPx"{((/r,)*/r|)}"
Uses the characters {},
The library has been updated with regular expressions which allow this to be much shorter. This version uses a recursive regex
Regex (Perl / PCRE), 21 bytes
^(<(((?1),)*(?1))?>)$
Uses the characters <>,. (Avoids [] as they would require being \-escaped.)
Try it online! - Perl
Try it on regex101 - PCRE1
Try it online! - PCRE2 v10.33
Attempt This Online! - PCRE2 v10.39+
Like the regex in the Raku answer, this uses recursion. Unlike Raku, standard regex has no concept of "separator" characters, so there's no concise way of implementing a comma-separated list, and the recursive call (?1) needs to be in two places.
There are many alternatives of the same length:
Regex (Perl / PCRE), 21 bytes
^(<((?1)(,(?2))?)?>)$
Also uses the characters <>,.
Try it online! - Perl
Try it online! - PCRE
Uses (?2) recursion instead of * repetition for the comma-separated list.
Regex (Perl / PCRE), 21 bytes
^(a((?1),)*(?1)?\Bz)$
Uses the characters az, in order to take advantage of the \B non-word-boundary assertion.
Try it online! - Perl
Try it online! - PCRE
Without the use of \B, there would be nothing stopping a comma-separated list ending in a comma from being accepted, as the (?1)? is optional independently of whether or not ((?1),)* matched anything.
The \B prevents this; if any list ended with a comma, the sequence ,z would be part of it, so all we need to do is prohibit this sequence. \Bz accomplishes this, as a and z are word-characters but , is not, thus there is no word boundary in the middle of az or zz but there is one in ,z.
Regex (Perl / PCRE), 21 bytes
^(a\B(?1)?(,(?1))*z)$
Also uses the characters az,. Mirror version of the above.
Try it online! - Perl
Try it online! - PCRE
Regex (Perl / PCRE), 21 bytes
^(<(\B(?1)|\b,\B)*z)$
Try it online! - Perl
Try it online! - PCRE
Uses the characters <z, in order to take advantage of the \b and \B word- and non-word-boundary assertions.
Regex (Perl / PCRE), 21 bytes
^(a((?1)\B|\B,\b)*>)$
Uses the characters a>,. Mirror version of the above.
Try it online! - Perl
Try it online! - PCRE
Regex (.NET), 35 33 29 bytes
^((a)+(?<-2>z)+(?(2),\b|$))+$
Uses the characters az, in order to take advantage of the \b word-boundary assertion.
Based on the old 35 byte one-liner in Neil's Retina answer.
-1 byte by using the characters <>, instead of [],, because [ needed to be \-escaped
-1 byte by using an illegal character, instead of $., as the impossible condition to assert Group 2 being empty at the end
-4 bytes by using the characters az, instead of <>,, obviating the need for explicitly asserting Group 2 is empty at the end
^ # Assert that we're at the beginning of the string.
(
(a)+ # Capture at least one "a" on the Group 2 stack.
(?<-2>z)+ # Match at least one "z", popping an entry from the Group 2
# stack for each one we match.
(?(2),\b|$) # If the Group 2 stack is non-empty, match a "," followed by a
# word boundary (since "," is a non-word character, this means
# it must be followed by a word character, i.e. [0-9A-Za-z_]),
# else assert that we're at the end of the string.
)+ # Loop the above at least 1 time.
$ # Assert that we're at the end of the string. The Group 2
# conditional at the end of the above loop guarantees that the
# only way to end the loop at the end of the string is for
# Group 2 to be empty, due to the "\b" in its non-empty
# clause. So there's no need to explicitly assert here
# something like "(?(2)$.)" (which would assert something
# impossible in the case that Group 2 is non-empty).
Note that if the <>, characters are still used, it can be 32 bytes:
^((<)+(?<-2>>)+(?(2),(?!$)|$))+$
\$\large\textit{Anonymous functions}\$
Perl, 33 bytes
sub{pop=~/^(<(((?1),)*(?1))?>)$/}
R, 49 48 44 39 bytes
\(L)grepl('^(<(((?1),)*(?1))?>)$',L,,1)
-1 byte thanks to Giuseppe
-4 bytes by using grepl() instead of sum(grep()) or any(grep())
-5 bytes by using a new anonymous function syntax introduced in R v4.1.0
PowerShell, 46 42 bytes
$args-match'^((a)+(?<-2>z)+(?(2),\b|$))+$'
Yacc, 22 bytes
s:'['b']'b:l|l:s|s','l
Try it online! (note: Yacc is installed on TIO, but doesn't have a TIO wrapper, so I had to write one myself; I also wrapped the function into a full program. The wrapper outputs by exit code.)
Function submission. (I originally forgot PPCG allowed functions to be sumbitted, so the first version of this was a full program with a lot of boilerplate. Submitting it as a function obviously makes more sense.)
This is a function/parser named s that reads input from yacc's usual input source. It does the yacc equivalent of throwing an exception if the input is not a list, or returns normally if the input is a list. (It doesn't explicitly return a value – Yacc uses "implicit int" like in C, so these parsers are technically returning integers, but we don't set the return value to anything in particular.)
This function uses [, ], , as suggested by the question, but could easily be adapted to use any three characters (although some choices would make the program longer due to needing to be escaped).
Explanation
Yacc is a language for writing parsers; you provide a description of syntactically legal input and it generates a parser for you. This program contains three parsers, s, b and l:
s:'['b']'b:l|l:s|s','l
s: An "s" is
'[' an opening square bracket
b then a "b"
']' then a closing square bracket;
b: A "b" is
l| either an "l", or {an empty string};
l: An "l" is
| either
s an "s"
| or
s an "s"
',' followed by a comma
l followed by another "l"
A direct translation of the question to Yacc would be s:'['']'|'['l']'l:s|s','l, but factoring out the brackets from the two branches of s into a separate rule b allows the program to be a byte shorter.
Yacc programs don't normally look this condensed; they're normally written with more whitespace, and semicolons between rules. However, the semicolons are optional, and whitespace is optional except between two identifiers (and I arranged this program so that it never had two identifiers in a row, allowing all the whitespace to be golfed off). A weird side effect of this flexibility is that Yacc's syntax is very hard to parse due to false positives (e.g. b:l|l is a valid rule, but b:l|l: has to be parsed as b:l|; l: despite that), and in fact, it isn't sufficiently powerful to write a parser for its own syntax (it's powerful, but its syntax is weird enough that it needs even more power to parse). It's helpful in golfing, though!
Rust (compile-time), 43 bytes
macro_rules!v{([$($a:tt),*])=>($(v!{$a})*)}
This is a Rust macro (the compile-time equivalent of a function) named v!. Use as v!{…}, where … is the string you want to check (without delimiters – string literals are opaque to the usual sort of Rust macro). I used [, ,, ] as in the question, although this could easily be adapted to use almost any Rust token in place of , and/or () rather than [].
Outputs via error/non-error: an invalid list causes a compile time error, a valid list causes no error (and the macro expands to an empty declaration).
Explanation
macro_rules!v{([$($a:tt),*])=>($(v!{$a})*)}
macro_rules!v{ } Macro declaration
([ ]) Match something surrounded by []
$( ) * which consists of 0 or more
$a:tt balanced token trees $a
, separated by commas
=>( ) and replace it with,
$( $a )* for each $a,
v!{$a} a recursive call to v!{$a}
We verify the structure of the outermost list, then recurse into the inner lists to make sure that those are also correct. Rust macros have a :tt builtin that matches brackets, which makes it easy to use the strategy of parsing out the elements first and recursing over them afterwards. The base case of the recursion is that when we match a zero-length list, the replacement expands into zero macro calls, because the loop in the replacement runs for zero iterations.
Note that Rust normally allows a trailing comma on lists, so we do actually have to write our own verification macro rather than using something that Rust has predefined.
C, 178 bytes
int v(char *s){int d=1;for(s++;*s!='\0';s++){if(d==0)return 0;if(*s=='{')d++;if(*s=='}'){d--;if(*(s+1)=='{')return 0;}if(*s==','&&!(*(s-1)=='}'&&*(s+1)=='{'))return 0;}return!d;}
Charcoal, 29 bytes
Fθ≔⪫⪪⪫⪪θ[[]]¦[]¦[[],[¦[[θ⁼θ[]
Try it online! Link is to verbose version of code. Outputs a Charcoal boolean, i.e. - for valid, nothing for invalid. Explanation: Boring port of @recursive's Stax answer.
Fθ
Repeat more than enough times...
≔⪫⪪⪫⪪θ[[]]¦[]¦[[],[¦[[θ
... remove the first or only element of a list if it's an empty list.
⁼θ[]
Check that we're left with an empty list.
Less boring 34 byte solution:
›⬤⁺,θ№,[[]],⁺ι§⁺θ,κ⊙θ∧κ⁼№…θκ[№…θκ]
Try it online! Link is to verbose version of code. Outputs a Charcoal boolean, i.e. - for valid, nothing for invalid. Explanation:
, Literal string `,`
⁺ Concatenated with
θ Input string
⬤ All characters satisfy
,[[]], Literal string `,[[]],`
№ Contains
ι Current character
⁺ Concatenated with
θ Input string
⁺ Concatenated with
, Literal string `,`
§ Indexed by
κ Current index
› And not
θ Input string
⊙ All characters satisfy
κ Current index
∧ Logical And
№ Count of
[ Literal string `[`
…θκ In current prefix
⁼ Equals
№ Count of
] Literal string `]`
…θκ In current prefix
Implicitly print
Note that by default Charcoal tries to parse an input in a JSON-like format so to defeat that you need to wrap the input in JSON i.e. prepend [" and append "].
Retina 0.8.2, 39 35 23 bytes
<<>>
<>
}`<<>,<
<<
^<>$
Try it online! Takes <,> characters but link is to test suite that converts from [] for convenience. Explanation: Port of @recursive's Stax solution.
<<>>
<>
Remove an empty list that's the only element of a list.
<<>,<
<<
Remove an empty list that's the first element of a list.
}`
Repeat until there are no more elements to remove.
^<>$
Check that we finished with an empty list.
Escaping means that it takes 29 bytes to use [,] characters:
\[\[]]
[]
}`\[\[],\[
[[
^\[]$
Try it online! Link includes test cases.
Previous 35-byte one-liner:
^((\[)+(?<-2>])+(?(2),|$))+(?(2)$.)
Try it online! Link includes test cases. Explanation:
^(
Start matching at the beginning of the string.
(\[)+
Count the number of [s.
(?<-2>])+
Count some ]s, but no more than the number of [s so far.
(?(2),|$)
After each run of [s and ]s, if there are still unbalanced ]s, then there must be a , next, otherwise we must be at the end of the string.
)+
Repeat as many times as necessary.
(?(2)$.)
Check that there weren't more [s than ]s.
Javascript, 79 78 77 bytes
f=s=>eval("r=/(<(<>,)+<>>)|(<<>>)/;r.test(s)&&f(s.replace(r,'<>'))||s=='<>'")
uses the characters <,> and returns true/false
saved 1 byte thanks to @RadvylfPrograms
saved 1 byte by using recursion and joining expressions
SNOBOL4 (CSNOBOL4), 84 bytes
L ='[]' | ('[' *L ARBNO(',' *L) ']')
INPUT POS(0) L RPOS(0) :F(END)
OUTPUT =1
END
Prints 1 for truthy and nothing for falsey.
Recursive pattern matching is pretty slick.
Stax, 22 23 bytes
Ç╥╫ÜΦªt┌¢♣☺¢+╬ï▼6◘-6¡Zq
Approach
- Repeatedly
- Replace
"[[]]"with"[]" Replace",[]]"with"]"- Replace
"[[],["with"[["
- Replace
- Is result equal to
"[]"?
Wolfram Language (Mathematica), 31 bytes
{Times=N}~Block~ToExpression@#&
Uses {,}. Returns by presence/absence of an error.
Missing arguments are interpreted as Null, but will raise an error message.
Juxtaposition is a valid operation (Times), so we use Block to turn it into N. Since N only handles one or two arguments, it errors when passed three or more arguments. When N has exactly two arguments, the second argument must be of the form precision or {precision,accuracy}, where precision and accuracy are numbers. Input is guaranteed to never have numbers, so the 2-argument form is also guaranteed to error.
Raku, 31 bytes
{($^a~~/"["<~~>*%",""]"/)~~$^a}
This is a function which takes the string as input and produces typical Raku Booleans (True or False).
Let's break it down. First, better spacing
{ ($^a ~~ /"[" <~~> * % "," "]"/) ~~ $^a}
First, $^a is a sort of special syntax for declaring lambdas without explicitly mentioning their arguments, so this is equivalent to the single-argument function
-> $a { ($a ~~ /"[" <~~> * % "," "]"/) ~~ $a}
Now, ~~ is a terribly clever thing in Raku called the smart-match operator. At a high-level, it's going to evaluate its right-hand side and then call the .ACCEPTS method on the right-hand side with the left-hand side as argument. We use it twice here: The leftmost application is on a regex object and will do a regular expression match. The rightmost application is on the input string and will perform ordinary string equality.
We're going to do a regex match against that regular expression enclosed in /. That match will return either a special regex Match object, or Nil if the match fails. We then compare that for string equality. A Match object stringifies as the substring it matched, so we're simply checking whether the match that was found is in fact the whole string. Nil, on the other hand, stringifies to "", and as per the comments the empty string is not valid input, so $^a will never compare equal to that.
Now let's look at the regex itself.
/"[" <~~> * % "," "]"/
If you haven't seen Raku regexes before, it may be best to forget everything you know about regular expressions, because the syntax is only superficially similar. Anything enclosed is quotes is matched literally, so "[", ",", and "]" are matched literally. * works like it does in most regex languages: It's the Kleene star and matches zero or more. % is a modifier on * which delimits multiple matches by the thing on its right (in our case, a comma). Finally, <~~> is the recursive matcher, which matches the entire regular expression again. So, in summary
/ # Start of regex
"[" # Match an opening bracket
* # Then match zero or more...
% "," # delimited by commas...
<~~> # of the current regular expression recursively
"]" # Finally, match a closing bracket
/ # End of regex
Haskell (GHCi), 0 bytes
Takes input from stdin, returns truthy iff stderr is empty.
R, 101 bytes
\(x,y='',s=\(p,b,x)sub(p,b,x,,,T)){while(y!=(y=x)){x=s('[[],[]','[[]',y);x=s('[[]]','[]',x)};y=='[]'}
Text replacement solution, '[[],[]' -> '[[]' '[[]]' -> '[]'
Earlier attempts at this were using far too many \\ to escape [/].
05AB1E, 10 bytes
„,]å≠i.E}˜
No error as truthy; error (** (Protocol.UndefinedError) protocol Enumerable not implemented for ...) as falsey.
Try it online or verify all test cases. (Wrap the single TIO input in """ quotes to get the input as string, instead of implicitly as (possibly faulty) list.)
Explanation:
i } # If
# the (implicit) input-string
≠ # does NOT
å # contain the substring
„,] # ",]":
.E # Evaluate the (implicit) input-string as Elixir
# (implicit else)
# (implicitly use the input-string)
˜ # Flatten (results in [] for lists; or an error for strings)
The if-statement is for edge-case [[],], which unfortunately is a valid Elixir/Python list that evaluates to [[]]. If it wasn't for this test cases, .E˜ would have been enough in the new version of 05AB1E: verify all test cases.
And it could even have been 1 byte in the legacy version of 05AB1E using the -e/--eval flag: verify all test cases.
APL (Dyalog Unicode), 5 bytes
Anonymous tacit prefix function taking input as [,] with output by erroring/not erroring.
⎕JSON
Try it online! (wrapped in error trapping code to give 0 when erroring and 1 when not erroring)
Attempts to convert from JSON.
Rust, 406 bytes
fn a(d:&str)->bool{let c=d.chars().collect::<Vec<_>>();let c=c.as_slice();let b=c.iter().fold((0,0),|mut a,&i|{if i=='['{a.0+=1}else if i==']'{a.1+=1}a});let m=c.windows(2).any(|a|a[0]==']'&&a[1]=='[');let mut j=false;c.iter().fold(0,|a,&i|{if i=='['{a+1}else if i==']'{a-1}else if i==','&&a==0{j=true;a}else{a}});let o=c.windows(3).find(|a|a[1]==','&&(a[0]!=']'||a[2]!='['));b.0==b.1&&!m&&!j&&o.is_none()}
Not a golfing language by any means, but it's worth giving it a try. It can be golfed down a lot as it's currently using a really naïve approach.
Python, 19 bytes (@dingledooper)
eval("set"+input())
Old Python, 22 bytes
def f(s):eval("set"+s)
This takes a string composed of the characters "(+)" and signals by erroring/not erroring.
How?
Why can't we just use eval?
- trailing commas
- missing outer brackets
Both will be tolerated by eval.
To address 1 we replace , with +.
To address 2 we switch from [] to () and prepend set.
Old Python, 28 bytes
def f(s):eval(s+","+s[1:-1])
This takes a string composed of the characters "[+]" (note that we have cheekily but legally replaced "," with "+") and signals by erroring/not erroring.
I'm not 100% sure this is correct but it works on all test cases.
Curry (PAKCS), 29 bytes
f('[':unwords a++"]")=all f a
Uses a space instead of ,. Returns True for truth, and nothing otherwise.
Curry (PAKCS), 53 bytes
f('[':a++"]")=g a
f"[]"=1
g(a++',':b)=f a*g b
g a=f a
Returns 1 for truth, and nothing otherwise.
PARI/GP, 0 bytes
Takes input from stdin, returns truthy iff stderr is empty.
PARI/GP automatically parses and evals everything from the stdin.
Vyxal P, 8 bytes
øBEÞfSȧ=
Still shorter than JavaScript! The link is a test suite, hence the additional flags - those are just to wrap all inputs into a list (a) and to treat all inputs as strings (S with updot - it's the equivalent of wrapping every input in quotation marks).
Explained
øBEÞfSȧ=
øBE # wrap the input in square brackets and try to evaluate it as a python literal
Þf # attempt to flatten the list by a level - if the input was listlike (that is, valid for this challenge or valid as a python tuple), this will undo the `øB`. Otherwise it leaves the string as is
S # convert the result of the above to string. The P flag makes it so that square brackets and commas are used for lists instead of the angular brackets and pipes Vyxal uses.
ȧ= # remove any whitespace and test to see if this new string is equal to the original input.
Whython, 36 bytes
lambda s:eval(s)>=[]!=",]"not in s?0
Returns True for valid lists, False or 0 for invalid lists. Attempt This Online!
Explanation
lambda s:eval(s)>=[]!=",]"not in s?0
lambda s: # Anonymous function that takes s, a string
eval(s) # Python eval
>=[] # True if the result is a list, error if tuple
!= # Keep the comparison chain going
",]"not in s # Check that there are no extra commas
?0 # If anything raised an error, return 0
Python3, 70 bytes:
def f(s):
try:return s==str(eval(s)).replace(' ','')
except:return 0