[Question]
  [
Given a set of substrings, such as `[ca, ar, car, rd]`, it's possible to create infinitely many strings by concatting them together. Some examples of this for the given substrings could be:
```
ca
caar
card
rdca
carrd
rdrd
...
```
One interesting property of this set of substrings is that any string can only be constructed in one way using them; there is no string where it's ambiguous which combination was used. As a counterexample, take the set `[foo, bar, obar, fo, baz]`. The string `foobar` could either be `foo + bar` or `fo + obar`.
**Task:**
Given a set of substrings, which will not contain duplicates, determine if the above property holds; that is, if for any concatenated ordering of any number of the substrings, it is unambiguously possible to determine the original order of the substrings that it was constructed out of.
In place of substrings you may use lists, with any reasonable data type in place of characters. You may also restrict the characters used in the substrings within reason, such as only using lowercase letters. Your chosen representation of the substrings must be able to represent at least a dozen "characters".
You may produce output using any of the following:
* A truthy/falsy value, with truthy representing either ambiguous or unambiguous (your choice)
* Two consistent values representing ambiguous and unambiguous
* One consistent value representing either ambiguous or unambiguous, and any other value representing the other
**Test cases:**
```
[ca, ar, car, rd]           unambiguous
[foo, bar, obar, fo, baz]   ambiguous
[a, b, c]                   unambiguous
[a, ab]                     unambiguous
[ab, bc, c]                 unambiguous
[b, ab, ba]                 ambiguous
[b, ab, ba, aa]             ambiguous
[a, aa]                     ambiguous
[abc, bcd, cda, dab, abcd]  ambiguous
[nn, no, on, oo]            unambiguous
[atombomb, at, omb]         ambiguous
[abc, dx, yz, ab, cd, xyz]  ambiguous
[xxx, xxxxx]                ambiguous
[a, ax, xx]                 unambiguous
[baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac, w, wb, acx, xba, aacy, ybaa, aaacz, zbaaa, aaaacd, d]  ambiguous
```
(Thanks to @Arnauld, @CursorCoercer, and @loopywalt for the last three test cases)
**Scoring:**
This is [code-golf](/questions/tagged/code-golf "show questions tagged 'code-golf'"), so shortest answer (in bytes, per language) wins.
  
[Answer]
# JavaScript (ES6), 110 bytes
Returns *false* for unambiguous or *true* for ambiguous.
```
f=(a,p='',q=p)=>p>q?f(a,q,p):p&&p==q||q.match(p)&&!p[a.map(s=>n*=s.length,n=1)|n]&&a.some(s=>s!=q&&f(a,p+s,q))
```
[Try it online!](https://tio.run/##jVHbboQgEH3vV3RfQFpq0tcmuB9ifeCi7jbKgGjDbvx3K2jSNlXThAww5zAz5/DBP7mT3dX0LxpUOU0VSzg1DGNqmSEsM5k9V3PKUkPeDEKGMTuONm15Ly@JIQidTM7nq0kcy/QTc2lT6rq/UM1eyagLhHjqoC0D7E7MIhTKmWdHLSGTBO2gKdMG6gTng@atuNYDDK7A5OEnWCU5lhzTR8y7EOWydQoX5C8zEkXk7eNcbGPxnZD7r7UOmIYQIZ4BDtr4EL2PjF8U/K7zQ8EVxB5i0QrrXq3J@2ZPsUqLlH8wwpkfTL@DLf4IqaJNKnIVX0vLnV/poRXziqQ@SmrFUXkVrbvdvwde@vnbtnbvV6/9Yvf0BQ "JavaScript (Node.js) – Try It Online")
### Commented
```
f = (              // f is a recursive function taking:
  a,               //   a[] = list of substrings
  p = '',          //   p = first string, initially empty
  q = p            //   q = second string, initially empty
) =>               //
p > q ?            // if p is lexicographically greater than q:
  f(a, q, p)       //   swap p and q
:                  // else:
  p && p == q ||   //   stop as successful if p is non empty and p = q
  q.match(p) &&    //   stop as failed if p cannot be found in q
  !p[              //   stop as failed if p is too long:
    a.map(s =>     //     for the upper bound, we use the product n
      n *=         //     of all substring lengths
        s.length,  //     (e.g. for two substrings whose lengths x and y
      n = 1        //     are co-prime, we may need to build strings of
    ) | n          //     length x * y to get a match)
  ] &&             //
  a.some(s =>      //   for each substring s in a[]:
    s != q &&      //     provided that s is not equal to q,
    f(a, p + s, q) //     do a recursive call where s is appended to p
  )                //   end of some()
```
### 98 bytes, much slower
```
f=(a,p='',q=p)=>p>q?f(a,q,p):p&&p==q||!p[a.map(s=>n*=s.length,n=1)|n]&&a.some(s=>s!=q&&f(a,p+s,q))
```
[Try it online!](https://tio.run/##fY/BboMwDIbve4r1kpA1Q9p1UuiDMA5OCqwT2A4pVQ68O1vSHjat5fLbyvfbv/MFFwhuOvH5FenYrmtnCtBspNTesDIVV/7Q/Tx5zeqdhWBj/LLsuIZyBC6CqfDFhHJosT9/ajRvasFGCCgDjW3CYWe8EGkF74P2Sq2OMNDQlgP1haxnhNGe@pnm0Ej19Bt2RS0dSP0sYUrqrmU6ykb9d2ajzb7HHOx9luesezyNmBhSUso90UZMTBpjdvyxyA@sNz9sb2eme2Aj4T6L8ZYcr@HrNw "JavaScript (Node.js) – Try It Online")
[Answer]
# [Python](https://www.python.org), 104 bytes
```
f=lambda l,x=set():l>l-(y:={a[len(b):]for a in l|x for b in[l,l|x][{a}<l]if b+"~">a>b})or y-x and f(l,y)
```
[Attempt This Online!](https://ato.pxeger.com/run?1=tZOxjtQwEIYLun2KkSnOBu8KOrSQlWh5hSiF7STEt44dJY4u2ePo6CgokQBpG3gnKLfhNRhnI9jkIlGdZdmy_fubmd_Jlx9V7wtnj8fvrc_XL34WeWREKVMBhndRk3nKtmZn1rTfRrciNpmlkm2T3NUgQFsw7zoIC4mL2HBcJvGtuHtlEp2DfErek53YyTuGkn7dgbAp5NTwno3xfofLRtsswFyF-GdsuwLNHUTD_qZuKqM9JQCEP2d4hAf0Cb3eNL7WFSVxAoQFynVA6M0o57jLOHuJmUVgnQeHF0TtmxvtC0paS5BV1dp6isFGGONXp28fT18_XMXSOUNzGhzQjLEokgmDx5ALqzJQRab2UIp6D1arfYZF1a6EN84KXwgLr40RdnWmk9PnT2Qs9_jr0TpWgoOoOagw1GkC_1pr0Xv9tnVts4pz5zjIIHLDmA_LQ9BfqBAmkXVJWaSFoHJJNdchTapF4kQnAy8kdF-3pMJ5ppzltsCZq0JaUqWYW4o3UjHAVTDwQmUtx-dGz3B2bgKd1uldKbEjw6OyvPBmHjPtOPSHcyUhfNcfpjG7DhVdaPeKmFU56P7nrHjwFvx-8IbO3WAPDqtQ9_kjUD2aKccUFLp6kH8TCuZOn_P82_wB)
Takes a set of strings (ASCII characters < "~"); returns the empty set for unambiguous and True for ambiguous.
#### How?
Alongside the set of given "syllables" we keep a set of "tips" which are generated by building two words from the syllables such that one is a prefix of the other (and they do not start with the same syllable). The tip is the longer word with the prefix removed.
At each iteration we try generating new tips by finding syllables which are prefixed by or do prefix a previously found tip. The new tip will be the difference. If no new tips are found output unambiguous, if a syllable was created as a new tip output ambiguous, otherwise rinse and repeat.
[Answer]
# [Python 3](https://docs.python.org/3.8/), ~~140 133~~ 129 bytes
This is my golf of [Ajax 1234](https://codegolf.stackexchange.com/users/74571/ajax1234)'s method (it was 197 at the time) - go upvote [that answer](https://codegolf.stackexchange.com/a/254204/53748)!
-7 Thanks to [ovs](https://codegolf.stackexchange.com/users/64121/ovs) (use `for j,k in q` loop in place of `while q` loop to remove the need for `j,k=q.pop()`.)
-4 Thanks to [Sʨɠɠan](https://codegolf.stackexchange.com/users/92689/s%ca%a8%c9%a0%c9%a0an) (function rather than full-program.)
```
def f(a):
 d={};q=[(i,[i])for i in a]
 for j,k in q:q+=[(j+i,k+[i])for i in a if''.join(a)[:1-len(j+i):k in d.setdefault(j,[k])]]
```
A function that accepts a list of strings and identifies ambiguity by success/failure:
* Errors when ambiguous (raises a ValueError "slice step cannot be zero") or
* Succeeding when unambiguous (returns `None`).
**[Try it online!](https://tio.run/##VY1BCsMgEEX3OcXsdIgNlG6KJScRF5YoHZNqTE2hlJ7daneFYZgH7/9ZX/kWw@m8bqVM1oHjBmUH0/j@XNKoOAlFGl3cgIACGN1BAy/mhkmmvkq@JzH3/yKQY2zwkUJtVPJ4WGxoIspfchoeNteHZl8y90LNGrUujtunWTiFdc8cEYtiJsf7tQ4TUO@2G@gv "Python 3.8 (pre-release) – Try It Online")** Or see the [test-suite](https://tio.run/##dZKxTsMwEIbn5ilOWZJQUwmxoKK@Ai8QPDixDW5TO3WckhYxsjEwMsDL9UXKORVtUlLLsnXx5/9@X67cuGejb@9Ku99zIUHGLJkGwGevb/erWRorkiqaSGNBgdLAaAA@mJOFD1fT1Rih@ViRxbgPgpJRNJkbpVExnd5cF0J7MJm2N/mkEg4Tsrpw8ZykC5pQulfL0lgHVgSBE5WrYAZhGAZpzggwSyD3i@UUTqPWbJmpp9rUVZBKYwhkHjLtKttw6/kOhWIZanVVBtV80myIOudQLcsHFXtc5vW8of/cEIX7GXnmjQ176znLvTWO3jje4KwVz30BO5TWBDRWyuBuDL38TmeWGU7UcEguO7U5z8kbApvt4SU@fbPZ9nM2fuD3pqEX/ftff2woqQonbPxgtCDQNsekKgvl4uhRRwn27OhqTUA0pcid4Ng4Vkyk0pwVBSIv44iASoKRsxtERzJeYyCaXJTOxyx3NSt8u52y43lRif5ppx54XlqlXYxpDwCBaPfzsft@j9K/G7OjI5oEBzzcfX2Gyf4X "Python 3.8 (pre-release) – Try It Online").
[Answer]
# Python3, 277 bytes:
```
def F(a):
 q,s=[(j,k)for j in a for k in a if j!=k and j[:len(k)]==k],[]
 for j,k in q:
  if j==k:return 0
  j,k=sorted([j,k],key=len)
  if k[len(j):]in s:continue
  for i in a:
   if all(J==K for J,K in zip(i[:len(k)-len(j)],k[len(j):])):q+=[(j+i,k)];s+=[k[len(j):]]
 return 1
```
[Try it online!](https://tio.run/##tVNNc5swED2HX7H1BalWM@30kiGjaw7JTP8A4SBAtAIsYRAx8OfdXUgd26HtKRqNPtjH27cPthn9L2e/3zXt8ZjrAh6Y4lEAe9HJmJWi4oVroQRjQQEdq@VoCig/yQqUzaGMo1pbVvFEyioRcRLMSHyZsHtkm@EYjFrt@9bCV3yEYdm51uucxXhORKVHiTx8gVcxcZY8SpCjizJnvbG9xiBxm1kFMRNW1TV7lPJpDj2KJwpOpmHmj7AvCxfmOLFyHu23VOLWYJHJfYeXtyiW8Cr129HsGpSJ98DrzncgYbPZBHGmBKhWQEZLmyfwNnqrdqn52bu@C@LCOQEpgdy8FvN1IvwZCslS5DpnWWWjpOka6hqHbGm2yniBS4mPBL3HraFwv0JeaVPr2i6UZSQtR205vpGrmTwjA89Q1gqw6JTD3bnk73V6t0txIodH5O7Mm@uc@SBgnJZKKP0wTpc5hwERA43kX/qRYcb9z1n14YP8/vCBzh1wksMZ1b38BNmIZqavEjJ0dUpPgsjci89J/XLq2sLUXrfsh7NawNxRt11TG8/CZxtiVwY3n18wH7ZZq28LY3Pq7vD5sA0FGB7cqK7T2I4P7IWDlMAOtIZn1of8@Bs)
[Answer]
# [Charcoal](https://github.com/somebody1234/Charcoal), 34 bytes
```
ＷＳ⊞υι¹≔⮌υθＦθＦυ¿№θ⁺ικ⎚¿‹ＬιＬ⪫υω⊞θ⁺ικ
```
[Try it online!](https://tio.run/##VY69DsIwDIT3PIVHWwoDcyfUCcRQwRNUVWgsIpvmp338ECoYWHynk/2dJz/GScdQ6@Y5OMCzvEq@58gyIxEMJXksFpg6M7Qw47G5U0o8C97c6mJyWMjC0uKHRsCFYNdCwA/AXks7WiwMoSRkC09q2D64MSJ14EJy@97VpdSGzNkjN97XXpTl078R/b75Z3W1ihhRo2JUTT2s4Q0 "Charcoal – Try It Online") Link is to verbose version of code. Takes input as a list of newline-terminated strings and outputs a Charcoal boolean, i.e. `1` for unambiguous, nothing for ambiguous. Explanation: Originally another port of @Ajax1234's answer, but since heavily simplified (see my comment on his answer), but it still seems to work, so...
```
ＷＳ⊞υι
```
Input the substrings.
```
¹
```
Assume they are unambiguous.
```
≔⮌υθＦθ
```
Start a breadth-first search with a clone of the list of substrings.
```
Ｆυ¿№θ⁺ικ⎚
```
Loop over all of the substrings and if an ambiguous concatenation is found then clear the canvas.
```
¿‹ＬιＬ⪫υω⊞θ⁺ικ
```
Otherwise if the current string is not too long then add the concatenation to the search.
[Answer]
# [Python 3](https://docs.python.org/3/), 219 bytes
```
lambda x:any(t!=s and t==s[:len(t)]and c(s,t,x) for s in x for t in x)
def c(m,n,e,t=[]):d=m[len(n):];return d not in t and(d in e or any(s==d[:len(s)]and c(m,n+s,e,t+[d])or d==s[:len(d)]and c(n+s,m,e,t+[d])for s in e))
```
[Try it online!](https://tio.run/##fVDBboMwDL33K7JTiMptN6Z8CeOQ4LAhFaciQQr9eZo4wCqKJkW2Zb@89@z77H8tfi6d/F5uatCgWKgUzoX/kI4pBOaldHV1M1h40aRGW7jSl0Gwzo7MsR5ZoNJTKS5guogZSixN6WXdiArkUCcCFFXzNRo/jciAoaUfPqkUkErDIk0Sd1JC1nSbZuS7usR4raEREQe7L9gwCTHsmN2eEWK5jz36gk8Yd@x/Jjs5Li652RU1bxUvGVdjim1OI/BGvGIIoglxNlH62CWsbs9@IKYu2hQt1daekoYUQ3iZbZmfb9JZItV5Cbvmbm0@DiJ6dU7Df2epVqcW37p5Zd0CbQ6EArXStW9n9XbQ8dHYk@lBn1MCXWN@/BnLGmE@7hXCeriQb7c8AQ "Python 3 – Try It Online")
Takes a list of strings as input, returns False for unambiguous and True for ambiguous
[Answer]
# [05AB1E](https://github.com/Adriandmen/05AB1E/wiki/Commands), ~~14~~ 8 [bytes](https://github.com/Adriandmen/05AB1E/wiki/Codepage)
```
ā€ãJ˜DÙQ
```
-6 bytes by checking if the flattened list contains only unique strings (taken from [*@lyxal*'s Vyxal answer](https://codegolf.stackexchange.com/a/254298/52210))
Outputs `0` if ambiguous and `1` if unambiguous.
[Try it online](https://tio.run/##yy9OTMpM/f//SOOjpjWHF3udnuNyeGbg///RSokl@blJQKSkA2QCCRAzFgA) or [verify all test cases with \$\leq4\$ items](https://tio.run/##ZU09CsIwFN5zikdwLHqBYhd1cBHn4vDyAy3YPGkLdtXBA7gJbq7eom7eoheJSSMtaAjvfX/5IrDKrMQa5iBJ6SlVKHINcQzLzYolHLrLFXhiX6fu/Gwf6/d90d62NmLeZscs32soNSrIDVPEAGZ0qGeh5bt@imOY9Fmjbcol8ohj6YbsZ6n4jqXcq8KLA0MRoFeFHCwRLKfhH3cIx/cBGuOwITfII6IQqKkQ7vpc7eUi/NY0jWONP2NRkBz/AA).
**Explanation:**
```
ā         # Push a list in the range [1, (implicit) input-length]
 €        # Map over each integer:
  ã       #  Get the value'th cartesian power of the (implicit) input-list
   J      # Join each inner-most lists together to a string
    ˜     # Flatten the list of lists of strings
     DÙQ  # Verify the list doesn't contain any duplicated strings:
     D    #  Duplicate this list
      Ù   #  Uniquify the copy
       Q  #  Check if the two lists are still the same
          # (after which the result is output implicitly)
```
[Answer]
# [Vyxal](https://github.com/Vyxal/Vyxal) `r`, ~~11~~ ~~8~~ 7 bytes
```
żvÞẊfÞu
```
[Try it Online!](https://vyxal.pythonanywhere.com/#WyJBciIsIiIsIsW8dsOe4bqKZsOedSIsIiIsIltcImNhXCIsIFwiYXJcIiwgXCJjYXJcIiwgXCJyZFwiXVxuW1wiZm9vXCIsIFwiYmFyXCIsIFwib2JhclwiLCBcImZvXCIsIFwiYmF6XCJdXG5bXCJiXCIsIFwiYWJcIiwgXCJiYVwiLCBcImFhXCJdXG5bXCJhYmNcIiwgXCJiY2RcIiwgXCJjZGFcIiwgXCJkYWJcIiwgXCJhYmNkXCJdXG5bXCJhYlwiLCBcImJjXCIsIFwiY1wiXVxuW1wieHh4XCIsIFwieHh4eHhcIl1cbltcImFcIiwgXCJheFwiLCBcInh4XCJdIl0=)
Two can play at the porting game, Kevin! -3 bytes by using cartesian products (taken from [*@KevinCruijssen*'s 05AB1E answer](https://codegolf.stackexchange.com/a/254291/78850)). And another -1 by using the `r` flag to get rid of a `$` that was needed for some weird reason (bug, probably).
Outputs `1` for unambiguous, `0` for ambiguous.
## Explained
```
żvÞẊfÞu
ż        # The range [1, len(input)]
 vÞẊ     # nth cartesian power of the input for each number in that range
    fÞu  # are all the items unique?
```
[Answer]
# T-SQL, 156 bytes
Returns 1 when ambiguous, 0 when unambiguous
```
WITH C(z,y)AS(SELECT*,*FROM @
UNION ALL SELECT','+z+','+x+',',+x
FROM C,@ D WHERE z not
like'%,'+x+',%')SELECT
top 1~(-1/sum(1))FROM
C GROUP BY y ORDER BY 1
```
**[Try it online](https://dbfiddle.uk/q3MSfFmI)**
]