23
\$\begingroup\$

Part of Advent of Code Golf 2021 event. See the linked meta post for details.

The story continues from AoC2015 Day 5, Part 2.


Santa needs help figuring out which strings in his text file are naughty or nice. He has already tried two sets of rules, but they're totally ad-hoc, cumbersome, and janky, so he comes up with a single rule that will rule them all:

  • A string is nice if it contains a pattern n..i..c..e where the letters n, i, c, and e are equally far apart from each other.

In other words, a string is nice if, for some non-negative integer x, it matches the regex n.{x}i.{x}c.{x}e.

Input: A nonempty string of lowercase English letters (you can choose to take uppercase instead).

Output: Truthy or falsy indicating whether the input string is nice or not. You can choose to

  • output truthy/falsy using your language's convention (swapping is allowed), or
  • use two distinct, fixed values to represent true (affirmative) or false (negative) respectively.

Standard rules apply. The shortest code in bytes wins.

Test cases

Truthy:

nice
noizcue
aaaaanicezzzz
nnicceee

Falsy:

x
abc
ince
nnicce
nniccexe
precinct
\$\endgroup\$
3
  • 7
    \$\begingroup\$ Suggested test case: precinct. Because my original Charcoal approach found x=-2. \$\endgroup\$
    – Neil
    Dec 2 '21 at 0:24
  • 4
    \$\begingroup\$ nniiccxe can be a nice test case for catching code that gives up after the first n it sees. \$\endgroup\$
    – Ezhik
    Dec 2 '21 at 4:25
  • 1
    \$\begingroup\$ Your regex is a little confusing since it seems to imply that the n and the e must be the first and last characters respectively. \$\endgroup\$
    – Wheat Wizard
    Dec 2 '21 at 23:35

28 Answers 28

11
\$\begingroup\$

J, 35 bytes

1 e.[:,'nice'E."1]#~#$"1&><@=\@i.@#

Try it online!

Consider f 'noizcue':

  • <@=\@i.@# Create a bunch of identity matrixes, up to the input length:

    ┌─┬───┬─────┬───────┬─────────┬───────────┬─────────────┐
    │1│1 0│1 0 0│1 0 0 0│1 0 0 0 0│1 0 0 0 0 0│1 0 0 0 0 0 0│
    │ │0 1│0 1 0│0 1 0 0│0 1 0 0 0│0 1 0 0 0 0│0 1 0 0 0 0 0│
    │ │   │0 0 1│0 0 1 0│0 0 1 0 0│0 0 1 0 0 0│0 0 1 0 0 0 0│
    │ │   │     │0 0 0 1│0 0 0 1 0│0 0 0 1 0 0│0 0 0 1 0 0 0│
    │ │   │     │       │0 0 0 0 1│0 0 0 0 1 0│0 0 0 0 1 0 0│
    │ │   │     │       │         │0 0 0 0 0 1│0 0 0 0 0 1 0│
    │ │   │     │       │         │           │0 0 0 0 0 0 1│
    └─┴───┴─────┴───────┴─────────┴───────────┴─────────────┘
    
  • #$"1&> Extend each row to the input length, filling extra rows with zeroes:

    1 1 1 1 1 1 1
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    
    1 0 1 0 1 0 1
    0 1 0 1 0 1 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    
    1 0 0 1 0 0 1
    0 1 0 0 1 0 0
    0 0 1 0 0 1 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    
    1 0 0 0 1 0 0
    0 1 0 0 0 1 0
    0 0 1 0 0 0 1
    0 0 0 1 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    
    1 0 0 0 0 1 0
    0 1 0 0 0 0 1
    0 0 1 0 0 0 0
    0 0 0 1 0 0 0
    0 0 0 0 1 0 0
    0 0 0 0 0 0 0
    0 0 0 0 0 0 0
    
    1 0 0 0 0 0 1
    0 1 0 0 0 0 0
    0 0 1 0 0 0 0
    0 0 0 1 0 0 0
    0 0 0 0 1 0 0
    0 0 0 0 0 1 0
    0 0 0 0 0 0 0
    
    1 0 0 0 0 0 0
    0 1 0 0 0 0 0
    0 0 1 0 0 0 0
    0 0 0 1 0 0 0
    0 0 0 0 1 0 0
    0 0 0 0 0 1 0
    0 0 0 0 0 0 1
    
  • #$"1&> Use those as masks to filter the input (rows of all zeroes elided to save space):

    noizcue
    nice   
    ozu    
    nze    
    oc     
    iu     
    nc     
    ou     
    ie     
    z      
    nu     
    oe     
    i      
    z      
    c      
    ne     
    o      
    i      
    z      
    c      
    u      
    n      
    o      
    i      
    z      
    c      
    u      
    e      
    
  • 1 e.[:,'nice' Is "nice" in any resulting rows?

\$\endgroup\$
9
\$\begingroup\$

05AB1E, 8 bytes

ā€ι»'ŒÁå

Try it online! or Try all cases!

ā         -- range from 1 to the length of the input s
 €ι       -- for each value k in this range, push [s[0::k], ..., s[k-1::k]]
   »      -- join each inner list by spaces and the resulting strings by newlines
    'ŒÁ   -- dictionary compressed word "nice"
       å  -- is this a substring?

Or look at the output of just ā€ι»

\$\endgroup\$
1
  • \$\begingroup\$ Oh, I like that €ι»! :) Looks like I'll have to do this second challenge in another language, though. ;) \$\endgroup\$ Dec 2 '21 at 7:35
7
\$\begingroup\$

JavaScript (ES6), 59 bytes

s=>[...s].some((_,i)=>s.match(`n.{${i}}i.{${i}}c.{${i}}e`))

Try it online!

\$\endgroup\$
7
\$\begingroup\$

Jelly, 17 12 11 bytes

ẆmþJẎ“£uƇ»e

Try it online!

-5 bytes by adapting emanresu A's approach
-1 byte thanks to ovs!

How it works

ẆmþJẎ“£uƇ»e - Main link. Takes a word W on the left
Ẇ           - Contiguous substrings S of W
   J        - Yield the range 1 ≤ i ≤ len(W)
  þ         - For each pair (S, i):
 m          -   Take the ith elements of s
    Ẏ       - Tighten into a list of strings
     “£uƇ»  - Compressed string; "nice"
          e - Is this in the list of strings?
\$\endgroup\$
4
  • \$\begingroup\$ Nice! I was trying to do this but gave up because chaining pain. \$\endgroup\$
    – emanresu A
    Dec 2 '21 at 3:48
  • \$\begingroup\$ @emanresuA Yeah, I messed around with F, and various other vectorising commands before I realised that ) was much simpler. \$\endgroup\$ Dec 2 '21 at 4:11
  • \$\begingroup\$ ẆµmJ) can be shortened to ẆmþJ. \$\endgroup\$
    – ovs
    Dec 2 '21 at 9:19
  • \$\begingroup\$ @ovs Very nice, thanks! For some reason, I couldn't get þ to work - guess I was just doing something wrong :) \$\endgroup\$ Dec 2 '21 at 17:25
7
\$\begingroup\$

Retina 0.8.2, 42 41 bytes

n(.)*i(?<2-1>.)*(?(1)^)c(?<-2>.)*(?(2)^)e

Try it online! Link includes test cases. Edit: Saved 1 byte by porting @ZaelinGoodman's regex. Explanation:

n(.)*i

Match n, followed by x characters, capturing x in capture group 1, followed by i.

(?<2-1>.)*(?(1)^)c

Match x characters by popping from capture group 1, simultaneously capturing into capture group 2, followed by c.

(?<-2>.)*(?(2)^)e

Match x characters by popping from capture group 2, followed by e.

\$\endgroup\$
6
\$\begingroup\$

Vyxal, 10 bytes

ǎ?żvef‛ߦc

Try it Online!

ǎ          # Substrings
   v       # Each...
    e      # Get every nth character for each of...
 ?ż        # 1...input length
     f     # flattened
         c # Includes
      ‛ߦ  # Compressed string 'nice'
\$\endgroup\$
6
\$\begingroup\$

Python, 66, 64, 62, 61 (@dingledooper), 60 bytes (@Kevin Cruijssen)

f=lambda s,i=1:s>s[:i]and('nice'in s[::i])|f(s,i+1)|f(s[i:])

Try it online!

Old version

Old version

Old version

Old version

\$\endgroup\$
5
  • 1
    \$\begingroup\$ 61 bytes: f=lambda s,i=1:""<s[i:]and('nice'in s[::i])|f(s,i+1)|f(s[i:]) \$\endgroup\$ Dec 2 '21 at 5:12
  • 1
    \$\begingroup\$ No improvement in byte-count, but ""<s[i:] is basically an obfuscated i<len(s) ;) \$\endgroup\$ Dec 2 '21 at 16:12
  • 1
    \$\begingroup\$ @KevinCruijssen s>s[:i] I meant s>s[:i]! \$\endgroup\$
    – loopy walt
    Dec 2 '21 at 16:28
  • \$\begingroup\$ @loopywalt Oh, s>s[:i] is a nice improvement! :D You don't really need to credit me though, that's all your own doing. :) \$\endgroup\$ Dec 2 '21 at 17:20
  • \$\begingroup\$ @KevinCruijssen I wouldn't have noticed without your spotting it. \$\endgroup\$
    – loopy walt
    Dec 2 '21 at 17:44
5
\$\begingroup\$

Python 3.8 (pre-release), 72 bytes

lambda s:'nice'in[L:=range(len(s))]+[s[i::j+1][:4]for j in L for i in L]

Try it online!

Same code pattern as yesterday?

\$\endgroup\$
1
  • \$\begingroup\$ Oh, didn't notice that when I wrote the challenge :P \$\endgroup\$
    – Bubbler
    Dec 2 '21 at 1:22
5
\$\begingroup\$

Excel, 78 bytes

=MAX(LET(q,REPT("?",SEQUENCE(LEN(A1))-1),COUNTIF(A1,"*n"&q&"i"&q&"c"&q&"e*")))

Results

Input is in the cell A1. Returns 1 for truthy and 0 for falsey.

  • REPT("?",SEQUENCE(LEN(a))-1) creates an array of strings, all made entirely of ? and varying in length from 1 to the length of the input minus 1. If the input is nice then this returns ['','?','??','???'].
  • LET(q,REPT(~) defines the results of the previous step to be q so we can reference it multiple times later using just the variable name and save bytes.
  • COUNTIF(A1,"*n"&q&"i"&q&"c"&q&"e*") does the actual checking. COUNTIF() accepts wild cards where * is any number of characters (including zero) and ? is exactly one character. Here, we search for something like *n?i?c?e* except the number of question marks varies with the results of the REPT() function. For a four letter word, we will search for all of these combinations: *nice*, *n?i?c?e*, *n??i??c??e*, and *n???i???c???e*.
  • MAX(LET(~,COUNTIF(~))) returns the max value of all those COUNTIF() checks. Since we only ever check one cell in any one of those, the max value will be 1 even if it appears multiple times such as in nicenice or nicenoiocoe.
\$\endgroup\$
4
\$\begingroup\$

TypeScript Types, 313 bytes

//@ts-ignore
type a<S,T>=T extends[infer C,...infer T]?S extends`${0 extends C?string:C}${infer S}`?a<S,T>:0:1;type b<S,T>=a<S,T>|(S extends`${string}${infer S}`?b<S,T>:never);type c<S,T=S,N=[],>=b<S,["n",...N,"i",...N,"c",...N,"e"]>|(T extends`${string}${infer T}`?c<S,T,[...N,0]>:0);type M<S>=1 extends c<S>?1:0

Try it online!

\$\endgroup\$
4
\$\begingroup\$

Python 3, 79 bytes:

import re
lambda s:any(re.findall(('.'*i).join('nice'),s)for i in range(len(s)))

Try it online!

\$\endgroup\$
4
  • \$\begingroup\$ It is recommended to include a link to an online code runner like in this or this, so that others can easily verify that the code actually works. Both tools come with a post formatter too, which is what (almost) every regular user here uses. \$\endgroup\$
    – Bubbler
    Dec 2 '21 at 1:15
  • \$\begingroup\$ You can exclude f= from your score because anonymous lambda is valid. Some golfing tips: writing import re and then re.findall is shorter than from re import*. You can use '.'*i instead of '.{'+str(i)+'}'. And maybe re.search also works with -1 byte? Also check out tips for golfing in python. \$\endgroup\$
    – Bubbler
    Dec 2 '21 at 1:21
  • \$\begingroup\$ @Bubbler Thanks for the tips, I updated the post. \$\endgroup\$
    – Ajax1234
    Dec 2 '21 at 2:09
  • \$\begingroup\$ Lambda answer with an import is often written like this. It is actually 80 bytes (because you need to count the newline too). \$\endgroup\$
    – Bubbler
    Dec 2 '21 at 2:27
4
\$\begingroup\$

Charcoal, 25 16 bytes

⊙θ⊙θ№✂θκLθ⊕μnice

Try it online! Link is to verbose version of code. Outputs a Charcoal boolean, i.e. - if nice, nothing if not. Explanation:

 θ                  Input string
⊙                   Any index satisfies
   θ                Input string
  ⊙                 Any index satisfies
      θ             Input string
     ✂              Sliced in steps of
           μ        Inner index
          ⊕         Incremented
       κ            From outer index
        Lθ          To the end of the string
    №               Count i.e. contains substring
            nice    Literal string `nice`
\$\endgroup\$
4
\$\begingroup\$

Rust (1.53+), 118 111 bytes

|mut s:&str|loop{(1..s.len()).any(|j|s.bytes().step_by(j).take(4).eq(*b"nice"))|(s=="")&&break ""!=s;s=&s[1..]}

Try it online!

This only works on Rust 1.53 and newer as older Rust versions did not implement IntoIterator for arrays, but only for array references, so the eq comparison would end up trying to compare u8 with &u8 which would not work without doing something like b"nice".iter().copied(), a good 15 extra characters.

  • -7 bytes by replacing the outer range with loop (thanks @AnttiP)
\$\endgroup\$
1
  • 1
    \$\begingroup\$ -7 bytes with a loop: |mut s:&str|loop{(1..s.len()).any(|j|s.bytes().step_by(j).take(4).eq(*b"nice"))|(s=="")&&break ""!=s;s=&s[1..]} \$\endgroup\$
    – AnttiP
    Dec 2 '21 at 11:04
4
\$\begingroup\$

PowerShell, 57 bytes

Developed on my own, then discovered as I checked the other solutions before posting that Neil beat me to this method with their excellent Retina Answer; so credit where credit is due.

Outputs False for Nice, and True for Naughty.

!($args-match'n(.)*i(?<2-1>.)*(?(1)$)c(?<-2>.)*(?(2)$)e')

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ I may have beaten you to the method but I need to learn that tip about counting the same balancing group twice. \$\endgroup\$
    – Neil
    Dec 18 '21 at 10:02
3
\$\begingroup\$

Pari/GP, 71 bytes

s->sum(i=1,#s,sum(j=1,(#s-i)\3,[Vec(s)[i+j*k]|k<-[0..3]]==Vec("nice")))

Try it online!

Outputs the number of "nice" patterns, truthy when this number is nonzero.

\$\endgroup\$
3
\$\begingroup\$

Java 8, 94 bytes

s->{int i=s.length();for(;!s.matches(".*n.{"+i+"}i.{"+i+"}c.{"+i+"}e.*")&&i-->0;);return i<0;}

Outputs false for truthy results and true for falsey results.

Try it online.

Explanation:

s->{                // Method with String parameter and boolean return-type
  int i=s.length(); //  Start `i` at the length of the input-String
  for(;             //  Continue looping as long as:
      !s.matches(".*n.{"+i+"}i.{"+i+"}c.{"+i+"}e.*")
                    //   We haven't found "nice" yet (with `i` delimited chars)
      &&i-->0;);    //   And `i` hasn't reached 0 yet
                    //   (Decreasing `i` by 1 after every check with `i--`)
  return i<0;}      //  After the loop: return whether `i` is -1

Java's String#matches checks the entire String with implicit leading ^ and trailing $, hence the need for the leading/trailing .* in the regex-check.

\$\endgroup\$
3
\$\begingroup\$

Zsh, 37 bytes

repeat $#1 {<*~*n$2i$2c$2e*;2+=?;}>$1

Attempt This Online!

Outputs via exit code: 0 for naughty, 1 for nice. Requires options extendedglob errexit globsubst.

Explanation:

  • >$1: create a file named {input}

  • repeat $#1: do {length of input} times:

    • <*~: search for a file not matching:
      • *n$2i$2c$2e*: construct a pattern using the variable $2
        • over the course of the loop, this makes *nice* *n?i?c?e* *n??i??c??e* etc.
        • * matches anything; ? matches any single character. These patterns match all possible "nice" strings
    • errexit: if there was no non-matching file (i.e., *n$2i$2c$2e* did match), then exit the program with code 1
    • append ? to the variable $2 (which starts out empty) to construct the new level of pattern
  • I used a numbered variable $2 rather than a named variable because it can be placed directly adjacent to a letter (like $2i)

  • globsubst is needed so that $2 can be treated as a pattern, not a literal string

  • extendedglob is needed to enable the ~ pattern negation syntax

Combining errexit with pattern negation basically does a De Morgan's Law transformation on the whole program, matching any of the constructed patterns.

\$\endgroup\$
3
\$\begingroup\$

Ruby, 56 ... 51 bytes

->s{(0..s.sum).any?{|x|s[/#{%w(n i c e)*(?.*x)}/]}}

Try it online!

\$\endgroup\$
2
\$\begingroup\$

Factor + math.unicode, 75 74 bytes

[ all-subseqs [ dup length 4 / ⌈ group flip first "nice">array = ] ∃ ]

Try it online!

all-subseqs [ ... ] ∃      ! Is there any subsequence in the input that...
dup length 4 / ⌈ group     ! ...when divided into quarters, with last quarter cut off...
flip first "nice">array =  ! ...the first column of these parts is equal to "nice"?
\$\endgroup\$
2
\$\begingroup\$

PHP, 80 bytes

for($i=-1;$argn[++$i];)preg_match("~n.{{$i}}i.{{$i}}c.{{$i}}e~",$argn)?die(A):0;

Try it online!

Port of Arnauld's answer, terminates with a truthy A if nice, or a falsy empty string if naughty.

Note: one of the input lines has to be a falsy one if you want to see the test cases, as the program terminates if truthy (for the same reason the die in the test cases is changed to a print)

\$\endgroup\$
1
\$\begingroup\$

R, 78 bytes

Or R>=4.1, 71 bytes by replacing the word function with \.

function(s){for(i in 0:nchar(s))F=F|grepl(gsub("x",i,"n.{x}i.{x}c.{x}e"),s);F}

Try it online!

\$\endgroup\$
1
\$\begingroup\$

ayr, 29 bytes

Thanks to this great answer for the method! I've never been very good at algorithms, so this was a great help.

v./'nice'E.&:,]#\:#$@1"i:&~&#

Try it!

Glad I saw this challenge, I have only just implemented E., nice to see it's already coming in handy!

Explained

i:&~&# List of identity matrices of increasing size, up to the length of the string

#$@1" For each of these matrices, extend it to match the size of the string

]#\: Then filter the string using each row of the identity matrices as a boolean mask.

'nice'E.&:, Flatten this to a single string, find matches of the string nice

v./ Or reduce. Is there a match?

\$\endgroup\$
1
\$\begingroup\$

Julia 1.0, 65 bytes

!x=any(i->x>replace(x,Regex(join("nice",".{$i}"))=>1),keys(x).-1)

Try it online!

\$\endgroup\$
1
\$\begingroup\$

SnakeEx, 30 bytes

m:n{s<>}i{s<>}c{s<>}e
s{P0}:.*

Matches (at least once) for truthy; doesn't match for falsey.

Try it here! No permalinks, so you'll need to enter the input and code yourself. (FYI: Newlines in the text boxes display correctly in Chrome but not in Firefox.) You can run all of the test cases at the same time; the program will find one match for each truthy case.

Explanation

s{P0}:.*
s    :    Define snake s as
      .*  Match any run of 0 or more characters
 {  }     With these parameters:
  P        After matching, move the location pointer to the end of the match
   0       All matches of s are in group 0, meaning they must have the same length

m:n{s<>}i{s<>}c{s<>}e
m:                     Define main snake m as
                       Start out moving rightward (implicit)
  n                    Match "n"
   {s  }               Match snake s
     <>                in the same direction (rightward)
        i{s<>}         Match "i" followed by snake s again
              c{s<>}   Match "c" followed by snake s again
                    e  Match "e"
\$\endgroup\$
1
\$\begingroup\$

PowerShell, 53 bytes

!(($t=$args)|?{$i++;"$t"-match"n.{$i}i.{$i}c.{$i}e"})

Try it online!


and 57 bytes alternatives:

!(($t=$args)|?{"$t"-match('nice'-replace'\B',($s+='.'))})

Try it online!

!(($t=$args)|?{"$t"-match((echo n i c e)-join($s+='.'))})

Try it online!

see also Zaelin Goodman's answer.

\$\endgroup\$
0
\$\begingroup\$

BQN, 44 bytes

{∨´("nice"≡/⟜𝕩)¨∾{(↕l)⌽¨<l↑∾4⥊<𝕩↑1}¨1+↕l←≠𝕩}

Try it!

Not sure if J can be ported here, but I'm sure there's much scope for golfing.

\$\endgroup\$
0
\$\begingroup\$

Wolfram Language (Mathematica), 68 bytes

Array[c=Characters@#;""<>c[[Span@##]]==="nice"&,0{,,}+Tr[1^c],1,Or]&

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Haskell + hgl, 44 bytes

ay(Uc mp<bm(lt2<l<nb<δ)(eq"nice")<uz)<ss<eu

Explanation

Here we are looking for the subsequence "nice" where all the elements are evenly spaced. So the first things we do to the input are, pair it with it's indexes and get all possible subsequences.

ss<eu

Now that we have these we want to check if any meet the criteria. So we use ay for any.

ay ... <ss<eu

Now we want to check two separate things here. One is that the values are equal to nice and the second is that the indexes are evenly spaced. So it would be nice to unzip the two parts e.g. [(0,'n'),(3,'i'),(4,'c'),(9,'e')] -> ([0,3,4,5],"nice").

ay(...<uz)<ss<eu

Now lets formulate our two tests. One is very easy:

eq"nice"

So now we want to determine if the indices are evenly spaced. We use δ to determine the distances, then to check if the list only contains one value we do lt2<l<nb, that is we nub the list and check the length is less than 2.

lt2<l<nb<δ

Now we use bm to map the two functions across our tuple:

ay(...<bm(lt2<l<nb<δ)(eq"nice")<uz)<ss<eu

Lastly we convert this tuples booleans to a single boolean with Uc mp, which is and (mp) uncurried (Uc).

ay(Uc mp<bm(lt2<l<nb<δ)(eq"nice")<uz)<ss<eu

Reflection

With that I'd like to reflect a bit on ways we might improve hgl in light of this. It doesn't score very well here, 44 bytes is pretty long. There are a couple of things that could be improved.

  • lt2<l<nb is really long to determine if a list is unique. This should probably be 1 function.
  • Although it wouldn't help with the size here there probably should be some way to get subsequences of a particular size. We almost had to get all the subsequences and then filter by size. It also would make this much faster.

Haskell + hgl, 47 bytes

Here's it solved with the parsing library

pP$lF bn_(h'*>ʃn*>l<h')$is(rM hd)$p<ʃ<p<"ice"

This is a good deal faster than the previous solution since it doesn't calculate every single subsequence.

Explanation

Let's start with the string we are looking for:

"nice"

Now we want to parse these letters with things in between them. It would be nice to use an is which intersperses elements in a list. So lets turn these into parsers:

χ<"nice"

This creates a list of parsers each which parses a single letter. Now we want to intersperse it with something that parsers that all parse the same amount of characters. hd parses a single character so rM hd takes a number and parses exactly that many of the character.

However this is a function, not a parser so we can't intersperse it with our parsers. We could try to supply the argument first and then intersperse but it's easier to just make our char parsers into functions. They take a single argument, a number, and just ignore it. To do this we just map p, (short for const) across the list.

p<χ<"nice"

However we can't intersperse yet. Unfortunately the parsers for rM hd return a string while our other parsers return a character. Even though we don't care about the results we can't put them in a list together. So we have to change one of them. It's fairly easy to just make our char parsers into string parsers:

p<ʃ<p<"nice"

p turns the char into a string, ʃ turns the string into a parser p turns the parser into a parser function. Now we can intersperse:

is(rM hd)$p<ʃ<p<"nice"

At this point we have a list of functions to parsers. We would like to supply all these parsers with the same argument since the gaps need to be the same sizes.

There are a couple of ways to do this. The first way that springs to mind is to use a traverse. sQ will pull all the arguments outside and it's super cheap. But it gets a little messy instead we can use a fold.

bn_ is a sort of chaining operator like >>=.

bn_ :: Monad m => m a -> (a -> m b) -> m a

We can use a fold across the list to then chain everything together.

lF bn_ :: (Foldable t, Monad m) => m a -> t (a -> m b) -> m a

So if we apply this to what we have:

ghci> :t F(lF bn_)$is(rM hd)$p<ʃ<p<"nice"
F(lF bn_)$is(rM hd)$p<ʃ<p<"nice"
  :: (Integral i, Ord i) =>
     Parser (List Prelude.Char) i -> Parser (List Prelude.Char) i

This takes a parser which produces the initial integer and gives us back the parser we want.

Now the question is "How do we figure out which integer?" We could try and have it do something like, guess every size less than the list to see, but the best way seems to be to break the n off of nice and get the number just as the size of the first gap after the n.

To parse an n we can do:

ʃn

Then after it we want a known number of characters. h' will parse any number of characters. And l< will convert that into a length.

ʃn*>l<h'

And we also have to allow for arbitrary characters before the first n:

h'*>ʃn*>l<h'

So now we slot that in:

lF bn_(h'*>ʃn*>l<h')$is(rM hd)$p<ʃ<p<"ice"

And we have a full parser. To turn that parser into a function we use pP, which returns true whenever there is a parse even if it doesn't consume the entire string.

Reflection

The parsers are very powerful but disappointingly parser based answers are usually a bit shy of the non-parser answer. Here it's 3 bytes, which is the cost to invoke a parser.

  • It's probably going to be eventually necessary to add a regex processor. Sometimes we will beat regex, but it would really open up a lot of opportunities.
  • I should probably make a parser evaluator that matches the parser anywhere in the input. Would save a h'*> here and probably in a few other places.
  • lMy could have been useful here but l<h' was shorter than lMy hd. Not sure if this is actionable, even shortening lMy to one character would leave them tied. But it's of interest to note.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.