16
\$\begingroup\$

Challenge :

Given a word, check whether or not it is an isogram.


What :

An isogram is a word consisting only of letters with no duplicates (case insensitive). The empty string is an isogram.


Examples :

"Dermatoglyphics"     ---> true
"ab"                  ---> true
"aba"                 ---> false
"moOse"               ---> false
"abc1"                ---> false
""                    ---> true

Input :

You may accept input in any reasonable format

The input will only contain letters and/or numbers, no spaces ([a-zA-Z0-9])


Output :

  • true or any truthy value if the input is an isogram
  • false or any falsy value otherwise

This is so shortest code in bytes in each language wins.

\$\endgroup\$
21
  • 3
    \$\begingroup\$ Suggested test case: sad2 \$\endgroup\$
    – Adám
    Apr 17, 2018 at 18:43
  • 1
    \$\begingroup\$ Your definition of isogram includes two different contradictory statements. Which is it? \$\endgroup\$
    – Wheat Wizard
    Apr 17, 2018 at 18:46
  • 10
    \$\begingroup\$ I would recommend that you start using the sandbox so that these issues can be caught prior to posting the challenge. \$\endgroup\$ Apr 17, 2018 at 19:04
  • 3
    \$\begingroup\$ @MuhammadSalman This is very sloppy, please remove ". Any" from the end of you quote and give some more examples (sad2das would fail even without the 2 so it doesn't show anything). \$\endgroup\$ Apr 17, 2018 at 19:18
  • 4
    \$\begingroup\$ The "What" and the "Notes" seem to contradict one another: "Implement a function that determines whether a string that contains only letters is an isogram" (emphasis added) and "There may be numbers and those will and must return false" say opposite things. I have voted to close as unclear for the moment, but will happily retract it once that is cleared up! \$\endgroup\$
    – Giuseppe
    Apr 17, 2018 at 19:23

45 Answers 45

9
\$\begingroup\$

Python 2/3, 36 52 48 bytes

lambda s:len(s)==len({*s.lower()}-{*str(56**7)})

Try it online!

I take advantage of the fact that set contains only unique elements. By invoking the __len__ method of each, I can determine whether s also contains only unique elements (ignoring case).

EDIT: Updated to satisfy the previously-overlooked requirement to return False for numeric inputs. The set of all digits is encoded as set(str(56**7)).

EDIT 2: Following this user suggestion, I now take advantage of unpacking the arguments to set comprehension. This formally breaks compatibility with Python 2.

\$\endgroup\$
8
  • 3
    \$\begingroup\$ welcome to PPCG! This also is supposed to return false when s contains a digit character. \$\endgroup\$
    – Giuseppe
    Apr 17, 2018 at 19:24
  • \$\begingroup\$ is using `56**7` (as in the other python answer) instead of str() shorter? I'm not familiar with python but that seems to be the main difference between your two answers. \$\endgroup\$
    – Giuseppe
    Apr 17, 2018 at 19:34
  • \$\begingroup\$ @Giuseppe python3 don't have ``, a python2-only version would save 4 bytes (3 on this + 1 on division instead equals) \$\endgroup\$
    – Rod
    Apr 17, 2018 at 19:42
  • \$\begingroup\$ @Rod exactly right. Funnily enough, the 56**7 was inspired by your own encoding of the digits 0-9 below, but saves on yours by 1 byte. \$\endgroup\$ Apr 17, 2018 at 20:14
  • \$\begingroup\$ Perhaps you can add a Python 2 version? 46 Bytes: lambda s:len(s)==len(set(s.lower())-{`56**7`}) \$\endgroup\$ Apr 17, 2018 at 20:25
5
\$\begingroup\$

05AB1E, 5 bytes

lDáÙQ

Try it online!

Explanation

l        # convert input to lower-case
 D       # duplicate
  á      # keep only letters
   Ù      # remove duplicates
    Q     # compare for equality
\$\endgroup\$
0
4
\$\begingroup\$

R, 41 bytes

!grepl("(.).*\\1|\\d",tolower(scan(,"")))

Try it online!

Regex approach. !grepl(regex,scan(,""),F) didn't work so I guess capturing doesn't match case-insensitively in R? I'm bad at regex in general, though, so I won't be surprised if I'm just doing it wrong...

R, 58 bytes

!anyDuplicated(c(el(strsplit(tolower(scan(,"")),"")),0:9))

Try it online!

Appends the digits 0:9 to the (lowercased) list of characters and tests if there are any duplicates.

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

Ruby, 25 23 21 bytes

-2 bytes on both thanks to Giuseppe

->s{/(.).*\1|\d/i!~s}

Try it online!


-2 bytes thanks to Kirill L.

Ruby -n, 21 19 18 16 bytes

p !/(.).*\1|\d/i

Try it online!

\$\endgroup\$
5
  • \$\begingroup\$ @Giuseppe I didn't think that would work, thanks \$\endgroup\$ Apr 17, 2018 at 19:21
  • 1
    \$\begingroup\$ I think, in the second version, you don't even need $_ - just throwing in a regex without anything else implicitly matches it against $_: 16 bytes \$\endgroup\$
    – Kirill L.
    Apr 18, 2018 at 12:23
  • \$\begingroup\$ @KirillL. thanks, I never saw !/.../ before, can't even find it on ruby-doc.org \$\endgroup\$ Apr 18, 2018 at 12:30
  • \$\begingroup\$ Not surprising, I also learned about it here after getting advice from some Perl guy :) \$\endgroup\$
    – Kirill L.
    Apr 18, 2018 at 13:39
  • \$\begingroup\$ @KirillL. not surprising either, ruby weirdness is generally inherited from perl \$\endgroup\$ Apr 18, 2018 at 14:04
3
\$\begingroup\$

Brachylog, 4 bytes

ḷo⊆Ạ

Try it online!

The predicate will succeed if the input is an isogram and fail if it is not, outputting the lowercase Latin alphabet if it does succeed. Since Brachylog's built-in predicate doesn't exactly match the ordinary relationship between a subset and superset, I had to spend a byte on sorting the lowercased input, but saved a byte on not having to explicitly check for duplicates in it. (If it didn't need to fail with numbers, we could just use ḷ≠.)

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

Husk, 6 bytes

§=f√ü_

Try it online!

Explanation

§=f√ü_  -- takes a string as argument, eg: "sAad2"
§       -- fork the argument..
  f√    -- | filter out non-letters: "sad"
    ü_  -- | deduplicate by lower-case: "sAd2"
 =      -- ..and compare: 0
\$\endgroup\$
2
\$\begingroup\$

Japt, 12 bytes

;v
oC ‰ eUq

Explanation:

;v
;         // Set alternative default vars, where C is the lowercase alphabet
 v        // Make the implicit input lowercase and reassign it
oC ‰ eUq
oC        // Remove all items from the input that are not in the alphabet
   ‰     // Split into chars and select unique array items
      eUq // Check if the result is equal to the input split into chars

Try it here.

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

MATL, 9 bytes

kt2Y2X&X=

Try it online!

k   % Lowercase implicit input
t   % Duplicate that
2Y2 % Push lowercase alphabet
X&  % Intersection of alphabet and duplicate lowercase input
X=  % Check for exact equality.
\$\endgroup\$
2
\$\begingroup\$

Python 3, 46 bytes

lambda s:s.isalpha()*len(s)==len({*s.lower()})

Try it online!

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

Japt 2.0, 12 11 bytes

-1 byte thanks to Nit

v
f\l â eUq

Test it online!

\$\endgroup\$
7
  • \$\begingroup\$ Uh, why did you change the version to a longer one? Also, I think the last version of Japt is 1.4.4... \$\endgroup\$ Apr 17, 2018 at 19:03
  • \$\begingroup\$ @EriktheOutgolfer The original didn't account for numbers automatically returning false. \$\endgroup\$
    – Oliver
    Apr 17, 2018 at 19:04
  • \$\begingroup\$ Ah, so you used an alpha version because it's actually shorter. \$\endgroup\$ Apr 17, 2018 at 19:05
  • \$\begingroup\$ @EriktheOutgolfer Right. The regex would've cost +2 in vanilla Japt. ethproductions.github.io/japt/… \$\endgroup\$
    – Oliver
    Apr 17, 2018 at 19:07
  • 1
    \$\begingroup\$ @Nit Thanks! Good use of e \$\endgroup\$
    – Oliver
    Apr 18, 2018 at 13:41
2
\$\begingroup\$

JavaScript (Node.js), 29 25 bytes

s=>!/(.).*\1|\d/i.test(s)

Try it online!

Thanks for the update on answer to @BMO , @l4m2 , @KevinCruijssen

-4 bytes thanks to @KevinCruijssen

\$\endgroup\$
4
  • \$\begingroup\$ s=>!/(.).*\1|[^a-z]/i.test(s)? \$\endgroup\$
    – l4m2
    Apr 18, 2018 at 10:23
  • \$\begingroup\$ @KevinCruijssen : I didn't see the updated version \$\endgroup\$
    – user79855
    Apr 18, 2018 at 13:03
  • \$\begingroup\$ I'm pretty sure [^a-z] can be replaced with \d \$\endgroup\$ Apr 18, 2018 at 14:52
  • \$\begingroup\$ @KevinCruijssen : Thanks. updated \$\endgroup\$
    – user79855
    Apr 18, 2018 at 15:52
2
\$\begingroup\$

Retina, 16 bytes

Ci`(.).*\1|\d
^0

Returns 1 as Truthy and 0 as Falsey values.
Thanks @Neil for discovering and fixing a bug in my initial code.

Try it online.

Explanation:

C             Check if the input matches part of the following regex:
 i`            Case insensitivity enabled
               Check if part of the input matches either:
  (.)           A character `C`
     .*         followed by zero or more characters
       \1       followed by the same character `C` again
         |     Or
          \d    A digit
^0             Invert Truthy/Falsey, basically replacing every 0 with a 1,
               and every other value with a 1
\$\endgroup\$
3
  • \$\begingroup\$ Why is yours opposite ? \$\endgroup\$ Apr 18, 2018 at 7:35
  • \$\begingroup\$ @MuhammadSalman Two reasons: reversing the matches would cost more bytes. And I'm not too skilled with Retina so I'm not sure how to reverse the matches to begin with.. xD \$\endgroup\$ Apr 18, 2018 at 7:37
  • \$\begingroup\$ reason 1). Ah ok. reason 2). LOL \$\endgroup\$ Apr 18, 2018 at 7:40
1
\$\begingroup\$

PowerShell, 91 bytes

param($b)($a=[char[]]$b.ToUpper()|group|sort c*)[0].Count-eq$a[-1].count-and$b-notmatch'\d'

Try it online!

Naive solution, but I can't come up with a better algorithm. Takes input $b, converts it ToUppercase, casts it as a char-array. Pipes that array into Group-Object which constructs a object that has name/count pairs for each input letter. We then sort that based on the count and take the 0th one thereof. We check that its .Count is -equal to the .Count of the last [-1] pair. If so, then the counts are all equal, otherwise we have a different amount of letters.

We then -and that with checking whether the input -notmatches against \d to rule out any digits in the input. That Boolean result is left on the pipeline and output is implicit.

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

Jelly, 8 bytes

ŒufØA⁼QƲ

Try it online!

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

Python 2, 57 56 bytes

x=input().lower()
print len(set(x)-set(`763**4`))/len(x)

Try it online!

First it turn then input into a set, removing the duplicates, then remove the digits (encoded in `763**4`), then check if the length is the same as the original input

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

Java 8, 61 39 bytes

s->!s.matches("(?i).*((.).*\\2|\\d).*")

Explanation:

Try it online.

s->  // Method with String parameter and boolean return-type
  !s.matches("(?i).*((.).*\\2|\\d).*")
     //  Return whether the input does not match the regex

Regex explanation:

String#matches implicitly adds ^...$.

^(?i).*((.).*\2|\d).*$
 (?i)                      Enable case insensitivity
^    .*                    Zero or more leading characters
       (       |  )        Followed by either:
        (.)                 Any character `C`
           .*               with zero or more characters in between
             \2             followed by that same character `C` again
               |           Or:
                \d          Any digit
                   .*$     Followed by zero or more trailing characters
\$\endgroup\$
1
\$\begingroup\$

APL (Dyalog Unicode), 12 bytes

Anonymous tacit function.

(∪≡~∘⎕D)819⌶

Try it online!

819⌶ lowercase

() apply the following tacit function on that:

~∘⎕D remove Digits from the argument

∪≡ are the unique elements of the argument identical to that?

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

Perl 6, 22 bytes

{!(.uc~~/(.).*$0|\d/)}

Try it online!

No matches for some character then later the same character. Implicit function as a code block, match implicitly on $_, invert book with !. Added |\d (ta Adam) but also needed .uc~~, which needed parentheses...

Alternative with Bags, 23 bytes

{.uc.ords.Bag⊆65..97}

Try it online!

This one normalises case then makes a bag (set with incidence counts). Subset or equal only true if all members are members of the comparison Bag, and all incidence counts are less than or equal to those in the comparison Bag. So any repeats or digits would make the comparison false.

\$\endgroup\$
7
  • \$\begingroup\$ Fails on abc1. \$\endgroup\$
    – Adám
    Apr 19, 2018 at 13:26
  • \$\begingroup\$ Ah, wrote this answer before the numbers spec was added. \$\endgroup\$
    – Phil H
    Apr 19, 2018 at 16:45
  • \$\begingroup\$ Can;t you just add |\d? \$\endgroup\$
    – Adám
    Apr 19, 2018 at 18:11
  • \$\begingroup\$ @Adám: Sort of. Realised it also didn't detect repeated letters if the cases of those letters was different, so needed to normalise case and add parens as well. \$\endgroup\$
    – Phil H
    Apr 19, 2018 at 20:07
  • 1
    \$\begingroup\$ 19 bytes \$\endgroup\$
    – Jo King
    Dec 18, 2018 at 21:55
1
\$\begingroup\$

Visual Basic for Applications (32 bit), 102 bytes

s=LCase(InputBox(u)):j=1:For i=1To Len(s):k=Mid(s,i,1):j=j*0^Instr(i+1,s,k)*(k Like"[a-z]"):Next:?j<>0

Used the fact that in VBA 0^x yields 1 if x is zero and 0 otherwise. Run in immediate (debug) window.

Edit: as pointed out by Taylor in the comments this only works in 32 bit installs of MS Office.

\$\endgroup\$
3
  • \$\begingroup\$ If you restrict your language to Excel VBA, then you can swap this over to s=LCase([A1]):j=1:For i=1To Len(s):k=Mid(s,i,1):j=j*0^InStr(i+1,s,k)*(k Like"[a-z]"):Next:?j<>0 for 95 bytes by taking input from [A1]. Also, it is worth noting that because Exponentiation in VBA is weird that this solution is restricted to 32 bit installs of office. \$\endgroup\$ Apr 20, 2018 at 16:38
  • \$\begingroup\$ Also, you can make your answer look better and more readable by using proper capitalization (see above) and adding a <!-- language-all: lang-vb --> flag to your answer to add syntax highlighting \$\endgroup\$ Apr 20, 2018 at 16:40
  • 1
    \$\begingroup\$ @TaylorScott thanks! Added syntax highlighting and noted de 32 bit restriction. About the Excel input, I'd rather keep the solution application-invariant whenever possible. \$\endgroup\$
    – dnep
    Apr 26, 2018 at 20:56
1
\$\begingroup\$

Julia 0.6, 29 bytes

s->!ismatch(r"\d|(.).*\1"i,s)

Try it online!

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

Perl 5 with -n -M5.010, 17 bytes

say!/\d|(.).*\1/i

Try it online!

Perl 5 port of my Julia answer.

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

C (gcc), 87 85 83 bytes

f(s,p,b,P)char*s,*p;{for(b=s;*s;++s)for(p=b*=*s>64;b&&p<s;b=(*s^*p++)&95?b:0);s=b;}

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ @ceilingcat Fine suggestion, thanks. \$\endgroup\$ Jun 25, 2018 at 21:38
  • \$\begingroup\$ @ceilingcat Thank you. \$\endgroup\$ Dec 18, 2018 at 21:21
1
\$\begingroup\$

Powershell, 32 bytes

param($s)$s-notmatch'\d|(.).*\1'
\$\endgroup\$
1
\$\begingroup\$

Japt v2.0a0, 8 bytes

v
¶r\L â

Try it

v\n¶r\L â     :Implicit input of string U
v             :Lowercase
 \n           :Reassign to U
   ¶          :Is equal to?
    r         :Remove
     \L       :  Regex /[^a-z]/gi
        â     :Deduplicate
\$\endgroup\$
1
\$\begingroup\$

Zsh, 32 bytes

c=${#${(uL)${(s::)1}}};((c<$#1))

Try it online!. Isogram/truthy returns 1, otherwise 0

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

K (ngn/k), 18 bytes

{(a^,/$!10)~?a:_x}

Try it online!

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

CJam, 11 bytes

qelA,s+_L|=

Try it online!

Explanation

The basic idea is to append each digit then check for duplicates. Since the append ensures that each digit is already present once, any further presence of digits will be a duplicate, causing it to return false.

q      e# read the input:            | "MOoSE1"
el     e# convert to lowercase:      | "moose1"
A      e# push 10:                   | "moose1" 10
,      e# range [0,N):               | "moose1" [0 1 2 3 4 5 6 7 8 9]
s      e# string representation:     | "moose1" "0123456789"
+      e# concatenate:               | "moose10123456789"
_      e# duplicate:                 | "moose10123456789" "moose10123456789"
L|     e# union with the empty list: | "moose10123456789" "mose1023456789"
       e# (this gets rid of duplicates)
=      e# Equal to original:         | 0
\$\endgroup\$
0
\$\begingroup\$

Red, 76 bytes

func[s][a: charset[#"a"-#"z"#"A"-#"Z"]parse s[any[copy c a ahead not to c]]]

Try it online!

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

Smalltalk, 57 bytes

Method to be defined in class String:

s^(self select:#isLetter)asUppercase asSet size=self size

This is most likely self-explanatory.

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

Pyth, 17 bytes

.Am&!t/rz0d}dGrz0

Test suite

Explanation:
.Am&!t/rz0d}dGrz0 # Code
  m           rz0 # Map the following over the lowercase input:
      /rz0d       #  Count occurrences of d in lowercase input
     t            #   minus 1
    !             #    inverted (0 -> True)
   &              #     and
           }dG    #      d is in the lowercase alphabet
.A                # Print whether all values are truthy
Python 3 translation:
z=input()
print(all(map(lambda d:not z.lower().count(d)-1and d in "abcdefghijklmnopqrstuvwxyz",z.lower())))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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