Is it a circumfix?

Introduction

We all know prefixes and suffixes. But there are other types of affixes that exist too. Such as circumfixes, a type of affix that has two parts, one of which is a prefix and another of which is a suffix. Figuring out whether some string is a prefix or a suffix of some other string is easy, but what about figuring out whether it might be a circumfix?

That is today's challenge - create a program or function which takes two strings as input, and determine whether the first is a circumfix of the second.

For the purposes of this challenge a string i1 is a circumfix of another string i2 if and only if there exists some non-empty string j which is a contiguous substring of i2 such that removing j from i2 results in i1, and j is neither a prefix nor a suffix of i2 (if it is, you don't have a circumfix, you just have a suffix or a prefix respectively).

For example, "fog" is a circumfix of "frog", because removing "r" from "frog" produces "fog".

When given valid input, your program either needs to output a single consistent value of your choice if the first input string is a circumfix of the second, and any other value if it is not, or vice versa. For example, you may decide have your program output 6 when the first string is a circumfix of the second, in which case any output except 6 is acceptable when it is not.

This is , so do make sure to golf your code.

Test cases

Format:
"String 1", "String 2" -> output
comments about the test case - in all these test cases, the output will be true if string 1 is a circumfix or string 2 and false otherwise
"apply", "appreciably" -> true
"app]reciab[ly"
"rake", "racket by the lake" -> true
multiple options - "r]acket by the l[ake" and "ra]cket by the la[ke"
"trout", "trumpet" -> false
Doesn't work at all
"bring", "brought him a gong" -> false
You only get to remove one substring - "br]ought h[i]m a go[ng" is not allowed
"falcon", "false conundrum" -> false
You can't have extra stuff at the start or end either - "fal]se [con(undrum)" is not allowed
"goose", "goosebumps" -> false
"goose]bumps[" is just a prefix
"lame", "blame" -> false
And "]b[lame" is just a suffix
"pale", "pale ale" -> true
"pale] ale[" is just a prefix, but "pal]e al[e" is a circumfix, so this is allowed
"b", "barb" -> false
This could be a prefix ("b]arb[") or a suffix ("]bar[b"), but not a circumfix - "b]ar[b" is not allowed
"abba", "aba" -> false
"abba" can be split into a prefix of "aba" ("ab") and a suffix of "aba" ("ba"), but "abba" is still not a circumfix of "aba"
"friend", "friend" -> false
It's only a proper circumfix if you actually remove something - "fri][end" doesn't make the cut
"float", "on" -> false
You may not assume the first input will be shorter than the second one
"", "" -> false
One or both input strings may be empty
"Twin Sister", "Twister" -> false
Inputs are ordered - you may reverse the order, but there must be a consistent ordering
"case", "Castle" -> false
Inputs are case sensitive
"<<@ 23|>", "<<@23??|> 23|>" -> true
"<<@]23??|>[ 23|>", not all characters will be letters)

• Are empty inputs possible? – Luis Mendo Oct 26 '19 at 21:17
• @LuisMendo Yes, one or both inputs may be empty – Sara J Oct 26 '19 at 21:19

J, 2321 20 bytes

>&#*]e.1}:@}.-&#]\.[


Try it online!

-1 byte thanks to Bubbler

Return true if:

• >&# left arg is strictly longer than right (see the "friend"/"friend" test case)
• * and...
• ] the right arg
• e. is an element of the list formed by...
• 1 }:@}. removing the first and last elements of...
• -&# ]\. [ all the outfixes \. of the left arg [ whose size is the difference in size between the two args -&#

That is, J has a builtin to subtract the "chunks" in the middle of the necessary size and leave us with the remaining prefixes and suffixes, catted. We simply have to remove the non-proper ones (ie, the first and last elements of the list of outfixes), and check for what we're searching for.

• +1 for the perfect use case of outfix. – Bubbler Oct 28 '19 at 8:58
• 1}:@}. in place of [:}:@}. saves a byte. Try it online! – Bubbler Oct 28 '19 at 9:04
• @Bubbler thanks and nice catch. updated. – Jonah Oct 28 '19 at 12:46

Python3, 8684838077 76 bytes

lambda a,b:len(a)<len(b)*any(a==b[:i]+b[i-len(a):]for i in range(1,len(a)))


Try it online!

-2 bytes thanks to @Value Ink whitespace before and after ==

-1 byte remove whitespace.

-3 bytes thanks to @ovs using any to write for loop in one line.

-3 bytes by replacing and with *

-1 byte by replacing def with lambda

Python3.8, 71 bytes

lambda a,b:(c:=len(a))<len(b)*any(a==b[:i]+b[i-c:]for i in range(1,c))


Try it online!

• Welcome to CGCC! Since the goal is to have a smaller byte count, you will want to remove the whitespace in your a == b... check. – Value Ink Oct 27 '19 at 0:14
• I put your 3.8 code into a shell and got a SyntaxError. – Alex Hall Oct 27 '19 at 11:35
• @AlexHall fixed it. – Angular Orbit Oct 27 '19 at 12:01

Ruby, 76 62 bytes

->a,b{s=a.size;s<b.size&&(1..s-2).any?{|i|a==b[0,i]+b[i-s,s]}}


Try it online!

Bracmat, 54 bytes

(C=c w a z.!arg:(?c.?w)&@(!c:%?a (%?z&@(!w:!a % !z))))


This solution uses associative (string) pattern matching and expression evaluation during pattern matching. The associative pattern is %?a %?z, which splits the subject, !c, in two strings, neither of which is empty. (The % prefix ensures that a pattern variable does not accept a neutral element, i.e. an empty string, in the case of string pattern matching.) The expression that is evaluated during pattern matching is @(!w:!a % !z). This happens to be another associative string pattern matching operation.

Try it online!

Regex (POSIX ERE or better), 17 bytes

Takes the two strings in joined format, delimited by a single newline.

^(.+)(.+)

Husk, 6 bytes

€§×+ḣṫ


Try it online!

JavaScript (ES6), 70 bytes

Takes input as (a)(b).

a=>b=>[...a].some((_,i)=>b[l=a.length]&&a==b.slice(0,l-i)+b.slice(-i))


Try it online!

• Eventually you can avoid returning undefined moving the first test inside some – edc65 Oct 28 '19 at 10:47
• @edc65 Good idea. Updated. – Arnauld Oct 28 '19 at 10:58

JavaScript (ES6), 72 bytes

s=>t=>[...s].some((x,i)=>t[l=s.length]&&i&&s==t.slice(0,i)+t.slice(i-l))


Return true if

• the second string is longer than the first
• there is a position to split the first string in 2 parts, such as the second string starts with the left part and ends with the right part

Test

z="apply", "appreciably" -> true
"app]reciab[ly"
"rake", "racket by the lake" -> true
multiple options - "r]acket by the l[ake" and "ra]cket by the la[ke"
"trout", "trumpet" -> false
Doesn't work at all
"bring", "brought him a gong" -> false
You only get to remove one substring - "br]ought h[i]m a go[ng" is not allowed
"falcon", "false conundrum" -> false
You can't have extra stuff at the start or end either - "fal]se [con(undrum)" is not allowed
"goose", "goosebumps" -> false
"goose]bumps[" is just a prefix
"lame", "blame" -> false
And "]b[lame" is just a suffix
"pale", "pale ale" -> true
"pale] ale[" is just a prefix, but "pal]e al[e" is a circumfix, so this is allowed
"b", "barb" -> false
This could be a prefix ("b]arb[") or a suffix ("]bar[b"), but not a circumfix - "b]ar[b" is not allowed
"abba", "aba" -> false
"abba" can be split into a prefix of "aba" ("ab") and a suffix of "aba" ("ba"), but "abba" is still not a circumfix of "aba"
"friend", "friend" -> false
It's only a proper circumfix if you actually remove something - "fri][end" doesn't make the cut
"float", "on" -> false
You may not assume the first input will be shorter than the second one
"", "" -> false
One or both input strings may be empty
"Twin Sister", "Twister" -> false
Inputs are ordered - you may reverse the order, but there must be a consistent ordering
"case", "Castle" -> false
Inputs are case sensitive
"<<@ 23|>", "<<@23??|> 23|>" -> true
"<<@]23??|>[ 23|>", not all characters will be letters)

f=s=>t=>[...s].some((x,i)=>t[l=s.length]&&i&&s==t.slice(0,i)+t.slice(i-l))

z.split('\n').forEach((s,i)=>{
var m = s.match(/"([^"]*)", "([^"]*)" -> (true|false)/)
if (m) {
console.log("${m[1]}" "${m[2]}" should be ${m[3]} and is${f(m[1])(m[2])})
}
})

Prolog (SWI), 104 bytes

e([],_).
e([H|T],[H|U]):-e(T,U).
e(L,[_|U]):-e(L,U).
f(X,Y):-string_chars(X,A),string_chars(Y,B),e(A,B).


Try it online!

APL (Dyalog Unicode), 21 bytesSBCS

Anonymous infix function. Takes strings 1 and 2 as left and right arguments. Requires ⎕IO←0.

{(⊂⍺)∊∊¨↑∘⍵¨¨(1↓⍳≢⍺)-⊂0,~/≢¨⍺⍵}


Try it online!

{} "dfn"; ⍺ and ⍵ are is strings 1 and 2:

⍺⍵ strings 1 and 2

≢¨ length of each

~/ remove elements from the first that are in the second (gives empty list if same length)

0, prepend zero

⊂ enclose to treat as a whole

()- subtract that from the following:

≢⍺ length of string 1

⍳ indices zero through that

1↓ drop the first one (the zero)

This gives us the head-tail pairs to try.

¨¨ for element of each of the head-tail pairs:

↑∘⍵ take that many characters from string 2 (from the end if negative)

∊¨ϵnlist (flatten) each

()∊ is the following an ϵlement of that?

⊂⍺ the entire string 1

• This appears to have a false positive for "abba", "aba" – Deadcode Mar 30 at 8:38

Java (JDK), 46 bytes

a->b->(a+"\0"+b).matches("(.+)(.+)\0\\1.+\\2")


Try it online!

Credits

• -2 bytes thanks to ceilingcat
• -2 bytes thanks to Sara J
• The 46 bytes solution (-86 bytes) is actually the full implementation in Java of deadcode's regex answer (which incidentally also contains a Java solution, but only to demonstrate the regex).
• You could save 2 bytes by doing return r instead of return r>0 at the end. – Sara J Oct 28 '19 at 15:19
• @SaraJ Indeed, I thought it was true/false because of the examples in the challenge. The output format is nice for once ;-) – Olivier Grégoire Oct 29 '19 at 10:38
• Oh wow, I didn't realize/remember you could do a regex match in Java without a lot of boilerplate. I suppose I'll keep the boilerplated version in my TIO testbed though, since otherwise it has to compile the regex each time it's called, right? – Deadcode Mar 30 at 9:56
• @Deadcode Yes, Java is boilerplate, but sometimes not as boilerplate as one might thing :-) The JVM keeps a cache of the last 8 regex (number to verify) that it compiled so no need to worry (too much) about performances, therefore you may shrink your version safely. – Olivier Grégoire Mar 30 at 10:15
• I remember now why I didn't use it. It matches the whole string, which would conflict with my format for pure regexes (which have anchors). It's great for golfing Java functions/programs that use regexes, though! Like this. – Deadcode Mar 30 at 11:08

Perl 5, 73 77 bytes

sub f{(grep$_[1]=~/^@{[$_[0]=~s|.{$_}|\Q$&\E.+|r]}$/,1..length($_[0])-1)?1:0}


Try it online!

• Fails on "....", "any string". Try it online! – Value Ink Oct 27 '19 at 2:15
• This should be one of the tests. Corrected for this now, 4 more bytes. – Kjetil S. Oct 28 '19 at 8:36

Wolfram Language (Mathematica), 30 bytes

#2/.{p__,__,s__}/;{p,s}==#->0&


Try it online!

Takes two lists of characters as input. Returns 0 if the first string is a circumfix of the second, or the second string otherwise.