13
\$\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\$
  • 3
    \$\begingroup\$ Suggested test case: sad2 \$\endgroup\$ – Adám Apr 17 '18 at 18:43
  • 1
    \$\begingroup\$ Your definition of isogram includes two different contradictory statements. Which is it? \$\endgroup\$ – Wheat Wizard Apr 17 '18 at 18:46
  • 9
    \$\begingroup\$ I would recommend that you start using the sandbox so that these issues can be caught prior to posting the challenge. \$\endgroup\$ – fəˈnɛtɪk Apr 17 '18 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\$ – Asone Tuhid Apr 17 '18 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 '18 at 19:23

42 Answers 42

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\$
  • 3
    \$\begingroup\$ welcome to PPCG! This also is supposed to return false when s contains a digit character. \$\endgroup\$ – Giuseppe Apr 17 '18 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 '18 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 '18 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\$ – Scott Norton Apr 17 '18 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\$ – Sunny Patel Apr 17 '18 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\$
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\$
  • \$\begingroup\$ @Giuseppe I didn't think that would work, thanks \$\endgroup\$ – Asone Tuhid Apr 17 '18 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 '18 at 12:23
  • \$\begingroup\$ @KirillL. thanks, I never saw !/.../ before, can't even find it on ruby-doc.org \$\endgroup\$ – Asone Tuhid Apr 18 '18 at 12:30
  • \$\begingroup\$ Not surprising, I also learned about it here after getting advice from some Perl guy :) \$\endgroup\$ – Kirill L. Apr 18 '18 at 13:39
  • \$\begingroup\$ @KirillL. not surprising either, ruby weirdness is generally inherited from perl \$\endgroup\$ – Asone Tuhid Apr 18 '18 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\$
  • \$\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\$ – Erik the Outgolfer Apr 17 '18 at 19:03
  • \$\begingroup\$ @EriktheOutgolfer The original didn't account for numbers automatically returning false. \$\endgroup\$ – Oliver Apr 17 '18 at 19:04
  • \$\begingroup\$ Ah, so you used an alpha version because it's actually shorter. \$\endgroup\$ – Erik the Outgolfer Apr 17 '18 at 19:05
  • \$\begingroup\$ @EriktheOutgolfer Right. The regex would've cost +2 in vanilla Japt. ethproductions.github.io/japt/… \$\endgroup\$ – Oliver Apr 17 '18 at 19:07
  • 1
    \$\begingroup\$ @Nit Thanks! Good use of e \$\endgroup\$ – Oliver Apr 18 '18 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\$
  • \$\begingroup\$ s=>!/(.).*\1|[^a-z]/i.test(s)? \$\endgroup\$ – l4m2 Apr 18 '18 at 10:23
  • \$\begingroup\$ @KevinCruijssen : I didn't see the updated version \$\endgroup\$ – user79855 Apr 18 '18 at 13:03
  • \$\begingroup\$ I'm pretty sure [^a-z] can be replaced with \d \$\endgroup\$ – Kevin Cruijssen Apr 18 '18 at 14:52
  • \$\begingroup\$ @KevinCruijssen : Thanks. updated \$\endgroup\$ – user79855 Apr 18 '18 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\$
  • \$\begingroup\$ Why is yours opposite ? \$\endgroup\$ – Muhammad Salman Apr 18 '18 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\$ – Kevin Cruijssen Apr 18 '18 at 7:37
  • \$\begingroup\$ reason 1). Ah ok. reason 2). LOL \$\endgroup\$ – Muhammad Salman Apr 18 '18 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\$
  • \$\begingroup\$ Fails on abc1. \$\endgroup\$ – Adám Apr 19 '18 at 13:26
  • \$\begingroup\$ Ah, wrote this answer before the numbers spec was added. \$\endgroup\$ – Phil H Apr 19 '18 at 16:45
  • \$\begingroup\$ Can;t you just add |\d? \$\endgroup\$ – Adám Apr 19 '18 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 '18 at 20:07
  • 1
    \$\begingroup\$ 19 bytes \$\endgroup\$ – Jo King Dec 18 '18 at 21:55
1
\$\begingroup\$

Swift, 81 bytes

let s=readLine()!.lowercased().characters;print(s.count<1||Set(s).count==s.count)

Try it online!

\$\endgroup\$
  • \$\begingroup\$ Nice! 25 bytes shorter than mine. \$\endgroup\$ – onnoweb Mar 25 at 19:08
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\$
  • \$\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\$ – Taylor Scott Apr 20 '18 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\$ – Taylor Scott Apr 20 '18 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 '18 at 20:56
1
\$\begingroup\$

05AB1E, 4 bytes

lDÔQ

Try it online!

Explanation

l      # convert input to lowercase
 D     # duplicate and push to stack
  Ô    # uniquify the list of characters
   Q   # check for equality
\$\endgroup\$
  • \$\begingroup\$ This fails if the input contains non-letter characters. \$\endgroup\$ – Shaggy Jun 26 '18 at 15:43
  • \$\begingroup\$ This works \$\endgroup\$ – LordColus Jun 26 '18 at 15:46
  • \$\begingroup\$ The input will only contain letters and/or numbers, no spaces ([a-zA-Z0-9]) \$\endgroup\$ – LordColus Jun 26 '18 at 15:47
  • \$\begingroup\$ "An isogram is a word consisting only of letters with no duplicates" - i.e., "words" containing numbers should return a falsey value. See the 5th test case for an example. \$\endgroup\$ – Shaggy Jun 26 '18 at 15:50
  • \$\begingroup\$ My bad. See @Enigma's answer for the correct 05AB1E code. \$\endgroup\$ – LordColus Jun 26 '18 at 15:55
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\$
  • \$\begingroup\$ @ceilingcat Fine suggestion, thanks. \$\endgroup\$ – Jonathan Frech Jun 25 '18 at 21:38
  • \$\begingroup\$ @ceilingcat Thank you. \$\endgroup\$ – Jonathan Frech Dec 18 '18 at 21:21
0
\$\begingroup\$

K (ngn/k), 18 bytes

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

Try it online!

\$\endgroup\$
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\$
0
\$\begingroup\$

C#, 82 bytes

bool f(string s)=>!!(s.GroupBy(c=>c).Any(c=>c.Count()>1|(!Char.IsLetter(c.Key))));

edit: added test for char

edit: using GroupBy to shorten it by 5 byte

\$\endgroup\$
  • 1
    \$\begingroup\$ Welcome to PPCG! I think you're missing the requirement that you also need to check that the input contains no digits. \$\endgroup\$ – Martin Ender Apr 18 '18 at 15:07
0
\$\begingroup\$

APL (Dyalog Unicode), 25 20 22 bytes

''≡'(.).*\1|\d'⎕S'&'⍠1

Try it online!

Returns 1 for true, else 0.

Saved 5 bytes thanks to @H.PWiz

Fixed, and saved another byte thanks to @Adám

How?

''≡'(.).*\1|\d'⎕S'&'⍠1 ⍝ Tacit fn
                    ⍠1 ⍝ Ignore case
               ⎕S'&'   ⍝ Search and return the match(es)
   '(.).*\1|\d'        ⍝ For this regex
''≡                    ⍝ And compare to the empty string
\$\endgroup\$
  • \$\begingroup\$ Fails on abc1. \$\endgroup\$ – Adám Apr 18 '18 at 20:46
  • \$\begingroup\$ Isn't \w. valid? \$\endgroup\$ – Adám Apr 19 '18 at 13:23
  • \$\begingroup\$ If you mean (.).*\1, no. It also fails for abc1 :/ \$\endgroup\$ – J. Sallé Apr 19 '18 at 13:25
  • \$\begingroup\$ I don't understand. What do you mean by "it also fails"? \$\endgroup\$ – Adám Apr 19 '18 at 13:27
  • \$\begingroup\$ If you Try it Online! you can see it returns 1 for abc1, when it should return 0. \$\endgroup\$ – J. Sallé Apr 19 '18 at 13:29
0
\$\begingroup\$

Tcl, 114 bytes

proc I w {lmap c [set L [split $w ""]] {if {[regexp -all -nocase $c $w]>1|![string is alp $c]} {return 0}}
expr 1}

Try it online!

Tcl, 121 bytes

proc I w {lmap c [set L [split $w ""]] {if {[llength [lsearch -al -noc $L $c]]>1|![string is alp $c]} {return 0}}
expr 1}

Try it online!

Still too long for my taste!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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