Check if words are isomorphs

64

15

Two words are isomorphs if they have the same pattern of letter repetitions. For example, both ESTATE and DUELED have pattern abcdca

ESTATE
DUELED

abcdca

because letters 1 and 6 are the same, letters 3 and 5 are the same, and nothing further. This also means the words are related by a substitution cipher, here with the matching E <-> D, S <-> U, T <-> E, A <-> L.

Write code that takes two words and checks whether they are isomorphs. Fewest bytes wins.

Input: Two non-empty strings of capital letters A..Z. If you wish, you can take these as a collection of two strings or as a single string with a separator.

Output: A consistent Truthy value for pairs that are isomorphs, and a consistent Falsey value if they are not. Strings of different lengths are valid inputs that are never isomorphs.

Test cases:

True:

ESTATE DUELED
DUELED ESTATE
XXX YYY
CBAABC DEFFED
RAMBUNCTIOUSLY THERMODYNAMICS
DISCRIMINATIVE SIMPLIFICATION

False:

SEE SAW
ANTS PANTS
BANANA SERENE
BANANA SENSES
AB CC
XXY XYY
ABCBACCBA ABCBACCAB
ABAB CD

Feel free to add more test cases you find useful.

Leaderboard

Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.

To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:

# Language Name, N bytes

where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

# Ruby, <s>104</s> <s>101</s> 96 bytes

var QUESTION_ID=50472;function answersUrl(e){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),e.has_more?getAnswers():process()}})}function shouldHaveHeading(e){var a=!1,r=e.body_markdown.split("\n");try{a|=/^#/.test(e.body_markdown),a|=["-","="].indexOf(r[1][0])>-1,a&=LANGUAGE_REG.test(e.body_markdown)}catch(n){}return a}function shouldHaveScore(e){var a=!1;try{a|=SIZE_REG.test(e.body_markdown.split("\n")[0])}catch(r){}return a}function getAuthorName(e){return e.owner.display_name}function process(){answers=answers.filter(shouldHaveScore).filter(shouldHaveHeading),answers.sort(function(e,a){var r=+(e.body_markdown.split("\n")[0].match(SIZE_REG)||[1/0])[0],n=+(a.body_markdown.split("\n")[0].match(SIZE_REG)||[1/0])[0];return r-n});var e={},a=1,r=null,n=1;answers.forEach(function(s){var t=s.body_markdown.split("\n")[0],o=jQuery("#answer-template").html(),l=(t.match(NUMBER_REG)[0],(t.match(SIZE_REG)||[0])[0]),c=t.match(LANGUAGE_REG)[1],i=getAuthorName(s);l!=r&&(n=a),r=l,++a,o=o.replace("{{PLACE}}",n+".").replace("{{NAME}}",i).replace("{{LANGUAGE}}",c).replace("{{SIZE}}",l).replace("{{LINK}}",s.share_link),o=jQuery(o),jQuery("#answers").append(o),e[c]=e[c]||{lang:c,user:i,size:l,link:s.share_link}});var s=[];for(var t in e)e.hasOwnProperty(t)&&s.push(e[t]);s.sort(function(e,a){return e.lang>a.lang?1:e.lang<a.lang?-1:0});for(var o=0;o<s.length;++o){var l=jQuery("#language-template").html(),t=s[o];l=l.replace("{{LANGUAGE}}",t.lang).replace("{{NAME}}",t.user).replace("{{SIZE}}",t.size).replace("{{LINK}}",t.link),l=jQuery(l),jQuery("#languages").append(l)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",answers=[],page=1;getAnswers();var SIZE_REG=/\d+(?=[^\d&]*(?:&lt;(?:s&gt;[^&]*&lt;\/s&gt;|[^&]+&gt;)[^\d&]*)*$)/,NUMBER_REG=/\d+/,LANGUAGE_REG=/^#*\s*([^,]+)/;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"><div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table></div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table></div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody></table><table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody></table>

xnor

Posted 2015-05-19T20:45:09.843

Reputation: 115 687

Are the lengths of the two inputs guaranteed to be same ? – Optimizer – 2015-05-19T21:04:38.367

@Optimizer No, the lengths can be different. – xnor – 2015-05-19T21:07:12.300

@Jakube No, your code should in theory work with inputs of any length. It's OK though if huge inputs fail on hardware due to issues like memory overflow or stack depth. – xnor – 2015-05-20T21:06:02.313

O.k. Then I'll delete my answer. – Jakube – 2015-05-20T21:07:22.470

Important test case: ABAB CD (for zip-like approaches) – Sp3000 – 2015-05-21T02:49:22.783

@Sp3000 Thanks, added it. – xnor – 2015-05-21T03:41:05.190

It seems the code snippet in the question isn't working? – kamoroso94 – 2017-10-13T19:04:10.870

@kamoroso94 I changed to link to https so it should work now. – xnor – 2017-10-14T12:39:48.493

Answers

95

J, 4 bytes

-:&=

Usage

   'THERMODYNAMICS' (-:&=) 'RAMBUNCTIOUSLY'  NB. parens are optional
1 

Explanation

  • = with 1 argument creates an equality-table comparing the elements of the input and its nub.

    ='ESTATE' gives the binary matrix
    
    = | E S T A T E    
    --+------------
    E | 1 0 0 0 0 1
    S | 0 1 0 0 0 0
    T | 0 0 1 0 1 0
    A | 0 0 0 1 0 0
    
  • -: with 2 arguments checks their equality (like == generally does). This works for different size matrices (or even different types) too.

  • f&g applies g to both input separately and then applies f to the two results together so x f&g y == f(g(x), g(y)).

  • So in our case we compare the two equality-tables.

Try it online here.

randomra

Posted 2015-05-19T20:45:09.843

Reputation: 19 909

2An interesting and elegant approach. Without an equivalent to that &, the closest thing you could do in K would probably be ~/{x=/:x}', which is a good bit longer. – JohnE – 2015-05-19T22:02:31.377

17Jesus. This has to be a contender for the codegolf hall of fame. – Brian Gordon – 2015-05-21T22:36:26.620

Wow, did not expect classify = to have any other use than for counting occurrences. – miles – 2017-01-26T19:46:16.807

37

K, 5 bytes

This has a delightfully elegant solution in K!

~/=:'

The "group" operator (monadic =) creates precisely the signature we want for word isomorphism; gathering vectors of the indices of each element of a vector, with the groups ordered by appearance:

  ="ABBAC"
(0 3
 1 2
 ,4)

  ="DCCDF"
(0 3
 1 2
 ,4)

Taking a pair of strings as a vector, we just need to apply group to each element (=:') and then reduce with "match" (~), the deep-equality operator:

  ~/=:'("RAMBUNCTIOUSLY";"THERMODYNAMICS")
1
  ~/=:'("BANANA";"SERENE")
0

JohnE

Posted 2015-05-19T20:45:09.843

Reputation: 4 632

15

Python 2, 41 bytes

f=lambda a,b:map(a.find,a)==map(b.find,b)

ygramul

Posted 2015-05-19T20:45:09.843

Reputation: 161

4This was the solution that inspired me to create this challenge! – xnor – 2015-05-22T03:28:57.290

12

CJam, 9 bytes

r_f#r_f#=

Prints 1 if the words are isomorphs and 0 if they're not.

Try it online in the CJam interpreter.

How it works

r    e# Read a whitespace separated token from STDIN.
_    e# Push a copy.
f#   e# Get the indexes of all characters from the first copy in the second.
r_f# e# Repeat for the second word.
=    e# Check for equality.

Dennis

Posted 2015-05-19T20:45:09.843

Reputation: 196 637

10

JavaScript, ES7, 62 55 54 52 51 bytes

f=(x,y,g=z=>[for(i of z)z.search(i)]+0)=>g(x)==g(y)

The logic is simple. I simply convert both the inputs into their corresponding character index values, convert that array into string and compare.

f=(x, y,                  // Create a function named f which takes two arguments x and y
   g=                     // There is a third default argument to f which equals to
     z=>                  // and arrow function which takes argument z
     [                    // Return this array which is created using array comprehension
      for(i of z)         // For each character of z
      z.search(i)         // Use the index of that character in z in place of the character
     ]+0                  // And finally type cast that array to a string
                          // Here, the array elements are automatically joined by a ','
                          // and appended by a 0.
                          // Its funny how JS type casts Array + Number to a string
   )=>                    // Now the body of function f starts
      g(x)==g(y)          // It simply returns if index map of x equals index map of y

Try the above code using the snippet below.

f=(x,y,g=z=>[for(i of z)z.search(i)]+0)=>g(x)==g(y)

// for cross browser testing, here is a slightly modified ES5 version of above function

if (!f) {
  function f(x, y) {
    function g(z) {
      var Z = [];
      for (var i = 0; i < z.length; i++) {
        Z[i] = z.search(z[i])
      }
      return Z + 0;
    }
    return g(x) == g(y);
  }
}

B.onclick=function() {
  O.innerHTML = f(D.value, E.value);
  
};
<pre>f(<input id=D />,<input id=E />)</pre><button id=B >Run</button>
<br>
<output id=O />

2 bytes saved thanks to @edc65

Optimizer

Posted 2015-05-19T20:45:09.843

Reputation: 25 836

7+1, Tried it, works well. +0 instead of +""? – edc65 – 2015-05-19T21:28:25.953

1@edc65 wow, typecasting WTF – Optimizer – 2015-05-19T21:29:49.467

1I realized just now that the strings are 'A-Z', so you can safely use search instead of indexOf and cut 1 more byte. – edc65 – 2015-05-20T07:57:57.803

array comprehensions havent been cut of es7 eventually? where does this code works? i think only in mozilla – DanielIndie – 2017-10-01T15:18:33.617

8

Bash + coreutils, 38

[ `tr $@<<<$1``tr $2 $1<<<$2` = $2$1 ]

Note we are using the usual shell idea of truthy/falsy here - zero means SUCCESS or TRUE and non-zero means error or FALSE:

$ for t in "ESTATE DUELED" "DUELED ESTATE" "XXX YYY" "CBAABC DEFFED" "RAMBUNCTIOUSLY THERMODYNAMICS" "DISCRIMINATIVE SIMPLIFICATION" "SEE SAW" "ANTS PANTS" "BANANA SERENE" "BANANA SENSES" "AB CC" "XXY XYY" "ABCBACCBA ABCBACCAB"; do
> ./isomorph.sh $t
> echo $t $?
> done
ESTATE DUELED 0
DUELED ESTATE 0
XXX YYY 0
CBAABC DEFFED 0
RAMBUNCTIOUSLY THERMODYNAMICS 0
DISCRIMINATIVE SIMPLIFICATION 0
SEE SAW 1
ANTS PANTS 1
BANANA SERENE 1
BANANA SENSES 1
AB CC 1
XXY XYY 1
ABCBACCBA ABCBACCAB 1
$ 

Digital Trauma

Posted 2015-05-19T20:45:09.843

Reputation: 64 644

8

Haskell, 33 29

EDIT:

this is way too late, but i found this improvement using applicatives, that were added to prelude only in march 2015.

s%k=g s==g k
g s=(==)<$>s<*>s

Old version:

s%k=g s==g k
g s=[a==b|a<-s,b<-s]

the checking function is (%)

this works by generating for each string its "equality record": for each two indices i j, it records whether they have equal characters. the record is ordered so that the record for two indices i, j is always in the same place* and therefore checking the equality of the records would return whether or not the strings have the same pattern.

for example, the equality record of "ABC" is [1,0,0,0,1,0,0,0,1] (1 for true, 0 for false) - there is True where any index is compared with itself. anywhere else is a false. (skipping these checks might be more efficient, but is harder in terms of golfng)

*if the strings are of the same length. otherwise it returns false just because the records are of different lengths

proud haskeller

Posted 2015-05-19T20:45:09.843

Reputation: 5 866

6

Haskell, 45 41 bytes

h l=map(`lookup`zip l[1..])l
x!y=h x==h y

Returns True or False, e.g "ESTATE" ! "DUELED" -> True.

Uses the map-char-to-first-index method as seen in many other answers. Association lists come in handy, because earlier entries trump. "aba" becomes [(a,1),(b,2),(a,3)] where lookup always fetches a -> 1.

Edit: @Mauris found 4 bytes to save.

nimi

Posted 2015-05-19T20:45:09.843

Reputation: 34 639

You can replace (flip lookup$zip l[1..]) by (`lookup`zip l[1..]). – Lynn – 2015-05-23T01:49:28.757

6

Brainfuck, 169 168 162 144 140 131 130

Compatible with Alex Pankratov's bff (brainfuck interpreter used on SPOJ and ideone) and Thomas Cort's BFI (used on Anarchy Golf).

The expected input is two strings separated by a tab, with no newline after the second string. The output is 1 for isomorphs and 0 for non-isomorphs, which is convenient for checking results visually, although not the shortest option. (Update: shorter version with \x01 and \x00 as output and \x00 as separator at the bottom of the answer.)

Demonstration on ideone.

,+
[
  -
  ---------
  >+<
  [
    >>-<
    [
      <
      [
        >+<
        <<<<-<+>>>>>-
      ]
      ++[->+]
      ->+[+<-]
      >[<<<<]
      <
    ]
    <[>+<-]
    +[->+]
    <->
    >>>
  ]
  >
  [
    [[-]<<<<<]
    >>>>
  ]
  <,+
]
>>>+>+
[
  [<->-]
  <[>>>>>]
  <<<<
]
-<[>]
+++++++[<+++++++>-]
<.

This problem turns out to be very nice for brainfuck.

The basic idea with indexing is to go backwards from the end of the current string prefix. If the character has not previously occurred, we can take the length of the string prefix. For example:

STATES
123255

The indexing in the code is actually slightly different but uses the same principle.

The memory layout is in blocks of 5:

0 0 0 0 0 0 c 0 i p 0 c 0 i p 0 c 0 i p 0 0 0 0

c stands for character, i for index, and p for previous (index). When the first string is being processed, all the p slots are zero. The cell to the left of c is used to hold a copy of the current character that we are trying to find the index of. The cell to the left of the current i is used to hold a -1 for easy pointer navigation.

There are many conditions that need to be considered carefully. At the end, we check for isomorphs by comparing the (i,p) pairs, and we reach the cluster of zero cells to the left of the leftmost (i,p) pair if and only if the strings are isomorphs. Here is a commented version of the code to make it easier to follow:

,+
[                       while there is input
  -
  ---------
  >+<                   increment char (adjust later)
  [                     if not tab
    >>-<                set navigation flag
    [                   loop to find index
      <                 travel to copy
      [
        >+<             restore char
        <<<<-<+>>>>>-   compare chars and create copy
      ]
      ++[->+]           travel between navigation flags
      ->+[+<-]          increment index by 2 and go back
      >[<<<<]           proceed if not fallen off string
      <                 compare chars
    ]
    <[>+<-]             restore char (or no op)
    +[->+]              go back to navigation flag
    <->                 adjust char
    >>>                 alignment
  ]
  >
  [                     if tab
    [[-]<<<<<]          erase chars and go to beginning
    >>>>                alignment
  ]
  <,+
]
>>>+>+                  check string lengths and start loop
[
  [<->-]                compare indices
  <[>>>>>]              realign if not equal
  <<<<                  proceed
]
-<[>]                   cell to left is zero iff isomorphs
+++++++[<+++++++>-]
<.

Update:

Here is a version that prints \x01 for isomorphs and \x00 for non-isomorphs. This is arguably a more accurate interpretation of Truthy and Falsey for brainfuck, because of the way [ and ] work. The only difference is at the very end.

Additional: Now using \x00 as a separator to save 10 bytes.

+
[
  -
  >+<
  [
    >>-<
    [
      <
      [
        >+<
        <<<<-<+>>>>>-
      ]
      ++[->+]
      ->+[+<-]
      >[<<<<]
      <
    ]
    <[>+<-]
    +[->+]
    <->
    >>>
  ]
  >
  [
    [[-]<<<<<]
    >>>>
  ]
  <,+
]
>>>+>+
[
  [<->-]
  <[>>>>>]
  <<<<
]
-<[>]
<+.

Mitch Schwartz

Posted 2015-05-19T20:45:09.843

Reputation: 4 899

5

Ruby, 83 bytes

t=->x{y=0;z=?`;x.gsub!(y[0],z.succ!)while y=x.match(/[A-Z]/);x};f=->a,b{t[a]==t[b]}

It's a function f that takes two arguments and returns true or false.

Explanation:

test = -> str {
    y = nil  # we're just initializing this; it doesn't matter to what
             # this is the variable we use to store the `match' result
    z = '`'  # backtick is the ASCII character before `a'
    while y = str.match(/[A-Z]/) do  # while there is an uppercase letter in str
        str.gsub!(y[0], z.succ!)  # replace all instances of the uppercase letter
                                  # with the next unused lowercase letter
    end
    str  # return the new string
}
# self-explanatory
f=->a,b{test[a]==test[b]}

Doorknob

Posted 2015-05-19T20:45:09.843

Reputation: 68 138

1This should save 4 bytes: t=->x{z=?\;x.chars.to_a.uniq.map{|c|x.gsub!(c,z.succ!)};x};f=->a,b{t[a]==t[b]}, and you could get it down to 68 if you use a hash for getting the replacement:t=->x{h={};i=9;x.gsub!(/./){|c|h[c]||h[c]=i+=1}};f=->a,b{t[a]==t[b]}` – blutorange – 2015-05-20T07:19:08.133

5

JavaScript (ES6), 62

Using an aux function h that maps each word to an array containing the position of each letter in the word, for instance: PASS -> [1,2,3,3]. Return true if the function h applied the two words gives the same result.

f=(a,b,h=w=>0+[for(c of(n=k=[],w))k[c]=k[c]||++n])=>h(b)==h(a)

// TEST

;[
// True
 ['ESTATE','DUELED']
,['DUELED','ESTATE']
,['XXX','YYY']
,['CBAABC','DEFFED']
,['RAMBUNCTIOUSLY','THERMODYNAMICS']
,['DISCRIMINATIVE','SIMPLIFICATION']

// False:

,['SEE','SAW']
,['ANTS','PANTS']
,['BANANA','SERENE']
,['BANANA','SENSES']
,['XXY','XYY']
,['ABCBACCBA','ABCBACCAB']
]
.forEach(t=>(f(t[0],t[1])?OK:KO).innerHTML+=t+'\n')
Ok<br>
<pre id=OK></pre><br>
KO<br>
<pre id=KO></pre>

edc65

Posted 2015-05-19T20:45:09.843

Reputation: 31 086

1Sometimes, simple is shorter ;) – Optimizer – 2015-05-19T21:23:42.620

5

Java, 107

(s,t)->java.util.Arrays.equals(s.chars().map(s::indexOf).toArray(),t.chars().map(t::indexOf).toArray())

Maps each character of s and t to its location, and checks for equality.

Expanded:

class Isomorphs {
    public static void main(String[] args) {
        java.util.function.BiFunction<String, String, Boolean> f =
            (s, t) -> java.util.Arrays.equals(
                                              s.chars().map(s::indexOf).toArray(),
                                              t.chars().map(t::indexOf).toArray()
                                             )
           ;
        System.out.println(f.apply("XXY", "XYY"));
    }
}

Ypnypn

Posted 2015-05-19T20:45:09.843

Reputation: 10 485

I don't think this will work correctly if the strings have different lengths. – JohnE – 2015-05-20T01:59:41.040

@JohnE Yes, it does. – Ypnypn – 2015-05-20T03:17:46.420

Ah, ok- I think the "expanded" version is misleading. – JohnE – 2015-05-20T03:23:03.083

5

R, 78

function(x,y)identical((g=function(z)match(a<-strsplit(z,"")[[1]],a))(x),g(y))

De-golfed:

word_to_num <- function(word) {
   chars <- strsplit(word,"")[[1]]
   match(chars, chars)
}
are_isomorph <- function(word1, word2) identical(word_to_num(word1), 
                                                 word_to_num(word2))

flodel

Posted 2015-05-19T20:45:09.843

Reputation: 2 345

beat me to it! (+1) – shadowtalker – 2015-05-20T04:20:11.170

I think all( (g=...)(x)==g(y)) is shorter than identical... – Giuseppe – 2017-09-27T19:01:10.583

4

APL (Dyalog), 5 4 bytes

-1 thanks to ngn's hint.

Anonymous tacit prefix function which takes a list of two strings as argument.

≡.⍳⍨

Try it Online!

This is an inner product, but instead of the usual + and × it uses

 identicalness

. and

 the ɩndex (the first occurrence of each element)

 with the entire two-element list of words used as both arguments

If we call the words A and B, then we can derive the previous solution as follows:

≡.⍳⍨ A B
A B ≡.⍳ A B
(A⍳A) ≡ (B⍳B)
(⍳⍨A) ≡ (⍳⍨B)
≡/ ⍳⍨¨ A B

Previous solution

Anonymous tacit prefix function which takes a list of two strings as argument.

≡/⍳⍨¨

Try it online!

 identicalness

/ across

 the ɩndex (the first occurrence of each element…)

 selfie (…in itself)

¨ of each

Adám

Posted 2015-05-19T20:45:09.843

Reputation: 37 779

can you see the inner product? :) – ngn – 2017-09-30T13:11:54.843

@ngn Yes of course. SIlly me. – Adám – 2017-09-30T20:12:14.010

Is the top link supposed to link to the old solution? – Zacharý – 2017-10-05T17:54:56.843

Too bad this doesn't work on higher rank arrays though :P – Zacharý – 2017-10-08T18:01:45.247

@Zacharý Obviously there is a way… (Not going to tell you yet, though!) – Adám – 2017-10-08T18:07:45.517

Yeah, I know it already. – Zacharý – 2017-10-08T18:10:53.780

Did you or @ngn ever FIND the 12-byte solution to Isomporphin? – Zacharý – 2017-10-16T13:39:57.120

I did. I'm hoping to golf it even further. – ngn – 2017-10-18T12:52:54.513

@ngn Oooh, you're leading again! I was getting disappointed ;-) – Adám – 2017-10-18T12:54:03.323

Was it because of that not-so-subtle- hint, or did you actually manage to find (no pun intended) it yourself? – Zacharý – 2017-10-19T20:29:49.857

Also, I have no idea how you people did GoL in so few bytes – Zacharý – 2017-10-19T21:50:47.230

@Zacharý YOu meant that @ ngn? – Adám – 2017-10-19T21:52:04.717

@Zacharý Some of the solutions are indeed incredibly clever. I'll be presenting some of the simpler ones (!) on dyalog.tv, Thursday next week. – Adám – 2017-10-19T21:53:47.430

The finding the solution was @ngn, but the GoL comment was directed at both of you. – Zacharý – 2017-10-20T00:31:53.733

@Zacharý Well, I can't tell you until next week. I do expect that we will publish the shortest solutions. – Adám – 2017-10-20T07:19:16.270

@Zachary How do you know I was able to find your solution? Mine could be totally different or I could be bluffing :) Anyway, I promise you full disclosure after the tournament. – ngn – 2017-10-20T12:16:04.060

@Adám When is the deadline? 23:59 BST on 22 Oct? – ngn – 2017-10-20T12:16:23.903

@ngn I suppose. Also, you should hang out in https://chat.stackexchange.com/rooms/52405/apl.

– Adám – 2017-10-20T12:17:51.000

1

@Zacharý as promised: https://ngn.github.io/apl-codegolf-2017/readme.txt

– ngn – 2017-10-24T09:24:25.810

@ngn Thank you, you just did my work for today for me. (I'll present some of these on Thursday's dyalog.tv) – Adám – 2017-10-24T09:26:10.817

Wow, nice job. Now we just need Michele's MonadicKey. – Zacharý – 2017-10-24T14:08:04.537

4

05AB1E, 6 bytes

εæδË}Ë

Try it online!

Takes input as a list: ['ESTATE', 'DUELED']

Explanations:

    εæδË}Ë   Full program
    ε        Apply on each
     æ         Powerset
      δË       For each generated substring: 1 if all equal, 0 otherwise
        }    End for each
         Ë   1 if all equal, 0 otherwise

scottinet

Posted 2015-05-19T20:45:09.843

Reputation: 981

4

Python 3, 85 bytes

f=lambda a,b:''.join(map(lambda g:dict(zip(a,b))[g],a))==b
g=lambda a,b:f(a,b)&f(b,a)

TheNumberOne

Posted 2015-05-19T20:45:09.843

Reputation: 10 855

Where is the input/output on this one? – James – 2015-05-20T04:11:45.190

@DJMcMayhem g is the main function, f is helper. There's a confusing choice of variable g inside f, but it's an unrelated bound variable.. The g= is optional as per the ruling allowing anon functions, which saves two chars.' – xnor – 2015-05-20T05:20:14.247

4

Pyth, 9 bytes

qFmmxdkdQ

Takes input in the following form:

"ESTATE", "DUELED"

If that is not acceptable, the following code is 10 bytes:

qFmmxdkd.z

and uses this input form:

ESTATE
DUELED

Uses the index of char in string representation.

isaacg

Posted 2015-05-19T20:45:09.843

Reputation: 39 268

The first input format is fine. I'm interested in how you're reducing to check equality but I'm unclear on how F works as fold. What's <binary>F? – xnor – 2015-05-20T21:15:36.803

@xnor <binary>F<seq> is <binary> folded over <seq>. It's equivalent to interspersing <binary> between every pair of elements of <seq>. Thus, <binary>F on a 2 element sequence simply applies the function to the sequence, equivalent to .* in Pyth or * in Python. – isaacg – 2015-05-21T07:00:17.163

I thought the trailing Q was implicit in Pyth? – Cyoce – 2016-11-20T23:36:35.023

@Cyoce Not back then - that feature was added in April 2016, almost a year later. – isaacg – 2016-11-21T03:54:19.287

4

Matlab, 50 bytes

f=@(s,t)isequal(bsxfun(@eq,s,s'),bsxfun(@eq,t,t'))

The function is defined as anonymous to save some space.

Example:

>> f=@(s,t)isequal(bsxfun(@eq,s,s'),bsxfun(@eq,t,t'));
>> f('ESTATE','DUELED')
ans =
     1
>> f('ANTS','PANTS')
ans =
     0

Luis Mendo

Posted 2015-05-19T20:45:09.843

Reputation: 87 464

4

Octave, 26 bytes

@(s,t)isequal(s==s',t==t')

alephalpha

Posted 2015-05-19T20:45:09.843

Reputation: 23 988

3Looks interesting. explanation? – proud haskeller – 2015-05-23T18:19:14.237

== is matrix element-wise equality, and since s and s' are different sizes, octave's "broadcasting" automatically tries to get matrices of the same size to operate on -- which in this case means repeating the row s and column s' – rakslice – 2015-06-22T08:20:02.947

It's the same approach as @LuisMendo's Matlab solution, but there the expansion is explicit. – rakslice – 2015-06-22T08:28:37.103

3

Husk, 5 bytes

¤=´×=

Try it online!

Explanation

       -- implicit input A, B (strings aka character lists)       | "ab" "12"
¤=     -- apply the following function to A & B, then compare:    | [1,0,0,1] == [1,0,0,1] -> 1
  ´×   --   Cartesian product with itself under                   | ["aa","ba","ab","bb"] ["11","21","12","22"]
    =  --   equality                                              | [ 1  , 0  , 0  , 1  ] [ 1  , 0  , 0  , 1  ]

ბიმო

Posted 2015-05-19T20:45:09.843

Reputation: 15 345

3

PCRE, 84 bytes

^((.)(?=.+ (\3.|)(.))(?=((?=(\2|)?+.* \3\4(\7?(?(?=.*+\6)(?!\4).|\4))).)+ ))+. \3..$ 

Subject should be two space-separated words, as in the OP. Here's a cursory explanation:

For each letter X in the first word:

Look ahead to the second word and establish back references to recall how far along we are as well as the letter Y in the second word corresponding to X.

For each letter Z past the current position in the first word:

Establish similar back references as above.

Look ahead to the corresponding letter in the second word and check if Z = X then match a Y, otherwise match a letter that isn't Y.

This iteration can end once we've matched up until the penultimate letter in the first word. At this point, since no further validation is necessary, all that remains is to test that the words are of equal length (the back reference containing accumulating substrings of the second word is always behind by 1 letter).

jaytea

Posted 2015-05-19T20:45:09.843

Reputation: 467

3

Mathematica, 46 bytes

Equal@@Values@*PositionIndex/@Characters@{##}&

alephalpha

Posted 2015-05-19T20:45:09.843

Reputation: 23 988

3

Ruby, 50 bytes

30 bytes shorter ruby code. Written before I took a look at the solutions, checks for each character of both strings whether the index of that character's first occurence matches; ie. transforms a string to its normalized form 01121 etc and compares those.

->x,y{g=->z{z.chars.map{|c|z=~/#{c}/}};g[x]==g[y]}

Test cases on ideone As an additional bonus, this breaks ideone's code highlighting.

blutorange

Posted 2015-05-19T20:45:09.843

Reputation: 1 205

2

Ruby, 31 bytes

->a{!!a.uniq!{|s|s.tr s,'a-z'}}

A Proc that takes an array of strings and checks whether any are isomorphic to each other. tr s,'a-z' with these arguments normalizes a string s by replacing each letter with the nth letter in the alphabet, where n is the greatest index with which that letter appears in the string. For example, estate becomes fbedef, as does dueled.

histocrat

Posted 2015-05-19T20:45:09.843

Reputation: 20 600

1

Perl, 38 bytes

($_,$a)=@ARGV;eval"y/$_/$a/";say$_~~$a

Run as perl -E '($_,$a)=@ARGV;eval"y/$_/$a/";say$_~~$a' RAMBUNCTIOUSLY THERMODYNAMICS

Prints 1 if true, nothing if false.

Gabriel Benamy

Posted 2015-05-19T20:45:09.843

Reputation: 2 827

1

R, 64 63 bytes

function(l,a=l[d<-which.min(nchar(l))],b=l[-d])chartr(a,b,a)==b

Try it online!

Takes input as a list of strings, e.g., c('estate','dueled') as in the TIO link.

This merely takes the strings, picks the min length as a -- otherwise it will error for b with length less than a -- and then does a character transposition. Fortunately, this is good enough to test for isomorphism.

For a language that's terrible with strings, R is surprisingly competitive in this challenge (if you want to compare byte-counts with other languages).

Giuseppe

Posted 2015-05-19T20:45:09.843

Reputation: 21 077

1

Common Lisp, 76 bytes

(lambda(a b)(equal #1=(map'list(lambda(x)(position x a))a)(setf a b a #1#)))

Try it online!

Renzo

Posted 2015-05-19T20:45:09.843

Reputation: 2 260

1

C++, 213 196 162 bytes

-51 bytes thanks to Zacharý

#include<map>
#define F(X,x)for(auto&e:X){if(x.end()==x.find(e))x[e]=65+x.size();e=x[e];}
auto i=[](auto a,auto b){std::map<int,int>c,d;F(a,c)F(b,d)return a==b;};

To call the lambda, you need to pass 2 arguments that are std::string data type

Code to test :

std::initializer_list<std::pair<std::string, std::string>> test{
    {"ESTATE","DUELED"},
    {"DUELED","ESTATE"},
    {"XXX","YYY"},
    {"CBAABC","DEFFED"},
    {"RAMBUNCTIOUSLY","THERMODYNAMICS"},
    {"DISCRIMINATIVE","SIMPLIFICATION"},
    {"SEE","SAW"},
    {"ANTS","PANTS"},
    {"BANANA","SERENE"},
    {"BANAnA","SENSES"},
    {"AB","CC"},
    {"XXY","XYY"},
    {"ABCBACCBA","ABCBACCAB"},
    {"ABAB","AC"}
};

for (const auto& a : test) {
    std::cout << "Test with " << a.first << " and " << a.second <<
        " outputs : " << (i(a.first, a.second)?"TRUE":"FALSE") << '\n';
}

for the code that tests, including iostream and string header file is required

HatsuPointerKun

Posted 2015-05-19T20:45:09.843

Reputation: 1 891

1It doesn't look like you use anything from the string header, so can you remove it and have the user include it themselves? – Zacharý – 2017-10-01T14:16:54.930

Does this work for 161 bytes?

– Zacharý – 2017-10-05T14:43:19.507

@Zacharý If you add the e as argument of find, yes, it works – HatsuPointerKun – 2017-10-05T16:12:42.690

That moment when you get beat by Brainfuck though >_< – Zacharý – 2017-10-14T16:52:04.363

1

JavaScript (ES6), 52 51 50 bytes

This version doesn't use array comprehension, and takes input using currying syntax.

a=>b=>(f=x=>0+[...x].map(c=>x.search(c)))(a)==f(b)

const isomorph = a=>b=>(f=x=>0+[...x].map(c=>x.search(c)))(a)==f(b);
const tests = [
  {input: ["ESTATE", "DUELED"], expected: true},
  {input: ["DUELED", "ESTATE"], expected: true},
  {input: ["XXX", "YYY"], expected: true},
  {input: ["CBAABC", "DEFFED"], expected: true},
  {input: ["RAMBUNCTIOUSLY", "THERMODYNAMICS"], expected: true},
  {input: ["DISCRIMINATIVE", "SIMPLIFICATION"], expected: true},
  {input: ["SEE", "SAW"], expected: false},
  {input: ["ANTS", "PANTS"], expected: false},
  {input: ["BANANA", "SERENE"], expected: false},
  {input: ["BANANA", "SENSES"], expected: false},
  {input: ["AB", "CC"], expected: false},
  {input: ["XXY", "XYY"], expected: false},
  {input: ["ABCBACCBA", "ABCBACCAB"], expected: false},
  {input: ["ABAB", "CD"], expected: false}
];

for(const { input: [str1, str2], expected } of tests) {
  console.log(`isomorph("${str1}")("${str2}") = ${isomorph(str1)(str2)}, expected = ${expected}`);
}

kamoroso94

Posted 2015-05-19T20:45:09.843

Reputation: 739

1

C (gcc), 93 92 bytes

  • Saved a byte thanks to ceilingcat; performing an index shift.
m,o;r(char*p,char*h){for(o=0;*p;p++,++h)for(m=0;p[m];)o|=(p[m]==*p)^(h[m++]==*h);o|=*p!=*h;}

Try it online!

Jonathan Frech

Posted 2015-05-19T20:45:09.843

Reputation: 6 681

@ceilingcat Thank you. – Jonathan Frech – 2018-12-18T18:03:41.300

1

Cobra, 72 bytes

do(a='',b='')=(for i in a get a.indexOf(i))==for i in b get b.indexOf(i)

Οurous

Posted 2015-05-19T20:45:09.843

Reputation: 7 916

Are you sure this marks the AB CC test case False? – xnor – 2015-05-21T06:26:34.790

@xnor fixed now – Οurous – 2015-05-21T06:49:58.663

1

JavaScript (ES5), 142 98

Quite a big one, but I didn't saw an ES5 version yet.

for(l=j=2;j--;){c=prompt();for(i=c.length;i--;)c=c.replace(RegExp(c[i],"g"),i);b=l==c;l=c}alert(b)

Simply replaces every occurence of the first letter with it's reverse index value. Repeats this for every character.

It does the same for both inputs and compares the generated pattern.

The comparison is quite ugly, but i din't wan't to use an array to store and compare it.

C5H8NNaO4

Posted 2015-05-19T20:45:09.843

Reputation: 1 340

1Could you not move ;l=c to for(l=j=2;j--; and save a byte? – Jonathan Frech – 2018-12-08T15:07:44.083

0

PHP, 99 Bytes

for(;$ß=$argv[1][$±++];)$×.=$$ß?"\\".$$ß:"(.)".!$$ß=++$µ;echo preg_match("#^$×$#",$argv[2]);

old but no PHP solution and I have solve together with @Christoph and @Titus a similar task

Jörg Hülsermann

Posted 2015-05-19T20:45:09.843

Reputation: 13 026

0

Clojure, 49 bytes

#(apply =(for[s %&](for[a s b s](= a b))))

Hey it works on any number of arguments!

NikoNyrh

Posted 2015-05-19T20:45:09.843

Reputation: 2 361

0

Japt, 9 bytes

®¬m!âZÃre

Try it online!

Takes an array of two strings.

Unpacked & How it works

UmZ{Zq m!âZ} re

UmZ{     Map with...
Zq m       Split into chars and map...
!âZ          X=>Z.â(X)
             char=>str.search(char); First index of the char
}
re       Reduce with Array equality

Bubbler

Posted 2015-05-19T20:45:09.843

Reputation: 16 616

8 bytes – Shaggy – 2018-12-05T08:34:36.070

0

Japt, 6 bytes

Takes input as an array of 2 strings.

®ï¥Ãre

Try it

®          :Map input array
 ï         :  Cartesian product of each string with itself
  ¥        :  Reduce each pair of characters by testing for equality
   Ã       :End map
    r      :Reduce by
     e     : Array equality

Shaggy

Posted 2015-05-19T20:45:09.843

Reputation: 24 623

0

Powershell, 71 bytes

param($a,$b)filter i{($s=$_)|% t*y|%{$s|% i*f $_}}"$($a|i)"-eq"$($b|i)"

Less golfed test script:

$f = {

param($a,$b)
filter i{
    ($s=$_)|% toCharArray|%{$s|% indexOf $_}  # returns an array of first position of each char
}
"$($a|i)"-eq"$($b|i)"                         # returns equality of two array.toString()

}

@(
    ,("ESTATE", "DUELED", $true)
    ,("DUELED", "ESTATE", $true)
    ,("XXX", "YYY", $true)
    ,("CBAABC", "DEFFED", $true)
    ,("RAMBUNCTIOUSLY", "THERMODYNAMICS", $true)
    ,("DISCRIMINATIVE", "SIMPLIFICATION", $true)
    ,("SEE", "SAW", $false)
    ,("ANTS", "PANTS", $false)
    ,("BANANA", "SERENE", $false)
    ,("BANANA", "SENSES", $false)
    ,("AB", "CC", $false)
    ,("XXY", "XYY", $false)
    ,("ABCBACCBA", "ABCBACCAB", $false)
    ,("ABAB", "CD", $false)
) | % {
    $a,$b,$expected = $_
    $result = &$f $a $b
    "$($result-eq$expected): $result"

}

Output:

True: True
True: True
True: True
True: True
True: True
True: True
True: False
True: False
True: False
True: False
True: False
True: False
True: False
True: False

mazzy

Posted 2015-05-19T20:45:09.843

Reputation: 4 832

0

C# (Visual C# Interactive Compiler), 72 bytes

a=>b=>a.Select(c=>a.IndexOf(c)).SequenceEqual(b.Select(c=>b.IndexOf(c)))

Try it online!

This is a port of @ygramul answer...

My original answer is below:

C# (Visual C# Interactive Compiler), 84 bytes

a=>b=>a.Length==b.Length&&!a.Where((x,i)=>b.IndexOf(b[i++],i)!=a.IndexOf(x,i)).Any()

Try it online!

Less golfed...

// a and b are strings to compare
a=>b=>
  // ensure equal lenghts
  a.Length==b.Length&&
  // iterate over characters in a
  !a.Where((x,i)=>
    // find next occurrence of current
    // char in b
    b.IndexOf(b[i++],i) !=
    // compare to next occurrence
    // of corresponding char in a
    a.IndexOf(x,i)
  // check for any mismatches
  // and negate the result
  ).Any()

dana

Posted 2015-05-19T20:45:09.843

Reputation: 2 541

Since you can use any consistent truth values, negation is not necessary -- ==b.Length&&! ~> !=b.Length||. – Jonathan Frech – 2018-12-08T15:09:08.523

0

Japt, 8 bytes

®m!bZÃr¥

Try it online!

Oliver

Posted 2015-05-19T20:45:09.843

Reputation: 7 160

:p – Shaggy – 2018-12-11T11:57:28.520

@Shaggy Great minds... – Oliver – 2018-12-11T21:03:11.920

0

Python 3, 75 bytes

def C():s=input();return' '.join(str(s.index(c))for c in s)
print(C()==C())

Try it online!

James

Posted 2015-05-19T20:45:09.843

Reputation: 54 537

0

Ruby, 49 bytes

a.map{|s|s.chars.map{|c|s.index(c)}}.uniq.size==1

This is a function which takes in variable arguments which are stored into the 'a' variable. The algorithm is to map the characters in each word to the index of their first occurrence in the word. Once this is done, if there is only 1 unique element (aka both mappings are the same) then they are isomorphs.

tylerkschrute

Posted 2015-05-19T20:45:09.843

Reputation: 9

Except it isn't actually a function, it's just a code snippet. Snippets are not allowed by default. The shortest way to fix it is probably with an unnamed proc: ->a{...}

– Martin Ender – 2015-05-23T23:59:22.817

0

C#, 265 bytes

using System;using System.Linq;class I{static void Main(string[]a){var x=a[0].ToCharArray();var y=a[1].ToCharArray();Func<char[],char[],bool> l=(s,t)=>s.Select(i=>Array.IndexOf(s,i)).SequenceEqual(t.Select(i=>Array.IndexOf(t,i)));Console.WriteLine(l.Invoke(x,y));}}

fsacer

Posted 2015-05-19T20:45:09.843

Reputation: 131