# Compatible strings

When studying numerology, you can say two words (strings consisting entirely of letters) are compatible if they produce the same number under the following operation (let's use the string hello as an example):

• Map each letter to a number according to the following, ignoring case:

1 2 3 4 5 6 7 8 9
a b c d e f g h i
j k l m n o p q r
s t u v w x y z


The number at the top of the column a letter is in is its mapped value (e.g. a -> 1, x -> 6)

hello -> [8, 5, 3, 3, 6]

• Take the sum of these numbers. hello -> 25

• Repeatedly take the digital sum until it reaches a single digit (i.e. it's digital root). hello -> 2+5 = 7

For example, hello and world are not compatible (they yield 7 and 9 respectively), whereas coding and sandbox are (both 7).

You are to write a program which will take two strings as input and output two distinct consistent values which indicate whether the strings are compatible or not

• The inputs will only consist of letters (ABCDEFGHIJKLMNOPQRSTUVWXYZ or abcdefghijklmnopqrstuvwxyz) in a consistent case
• You may choose the case of the inputs, so long as it is consistent across inputs and runs
• You may take input as a delimited string (e.g. space separated), so long as the delimiter is non-empty and consists of entirely non-letter characters of your chosen case (i.e. if you input in uppercase, the delimiter may contain lowercase letters, but not uppercase letters)
• Otherwise, you may input and output in any convenient method

This is so the shortest code in bytes wins.

## Test cases

a, b -> out

"hello", "world" -> 0
"raahyjc", "mvj" -> 0
"mpqtjmjd", "bwkhrh" -> 0
"VZZLCZTH", "DOJEIV" -> 0
"coding", "sandbox" -> 1
"vhw", "wl" -> 1
"HMCZQZZRC", "SIQYOBXK" -> 1
"a", "j" -> 1


These two programs can generate more test cases

• Nov 20 '20 at 16:01
• @xash Nope, my mistake, I had them mixed up. Fixed Nov 20 '20 at 16:31
• Suggested edge case: "a", "j" -> 1 (one should not stop the process as soon as the length is 1, as the first step is mandatory). Nov 20 '20 at 17:41
• @Arnauld Good catch, added Nov 20 '20 at 17:42
• @user so long as it's the set that removes the duplicates (i.e. the program still takes "2" arguments), yeah that's fine. E.g. an answer f(s) must still take input as f({'a', 'a'}) rather than f({'a'}) Nov 20 '20 at 19:34

# Jelly, 6 bytes

O’§%9E


We take input in capital letters. The key insight here is to note (ref. en.wikipedia.org/wiki/Digital_root that the digital root of $$\n>0\$$ is $$\n\mod 9\$$ except if $$\n\$$ is a multiple of 9, at which case it is 9. However, we only care if the digital roots are equal, so we can treat the digital root as $$\n\mod 9\$$ in all cases.

O’§%9E
O       # Character values A→65,...
’      # minus 1, A→64...
§     # take the sum of each word
%9   # modulo 9 (∑{A...} = ∑{1...})
E  # are the two words' sums equal?


# K (oK), 10 9 bytes

=/9!+/'3+


Try it online!

# Husk, 11 8 bytes

-7 bytes after looking at fireflame241's answer
-3 bytes thanks to Dominic van Essen

Ëo%9ṁo←c


Try it online!

Needs the input a list of uppercase words.

Ëo%9ṁo←c
ṁ     Map each word and sum the results
c  Get integer value of character
o←   And decrement
ȯ%9      Take that modulo 9
Ë         Check if they're equal

• 9 bytes with uppercase input... Nov 20 '20 at 18:35
• @DominicvanEssen Nice, thanks!
– user
Nov 20 '20 at 18:41
• ...and 8 bytes by using Ë, I think (first time for me using Ë...)... Nov 20 '20 at 18:43
• @DominicvanEssen Ë is very smart, I didn't think of taking the input as a list
– user
Nov 20 '20 at 18:45

# Wolfram Language (Mathematica), 32 27 bytes

9∣(#-#2&@@FromDigits/@#)&


Try it online!

Takes a list containing two strings.

Inspired by Neil's Charcoal solution: FromDigits still recognizes a=10, b=11, etc., even if those digits are not present in the base (by default, it interprets its input base 10).

# 05AB1E, 12 11 bytes

Takes input as lists of lists of characters.

-1 byte thanks to Kevin Cruijssen!

ÇÌ9%>ΔO€S}Ë


Try it online!

Commented:

€Ç             # convert both words to codepoint lists  a->97, b->98, ...
Ì            # add 2 to each number                   a->99, b->100, ...
9%          # modulo 9                               a->0, b->1, ...
>         # increment                              a->1, b->2, ...
Δ   }    # execute until the reult doesn't change:
O       #   sum each list
€S     #   split the results into lists of digits
Ë   # are both lists equal?


A port of fireflame's excellent Jelly answer comes in at 6 bytes as well (provided by Kevin):

Ç<O9%Ë


Try it online!

• Why not add 3 to each number, you can skip off increment then, I suppose? I don't know osabie, btw.. Nov 20 '20 at 17:16
• @vrintle Adding 3 will change the result of the modulo 9. The increment changes a 0 to a 1 for the digital sum calculation (it's (x+2) % 9 + 1, which is different from (x + 3) % 9) Nov 20 '20 at 17:34
• If you take the input as a list of character-lists, you can drop the first € for -1. Also, a port of the Jelly answer is 6 bytes as well. Nov 23 '20 at 8:28
• @KevinCruijssen I already input as a list of singleton lists containing the string, but this seems more reasonable ;). Added the port to the answer.
– ovs
Nov 23 '20 at 8:47

# Retina 0.8.2, 31 30 bytes

+T_ldl
.
$* 1{9}|(1*)¶\1 ^$


Try it online! Takes input on separate lines, but link includes header which lowercases the test suite and splits on , for convenience. Explanation:

+T_ldl


Retina 0.8.2 doesn't have Y, but fortunately we can just repeatedly transliterate 9 digits at a time, shuffling the letters back 9 places in the alphabet.

.
$*  Convert all of the digits to unary. 1{9}|(1*)¶\1  Take the difference modulo 9. ^$


Check that the result is zero.

# J, 15 bytes

=&(9|1#.3+3&u:)


Try it online!

### Original approach: J, 29 bytes

-2 after reading ovs' answer: (x-97)|9 = (x+2)|9

=&(1(,.&.":@#.^:_)1+9|2+3&u:)


Try it online!

=&(1(,.&.":@#.^:_)1+9|2+3&u:)
f&(            g            ) call g on both arguments, then call f
3&u:  convert string to code points
2+      'a' -> 99, 'b' -> 100 …
9|        mod 9
1+          plus 1
1(  f     )^:_             execute 1 f (list of digits)
until the result does not change
@#.                sum digits and
&.":                   convert the sum to a string and
,.                       itemize the characters
&.":                   convert the characters back to digits
=&                            are the digital roots equal?


# Perl 5-MList::Util=sum -p, 61 bytes

y/a-z/1-91-91-8/;s|\d+|sum$&=~/./g|ge while/\d\d/;$_=/(.) \1/


Try it online!

• Three bytes less with: Try it online! Assuming - to be just as valid as a separator between the words as space. If the delimiter is == then one more byte (the !) can be dropped from the code. I think the question says such delimiters are ok. Nov 22 '20 at 5:04

# Python 2, 61 $$\\cdots\$$ 53 51 bytes

Saved a byte thanks to ovs!!!
Saved 2 bytes thanks to Dingus!!!

lambda t:len({sum(ord(c)+3for c in w)%9for w in t})


Try it online!

Inputs a tuple of two strings in lowercase and returns $$\1\$$ if they are compatible or $$\2\$$ otherwise.

• You can do +3 or -6 instead of -96 for 1 byte less.
– ovs
Nov 20 '20 at 18:49
• @ovs Because $-96\equiv +3\pmod 9$, nice one - thanks! :D Nov 20 '20 at 19:24

# Python 3, 203 145 bytes

-58 bytes thanks to @caird coinheringaahing

def f(a):
x,y=[sum((ord(i)-97)%9+1for i in z)for z in a]
g=lambda t:sum(int(i)for i in str(t))
while x>9:x=g(x)
while y>9:y=g(y)
return x==y


Try it online!

• 145 bytes, taking input in lowercase (you can choose to only have to handle 1 case) Nov 20 '20 at 16:39
• And, if you're good switching it Python 2, 141 bytes Nov 20 '20 at 16:42
• 99 bytes, based on caird's suggestions.
– ovs
Nov 20 '20 at 16:47

# Retina, 31 bytes

Yl_1-9
{\d
*
)_+
\$.&
(.) \1


Try it online!

A direct implementation of the given algorithm. Outputs 0/1 indicating incompatible/compatible taking the argument in lower case.

### Explanation

Yl_1-9


Transliterate cyclically the sets a-z_ to 1-9. Here, the underscore is not a real character but a dummy, not that it particularly matters. To cyclically transliterate we extend both sequences infinitely, then each time we encounter a character in the input in the first set we output the corresponding character in the same location in the second set. This behaviour requires the dummy character to map to 9 after z is mapped to 8, as otherwise the second occurrence of each character will be off by one, the third occurrence off by two, etc.

{\d
*
)_+


Here we loop until a fixed point over these two stages, indicated by { and ). The first stage converts each individual digit to a corresponding number of _ characters, while the second converts each chain of _ characters to a number equal to the length of the chain. This reaches a fixed point naturally only at single digit numbers.

(.) \1


Count if the remaining digits are the same, so the total is zero if they are not the same and one if they are.

# Scala, 27 bytes

Uses fireflame241's approach.

_.map(_.map(3+).sum%9).size


Try it online!

Accepts input as a Set of lowercase strings. Returns 2 if inputs are not compatible, 1 if they are. I'll probably go to hell for my abuse of the rules here.

a#b=o a-o b
o s=sum[fromEnum c-1|c<-s]mod9


Try it online!

# Ruby 2.7, 89 $$\\cdots\$$ 55 52 41 bytes

Saved a whooping 34 bytes, thanks to fireflame241's answer!
Saved extra 11 bytes, thanks to Dingus!

->w{!w.map{|s|s.bytes.sum{_1-6}%9}.uniq!}


Try it online!

This outputs with the boolean values swapped!
In Ruby 2.7, we can use _1 in place of |c|c to save 2 bytes.

• "You may choose the case of the inputs, so long as it is consistent across inputs and runs" so you can just take input in lowercase and remove the .downcase Nov 20 '20 at 17:37
• Oh, thank you @cairdcoinheringaahing Nov 20 '20 at 17:38
• It looks like this fails for 'hmczqzzrc', 'siqyobxk' (and errors for the commented out examples, not sure how much of an issue that is) Nov 20 '20 at 17:43
• @cairdcoinheringaahing - it was a typo of n>9 in repeated digit sum. Nov 20 '20 at 17:49
• @vrintle Answering both, so long as the outputs are distinct and consistent across runs, they can be anything you like. However, note that “non-empty array vs empty array” is not consistent, as the non-empty array may change across runs Nov 21 '20 at 3:11

# Charcoal, 13 bytes

¬﹪⁻⍘Ｓ³⁷⍘Ｓ³⁷¦⁹


Try it online! Link is to verbose version of code. Takes input in lower case. Outputs a Charcoal boolean, i.e. - for compatible, nothing if not. Explanation:

    Ｓ           First input string
⍘ ³⁷         Interpret as base 37
⁻             Subtract
⍘Ｓ³⁷     Second input string as base 37
﹪          ⁹   Reduced modulo 9
¬               Equals zero?
Implicitly print


Base 37 interprets a=10, b=11 ... z=35 which have the required digital roots, plus also it interprets aa=380 which has a digital root of 2 etc.

# Java 8, 50 bytes

a->b->a.map(v->v-1).sum()%9==b.map(v->v-1).sum()%9


Inputs in uppercase as character-Streams.

Port of @fireflame241's Jelly answer, so make sure to upvote him!

Try it online.

Explanation:

a->b->                    // Method with two IntStream parameters & boolean return-type
a.map(v->               //  Map over the characters of the first input:
v-1)           //   Decrease each value by 1
.sum()                 //  Then sum them all together
%9               //  And take modulo-9 on that
==b.map(v->v-1).sum()%9 //  Do the same for the second input,
//  and check if they're equal to one another


# Vyxala, 7 bytes

C‹v∑9%≈


Another port of fireflame241's Jelly solution. Takes input as newline-separated strings. Try it Online!

C‹v∑9%≈
C          Convert the input from a list of strings to a list of lists of their codepoints
‹         Decrement every number
v∑       Sum each sublist
9%     Mod 9 both sums
≈    Check if both sums are equal


# JavaScript (ES6),  80 79 65  63 bytes

Saved 14 bytes by using @fireflame241's insight
Saved 2 more bytes thanks to @Neil

Expects (a)(b). Returns a Boolean value.

a=>b=>(g=s=>[...s].map(c=>t+=parseInt(c,36),t=0)&&t%9)(a)==g(b)


Try it online!

• s=>[...s].map(c=>t+=parseInt(c,36),t=0)&&t%9 saves 2 bytes.
– Neil
Nov 20 '20 at 18:50

# Wolfram Language (Mathematica), 66 bytes

SameQ@@(Tr@*IntegerDigits~FixedPoint~Tr@#&/@LetterNumber@#~Mod~9)&


Try it online!

thanks @att

• Composition and prefix application have higher precedence than infix application (although //. would still be shorter), and LetterNumber is Listable. Using the third argument of Mod is shorter than using /., but the replacement currently doesn't do anything since it's performed just before SameQ.
– att
Nov 20 '20 at 19:51

# C (gcc), 67 bytes

Takes two lowercase strings and returns 0 if they match, non-zero otherwise. Uses the method from @fireflame241's answer.

i;g(char*s){for(i=0;*s;i+=(2+*s++)%9+1);s=i%9;}f(s,t){s=g(s)-g(t);}


Try it online!

• Suggest i+=1-~-~*s++%9 instead of i+=(2+*s++)%9+1) Jan 11 at 2:15