Determine if a string has all unique characters

From Rosetta Code
Task
Determine if a string has all unique characters
You are encouraged to solve this task according to the task description, using any language you may know.
Task

Given a character string   (which may be empty, or have a length of zero characters):

  •   create a function/procedure/routine to:
  •   determine if all the characters in the string are unique
  •   indicate if or which character is duplicated and where
  •   display each string and its length   (as the strings are being examined)
  •   a zero─length (empty) string shall be considered as unique
  •   process the strings from left─to─right
  •   if       unique,   display a message saying such
  •   if not unique,   then:
  •   display a message saying such
  •   display what character is duplicated
  •   only the 1st non─unique character need be displayed
  •   display where "both" duplicated characters are in the string
  •   the above messages can be part of a single message
  •   display the hexadecimal value of the duplicated character


Use (at least) these five test values   (strings):

  •   a string of length     0   (an empty string)
  •   a string of length     1   which is a single period   (.)
  •   a string of length     6   which contains:   abcABC
  •   a string of length     7   which contains a blank in the middle:   XYZ  ZYX
  •   a string of length   36   which   doesn't   contain the letter "oh":
1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ


Show all output here on this page.


Related tasks



AppleScript[edit]

Following AppleScript's convention of one-based indices:

Translation of: Haskell
Translation of: Python
Translation of: JavaScript
use AppleScript version "2.4"
use framework "Foundation"
use scripting additions
 
on run
script showSource
on |λ|(s)
quoted("'", s) & " (" & length of s & ")"
end |λ|
end script
 
script showDuplicate
on |λ|(mb)
script go
on |λ|(tpl)
set {c, ixs} to tpl
quoted("'", c) & " at " & intercalate(", ", ixs)
end |λ|
end script
maybe("None", go, mb)
end |λ|
end script
 
fTable("Indices (1-based) of any duplicated characters:\n", ¬
showSource, showDuplicate, ¬
duplicatedCharIndices, ¬
{"", ".", "abcABC", "XYZ ZYX", "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"})
end run
 
 
------------------CHARACTER DUPLICATIONS-------------------
 
-- duplicatedCharIndices :: String -> Maybe (Char, [Int])
on duplicatedCharIndices(s)
script positionRecord
on |λ|(dct, c, i)
set k to (id of c) as string
script additional
on |λ|(xs)
insertDict(k, xs & i, dct)
end |λ|
end script
maybe(insertDict(k, {i}, dct), additional, lookupDict(k, dct))
end |λ|
end script
 
script firstDuplication
on |λ|(sofar, idxs)
set {iCode, xs} to idxs
if 1 < length of xs then
script earliest
on |λ|(kxs)
if item 1 of xs < (item 1 of (item 2 of kxs)) then
Just({chr(iCode), xs})
else
sofar
end if
end |λ|
end script
maybe(Just({chr(iCode), xs}), earliest, sofar)
else
sofar
end if
end |λ|
end script
 
foldl(firstDuplication, Nothing(), ¬
assocs(foldl(positionRecord, {name:""}, chars(s))))
end duplicatedCharIndices
 
 
--------------------------GENERIC--------------------------
 
-- Just :: a -> Maybe a
on Just(x)
-- Constructor for an inhabited Maybe (option type) value.
-- Wrapper containing the result of a computation.
{type:"Maybe", Nothing:false, Just:x}
end Just
 
-- Nothing :: Maybe a
on Nothing()
-- Constructor for an empty Maybe (option type) value.
-- Empty wrapper returned where a computation is not possible.
{type:"Maybe", Nothing:true}
end Nothing
 
-- Tuple (,) :: a -> b -> (a, b)
on Tuple(a, b)
-- Constructor for a pair of values, possibly of two different types.
{type:"Tuple", |1|:a, |2|:b, length:2}
end Tuple
 
-- assocs :: Map k a -> [(k, a)]
on assocs(m)
script go
on |λ|(k)
set mb to lookupDict(k, m)
if true = |Nothing| of mb then
{}
else
{{k, |Just| of mb}}
end if
end |λ|
end script
concatMap(go, keys(m))
end assocs
 
-- keys :: Dict -> [String]
on keys(rec)
(current application's ¬
NSDictionary's dictionaryWithDictionary:rec)'s allKeys() as list
end keys
 
-- chr :: Int -> Char
on chr(n)
character id n
end chr
 
-- chars :: String -> [Char]
on chars(s)
characters of s
end chars
 
-- compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
on compose(f, g)
script
property mf : mReturn(f)
property mg : mReturn(g)
on |λ|(x)
mf's |λ|(mg's |λ|(x))
end |λ|
end script
end compose
 
-- concatMap :: (a -> [b]) -> [a] -> [b]
on concatMap(f, xs)
set lng to length of xs
set acc to {}
tell mReturn(f)
repeat with i from 1 to lng
set acc to acc & (|λ|(item i of xs, i, xs))
end repeat
end tell
return acc
end concatMap
 
-- enumFromTo :: Int -> Int -> [Int]
on enumFromTo(m, n)
if m ≤ n then
set lst to {}
repeat with i from m to n
set end of lst to i
end repeat
lst
else
{}
end if
end enumFromTo
 
-- foldl :: (a -> b -> a) -> a -> [b] -> a
on foldl(f, startValue, xs)
tell mReturn(f)
set v to startValue
set lng to length of xs
repeat with i from 1 to lng
set v to |λ|(v, item i of xs, i, xs)
end repeat
return v
end tell
end foldl
 
-- fst :: (a, b) -> a
on fst(tpl)
if class of tpl is record then
|1| of tpl
else
item 1 of tpl
end if
end fst
 
-- fTable :: String -> (a -> String) -> (b -> String) -> (a -> b) -> [a] -> String
on fTable(s, xShow, fxShow, f, xs)
set ys to map(xShow, xs)
set w to maximum(map(my |length|, ys))
script arrowed
on |λ|(a, b)
justifyRight(w, space, a) & " -> " & b
end |λ|
end script
s & linefeed & unlines(zipWith(arrowed, ¬
ys, map(compose(fxShow, f), xs)))
end fTable
 
-- insertDict :: String -> a -> Dict -> Dict
on insertDict(k, v, rec)
tell current application
tell dictionaryWithDictionary_(rec) of its NSMutableDictionary
its setValue:v forKey:(k as string)
it as record
end tell
end tell
end insertDict
 
-- intercalate :: String -> [String] -> String
on intercalate(delim, xs)
set {dlm, my text item delimiters} to ¬
{my text item delimiters, delim}
set str to xs as text
set my text item delimiters to dlm
str
end intercalate
 
-- justifyRight :: Int -> Char -> String -> String
on justifyRight(n, cFiller, strText)
if n > length of strText then
text -n thru -1 of ((replicate(n, cFiller) as text) & strText)
else
strText
end if
end justifyRight
 
-- length :: [a] -> Int
on |length|(xs)
set c to class of xs
if list is c or string is c then
length of xs
else
(2 ^ 29 - 1) -- (maxInt - simple proxy for non-finite)
end if
end |length|
 
-- lookupDict :: a -> Dict -> Maybe b
on lookupDict(k, dct)
-- Just the value of k in the dictionary,
-- or Nothing if k is not found.
set ca to current application
set v to (ca's NSDictionary's dictionaryWithDictionary:dct)'s objectForKey:k
if missing value ≠ v then
Just(item 1 of ((ca's NSArray's arrayWithObject:v) as list))
else
Nothing()
end if
end lookupDict
 
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
-- The list obtained by applying f
-- to each element of xs.
tell mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs, i, xs)
end repeat
return lst
end tell
end map
 
-- maximum :: Ord a => [a] -> a
on maximum(xs)
script
on |λ|(a, b)
if a is missing value or b > a then
b
else
a
end if
end |λ|
end script
 
foldl(result, missing value, xs)
end maximum
 
-- maybe :: b -> (a -> b) -> Maybe a -> b
on maybe(v, f, mb)
-- The 'maybe' function takes a default value, a function, and a 'Maybe'
-- value. If the 'Maybe' value is 'Nothing', the function returns the
-- default value. Otherwise, it applies the function to the value inside
-- the 'Just' and returns the result.
if Nothing of mb then
v
else
tell mReturn(f) to |λ|(Just of mb)
end if
end maybe
 
-- min :: Ord a => a -> a -> a
on min(x, y)
if y < x then
y
else
x
end if
end min
 
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
-- 2nd class handler function lifted into 1st class script wrapper.
if script is class of f then
f
else
script
property |λ| : f
end script
end if
end mReturn
 
-- quoted :: Char -> String -> String
on quoted(c, s)
-- string flanked on both sides
-- by a specified quote character.
c & s & c
end quoted
 
-- Egyptian multiplication - progressively doubling a list, appending
-- stages of doubling to an accumulator where needed for binary
-- assembly of a target length
-- replicate :: Int -> a -> [a]
on replicate(n, a)
set out to {}
if 1 > n then return out
set dbl to {a}
 
repeat while (1 < n)
if 0 < (n mod 2) then set out to out & dbl
set n to (n div 2)
set dbl to (dbl & dbl)
end repeat
return out & dbl
end replicate
 
-- take :: Int -> [a] -> [a]
-- take :: Int -> String -> String
on take(n, xs)
set c to class of xs
if list is c then
if 0 < n then
items 1 thru min(n, length of xs) of xs
else
{}
end if
else if string is c then
if 0 < n then
text 1 thru min(n, length of xs) of xs
else
""
end if
else if script is c then
set ys to {}
repeat with i from 1 to n
set v to |λ|() of xs
if missing value is v then
return ys
else
set end of ys to v
end if
end repeat
return ys
else
missing value
end if
end take
 
-- unlines :: [String] -> String
on unlines(xs)
-- A single string formed by the intercalation
-- of a list of strings with the newline character.
set {dlm, my text item delimiters} to ¬
{my text item delimiters, linefeed}
set str to xs as text
set my text item delimiters to dlm
str
end unlines
 
-- zip :: [a] -> [b] -> [(a, b)]
on zip(xs, ys)
zipWith(Tuple, xs, ys)
end zip
 
-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
on zipWith(f, xs, ys)
set lng to min(|length|(xs), |length|(ys))
if 1 > lng then return {}
set xs_ to take(lng, xs) -- Allow for non-finite
set ys_ to take(lng, ys) -- generators like cycle etc
set lst to {}
tell mReturn(f)
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs_, item i of ys_)
end repeat
return lst
end tell
end zipWith
Output:
Indices (1-based) of any duplicated characters:

                                     '' (0) -> None
                                    '.' (1) -> None
                               'abcABC' (6) -> None
                              'XYZ ZYX' (7) -> 'X' at 1, 7
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' (36) -> '0' at 10, 25

AWK[edit]

 
# syntax: GAWK -f DETERMINE_IF_A_STRING_HAS_ALL_UNIQUE_CHARACTERS.AWK
BEGIN {
for (i=0; i<=255; i++) { ord_arr[sprintf("%c",i)] = i } # build array[character]=ordinal_value
n = split(",.,abcABC,XYZ ZYX,1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ",arr,",")
for (i in arr) {
width = max(width,length(arr[i]))
}
width += 2
fmt = "| %-*s | %-6s | %-10s | %-8s | %-3s | %-9s |\n"
head1 = head2 = sprintf(fmt,width,"string","length","all unique","1st diff","hex","positions")
gsub(/[^|\n]/,"-",head1)
printf(head1 head2 head1) # column headings
for (i=1; i<=n; i++) {
main(arr[i])
}
printf(head1) # column footing
exit(0)
}
function main(str, c,hex,i,leng,msg,position1,position2,tmp_arr) {
msg = "yes"
leng = length(str)
for (i=1; i<=leng; i++) {
c = substr(str,i,1)
if (c in tmp_arr) {
msg = "no"
first_diff = "'" c "'"
hex = sprintf("%2X",ord_arr[c])
position1 = index(str,c)
position2 = i
break
}
tmp_arr[c] = ""
}
printf(fmt,width,"'" str "'",leng,msg,first_diff,hex,position1 " " position2)
}
function max(x,y) { return((x > y) ? x : y) }
 
Output:
|----------------------------------------|--------|------------|----------|-----|-----------|
| string                                 | length | all unique | 1st diff | hex | positions |
|----------------------------------------|--------|------------|----------|-----|-----------|
| ''                                     | 0      | yes        |          |     |           |
| '.'                                    | 1      | yes        |          |     |           |
| 'abcABC'                               | 6      | yes        |          |     |           |
| 'XYZ ZYX'                              | 7      | no         | 'Z'      | 5A  | 3 5       |
| '1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' | 36     | no         | '0'      | 30  | 10 25     |
|----------------------------------------|--------|------------|----------|-----|-----------|

C[edit]

In interactive mode, strings with spaces have to be enclosed in double quotes ("")

 
#include<stdbool.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
 
typedef struct positionList{
int position;
struct positionList *next;
}positionList;
 
typedef struct letterList{
char letter;
int repititions;
positionList* positions;
struct letterList *next;
}letterList;
 
letterList* letterSet;
bool duplicatesFound = false;
 
void checkAndUpdateLetterList(char c,int pos){
bool letterOccurs = false;
letterList *letterIterator,*newLetter;
positionList *positionIterator,*newPosition;
 
if(letterSet==NULL){
letterSet = (letterList*)malloc(sizeof(letterList));
letterSet->letter = c;
letterSet->repititions = 0;
 
letterSet->positions = (positionList*)malloc(sizeof(positionList));
letterSet->positions->position = pos;
letterSet->positions->next = NULL;
 
letterSet->next = NULL;
}
 
else{
letterIterator = letterSet;
 
while(letterIterator!=NULL){
if(letterIterator->letter==c){
letterOccurs = true;
duplicatesFound = true;
 
letterIterator->repititions++;
positionIterator = letterIterator->positions;
 
while(positionIterator->next!=NULL)
positionIterator = positionIterator->next;
 
newPosition = (positionList*)malloc(sizeof(positionList));
newPosition->position = pos;
newPosition->next = NULL;
 
positionIterator->next = newPosition;
}
if(letterOccurs==false && letterIterator->next==NULL)
break;
else
letterIterator = letterIterator->next;
}
 
if(letterOccurs==false){
newLetter = (letterList*)malloc(sizeof(letterList));
newLetter->letter = c;
 
newLetter->repititions = 0;
 
newLetter->positions = (positionList*)malloc(sizeof(positionList));
newLetter->positions->position = pos;
newLetter->positions->next = NULL;
 
newLetter->next = NULL;
 
letterIterator->next = newLetter;
}
}
}
 
void printLetterList(){
positionList* positionIterator;
letterList* letterIterator = letterSet;
 
while(letterIterator!=NULL){
if(letterIterator->repititions>0){
printf("\n'%c' (0x%x) at positions :",letterIterator->letter,letterIterator->letter);
 
positionIterator = letterIterator->positions;
 
while(positionIterator!=NULL){
printf("%3d",positionIterator->position + 1);
positionIterator = positionIterator->next;
}
}
 
letterIterator = letterIterator->next;
}
printf("\n");
}
 
int main(int argc,char** argv)
{
int i,len;
 
if(argc>2){
printf("Usage : %s <Test string>\n",argv[0]);
return 0;
}
 
if(argc==1||strlen(argv[1])==1){
printf("\"%s\" - Length %d - Contains only unique characters.\n",argc==1?"":argv[1],argc==1?0:1);
return 0;
}
 
len = strlen(argv[1]);
 
for(i=0;i<len;i++){
checkAndUpdateLetterList(argv[1][i],i);
}
 
printf("\"%s\" - Length %d - %s",argv[1],len,duplicatesFound==false?"Contains only unique characters.\n":"Contains the following duplicate characters :");
 
if(duplicatesFound==true)
printLetterList();
 
return 0;
}
 

Output, test strings from the task Determine_if_a_string_has_all_the_same_characters are also included :

[email protected]:~/doodles$ ./a.out
"" - Length 0 - Contains only unique characters.
[email protected]:~/doodles$ ./a.out .
"." - Length 1 - Contains only unique characters.
[email protected]:~/doodles$ ./a.out abcABC
"abcABC" - Length 6 - Contains only unique characters.
[email protected]:~/doodles$ ./a.out "XYZ YZX"
"XYZ YZX" - Length 7 - Contains the following duplicate characters :
'X' (0x58) at positions :  1  7
'Y' (0x59) at positions :  2  5
'Z' (0x5a) at positions :  3  6
[email protected]:~/doodles$ ./a.out 1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" - Length 36 - Contains the following duplicate characters :
'0' (0x30) at positions : 10 25
[email protected]:~/doodles$ ./a.out "   "
"   " - Length 3 - Contains the following duplicate characters :
' ' (0x20) at positions :  1  2  3
[email protected]:~/doodles$ ./a.out 2
"2" - Length 1 - Contains only unique characters.
[email protected]:~/doodles$ ./a.out 333
"333" - Length 3 - Contains the following duplicate characters :
'3' (0x33) at positions :  1  2  3
[email protected]:~/doodles$ ./a.out .55
".55" - Length 3 - Contains the following duplicate characters :
'5' (0x35) at positions :  2  3
[email protected]:~/doodles$ ./a.out tttTTT
"tttTTT" - Length 6 - Contains the following duplicate characters :
't' (0x74) at positions :  1  2  3
'T' (0x54) at positions :  4  5  6
[email protected]:~/doodles$ ./a.out "4444 444k"
"4444 444k" - Length 9 - Contains the following duplicate characters :
'4' (0x34) at positions :  1  2  3  4  6  7  8

C#[edit]

using System;
using System.Linq;
 
public class Program
{
static void Main
{
string[] input = {"", ".", "abcABC", "XYZ ZYX", "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"};
foreach (string s in input) {
Console.WriteLine($"\"{s}\" (Length {s.Length}) " +
string.Join(", ",
s.Select((c, i) => (c, i))
.GroupBy(t => t.c).Where(g => g.Count() > 1)
.Select(g => $"'{g.Key}' (0X{(int)g.Key:X})[{string.Join(", ", g.Select(t => t.i))}]")
.DefaultIfEmpty("All characters are unique.")
)
);
}
}
}
Output:
"" (Length 0) All characters are unique.
"." (Length 1) All characters are unique.
"abcABC" (Length 6) All characters are unique.
"XYZ ZYX" (Length 7) 'X'(0X58) [0, 6], 'Y'(0X59) [1, 5], 'Z'(0X5A) [2, 4]
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" (Length 36) '0'(0X30) [9, 24]

C++[edit]

#include <iostream>
#include <string>
 
void string_has_repeated_character(const std::string& str)
{
size_t len = str.length();
std::cout << "input: \"" << str << "\", length: " << len << '\n';
for (size_t i = 0; i < len; ++i)
{
for (size_t j = i + 1; j < len; ++j)
{
if (str[i] == str[j])
{
std::cout << "String contains a repeated character.\n";
std::cout << "Character '" << str[i]
<< "' (hex " << std::hex << static_cast<unsigned int>(str[i])
<< ") occurs at positions " << std::dec << i + 1
<< " and " << j + 1 << ".\n\n";
return;
}
}
}
std::cout << "String contains no repeated characters.\n\n";
}
 
int main()
{
string_has_repeated_character("");
string_has_repeated_character(".");
string_has_repeated_character("abcABC");
string_has_repeated_character("XYZ ZYX");
string_has_repeated_character("1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ");
return 0;
}
Output:
input: "", length: 0
String contains no repeated characters.

input: ".", length: 1
String contains no repeated characters.

input: "abcABC", length: 6
String contains no repeated characters.

input: "XYZ ZYX", length: 7
String contains a repeated character.
Character 'X' (hex 58) occurs at positions 1 and 7.

input: "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ", length: 36
String contains a repeated character.
Character '0' (hex 30) occurs at positions 10 and 25.

D[edit]

Translation of: C++
import std.stdio;
 
void uniqueCharacters(string str) {
writefln("input: `%s`, length: %d", str, str.length);
foreach (i; 0 .. str.length) {
foreach (j; i + 1 .. str.length) {
if (str[i] == str[j]) {
writeln("String contains a repeated character.");
writefln("Character '%c' (hex %x) occurs at positions %d and %d.", str[i], str[i], i + 1, j + 1);
writeln;
return;
}
}
}
writeln("String contains no repeated characters.");
writeln;
}
 
void main() {
uniqueCharacters("");
uniqueCharacters(".");
uniqueCharacters("abcABC");
uniqueCharacters("XYZ ZYX");
uniqueCharacters("1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ");
}
Output:
input: ``, length: 0
String contains no repeated characters.

input: `.`, length: 1
String contains no repeated characters.

input: `abcABC`, length: 6
String contains no repeated characters.

input: `XYZ ZYX`, length: 7
String contains a repeated character.
Character 'X' (hex 58) occurs at positions 1 and 7.

input: `1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ`, length: 36
String contains a repeated character.
Character '0' (hex 30) occurs at positions 10 and 25.

Factor[edit]

USING: formatting fry generalizations io kernel math.parser
sequences sets ;
 
: repeated ( elt seq -- )
[ dup >hex over ] dip indices first2
" '%c' (0x%s) at indices %d and %d.\n" printf ;
 
: uniqueness-report ( str -- )
dup dup length "%u — length %d — contains " printf
[ duplicates ] keep over empty?
[ 2drop "all unique characters." print ]
[ "repeated characters:" print '[ _ repeated ] each ] if ;
 
""
"."
"abcABC"
"XYZ ZYX"
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"
[ uniqueness-report nl ] 5 napply
Output:
"" — length 0 — contains all unique characters.

"." — length 1 — contains all unique characters.

"abcABC" — length 6 — contains all unique characters.

"XYZ ZYX" — length 7 — contains repeated characters:
  'Z' (0x5a) at indices 2 and 4.
  'Y' (0x59) at indices 1 and 5.
  'X' (0x58) at indices 0 and 6.

"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" — length 36 — contains repeated characters:
  '0' (0x30) at indices 9 and 24.

Go[edit]

package main
 
import "fmt"
 
func analyze(s string) {
chars := []rune(s)
le := len(chars)
fmt.Printf("Analyzing %q which has a length of %d:\n", s, le)
if le > 1 {
for i := 0; i < le-1; i++ {
for j := i + 1; j < le; j++ {
if chars[j] == chars[i] {
fmt.Println(" Not all characters in the string are unique.")
fmt.Printf("  %q (%#[1]x) is duplicated at positions %d and %d.\n\n", chars[i], i+1, j+1)
return
}
}
}
}
fmt.Println(" All characters in the string are unique.\n")
}
 
func main() {
strings := []string{
"",
".",
"abcABC",
"XYZ ZYX",
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ",
"01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X",
"hétérogénéité",
"🎆🎃🎇🎈",
"😍😀🙌💃😍🙌",
"🐠🐟🐡🦈🐬🐳🐋🐡",
}
for _, s := range strings {
analyze(s)
}
}
Output:
Analyzing "" which has a length of 0:
  All characters in the string are unique.

Analyzing "." which has a length of 1:
  All characters in the string are unique.

Analyzing "abcABC" which has a length of 6:
  All characters in the string are unique.

Analyzing "XYZ ZYX" which has a length of 7:
  Not all characters in the string are unique.
  'X' (0x58) is duplicated at positions 1 and 7.

Analyzing "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" which has a length of 36:
  Not all characters in the string are unique.
  '0' (0x30) is duplicated at positions 10 and 25.

Analyzing "01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X" which has a length of 39:
  Not all characters in the string are unique.
  '0' (0x30) is duplicated at positions 1 and 11.

Analyzing "hétérogénéité" which has a length of 13:
  Not all characters in the string are unique.
  'é' (0xe9) is duplicated at positions 2 and 4.

Analyzing "🎆🎃🎇🎈" which has a length of 4:
  All characters in the string are unique.

Analyzing "😍😀🙌💃😍🙌" which has a length of 6:
  Not all characters in the string are unique.
  '😍' (0x1f60d) is duplicated at positions 1 and 5.

Analyzing "🐠🐟🐡🦈🐬🐳🐋🐡" which has a length of 8:
  Not all characters in the string are unique.
  '🐡' (0x1f421) is duplicated at positions 3 and 8.

Haskell[edit]

import Data.List (groupBy, intersperse, sort, transpose)
import Data.Char (ord, toUpper)
import Numeric (showHex)
 
hexFromChar :: Char -> String
hexFromChar c = map toUpper $ showHex (ord c) ""
 
string :: String -> String
string xs = ('\"' : xs) ++ "\""
 
char :: Char -> String
char c = ['\'', c, '\'']
 
size :: String -> String
size = show . length
 
positions :: (Int, Int) -> String
positions (a, b) = show a ++ " " ++ show b
 
forTable :: String -> [String]
forTable xs = string xs : go (allUnique xs)
where
go Nothing = [size xs, "yes", "", "", ""]
go (Just (u, ij)) = [size xs, "no", char u, hexFromChar u, positions ij]
 
showTable :: Bool -> Char -> Char -> Char -> [[String]] -> String
showTable _ _ _ _ [] = []
showTable header ver hor sep contents =
unlines $
hr :
(if header
then z : hr : zs
else intersperse hr zss) ++
[hr]
where
vss = map (map length) contents
ms = map maximum (transpose vss) :: [Int]
hr = concatMap (\n -> sep : replicate n hor) ms ++ [sep]
top = replicate (length hr) hor
bss = map (map (`replicate` ' ') . zipWith (-) ms) vss
zss@(z:zs) =
zipWith
(\us bs -> concat (zipWith (\x y -> (ver : x) ++ y) us bs) ++ [ver])
contents
bss
 
table xs =
showTable
True
'|'
'-'
'+'
(["string", "length", "all unique", "1st diff", "hex", "positions"] :
map forTable xs)
 
allUnique
:: (Ord b, Ord a, Num b, Enum b)
=> [a] -> Maybe (a, (b, b))
allUnique xs = go . groupBy (\(x, _) (y, _) -> x == y) . sort . zip xs $ [0 ..]
where
go [] = Nothing
go ([_]:us) = go us
go (((u, i):(_, j):_):_) = Just (u, (i, j))
 
main :: IO ()
main =
putStrLn $
table ["", ".", "abcABC", "XYZ ZYX", "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"]
Output:
+--------------------------------------+------+----------+--------+---+---------+
|string                                |length|all unique|1st diff|hex|positions|
+--------------------------------------+------+----------+--------+---+---------+
|""                                    |0     |yes       |        |   |         |
|"."                                   |1     |yes       |        |   |         |
|"abcABC"                              |6     |yes       |        |   |         |
|"XYZ ZYX"                             |7     |no        |'X'     |58 |0 6      |
|"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"|36    |no        |'0'     |30 |9 24     |
+--------------------------------------+------+----------+--------+---+---------+

Alternatively, defining a duplicatedCharIndices function in terms of sortOn, groupBy, and filter:

import Data.List (groupBy, intercalate, sortOn)
import Control.Arrow ((&&&))
import Data.Function (on)
import Numeric (showHex)
import Data.Char (ord)
 
duplicatedCharIndices :: String -> Maybe (Char, [Int])
duplicatedCharIndices s
| null duplicates = Nothing
| otherwise =
Just $ ((snd . head) &&& fmap fst) (head (sortOn (fst . head) duplicates))
where
duplicates =
filter ((1 <) . length) $
groupBy (on (==) snd) $ sortOn snd $ zip [0 ..] s
 
---------------------------TEST----------------------------
main :: IO ()
main =
putStrLn $
fTable
"First duplicated character, if any:"
((++) <$> show <*> ((" (" ++) . (++ ")") . show . length))
(maybe
"None"
(\(c, ixs) ->
unwords
[ show c
, "(0x" ++ showHex (ord c) ") at"
, intercalate ", " (show <$> ixs)
]))
duplicatedCharIndices
["", ".", "abcABC", "XYZ ZYX", "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"]
 
--------------------------DISPLAY--------------------------
fTable :: String -> (a -> String) -> (b -> String) -> (a -> b) -> [a] -> String
fTable s xShow fxShow f xs =
let rjust n c = drop . length <*> (replicate n c ++)
w = maximum (length . xShow <$> xs)
in unlines $
s : fmap (((++) . rjust w ' ' . xShow) <*> ((" -> " ++) . fxShow . f)) xs
Output:
First duplicated character, if any:
                                     "" (0) -> None
                                    "." (1) -> None
                               "abcABC" (6) -> None
                              "XYZ ZYX" (7) -> 'X' (0x58) at 0, 6
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" (36) -> '0' (0x30) at 9, 24


Or, as an alternative to grouping and sorting – folding a string down to a Map of indices:

import qualified Safe as S
import qualified Data.Map.Strict as M
import Data.List (intercalate, foldl') --'
import Data.Ord (comparing)
import Numeric (showHex)
import Data.Char (ord)
 
duplicatedCharIndices :: String -> Maybe (Char, [Int])
duplicatedCharIndices xs =
S.minimumByMay
(comparing (head . snd))
(M.toList
(M.filter
((1 <) . length)
(foldl' --'
(\a (i, c) -> M.insert c (maybe [i] (++ [i]) (M.lookup c a)) a)
M.empty
(zip [0 ..] xs))))
 
-- OR, fusing filter, toList, and minimumByMay down to a single fold:
duplicatedCharIndices_ :: String -> Maybe (Char, [Int])
duplicatedCharIndices_ xs =
M.foldrWithKey
go
Nothing
(foldl' --'
(\a (i, c) -> M.insert c (maybe [i] (++ [i]) (M.lookup c a)) a)
M.empty
(zip [0 ..] xs))
where
go k [_] mb = mb -- Unique
go k xs Nothing = Just (k, xs) -- Duplicated
go k xs@(x:_) (Just (c, ys@(y:_)))
| x < y = Just (k, xs) -- Earlier duplication
| otherwise = Just (c, ys)
 
---------------------------TEST----------------------------
main :: IO ()
main =
putStrLn $
fTable
"First duplicated character, if any:"
((++) <$> show <*> ((" (" ++) . (++ ")") . show . length))
(maybe
"None"
(\(c, ixs) ->
unwords
[ show c
, "(0x" ++ showHex (ord c) ") at"
, intercalate ", " (show <$> ixs)
]))
duplicatedCharIndices_
["", ".", "abcABC", "XYZ ZYX", "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"]
 
--------------------------DISPLAY--------------------------
fTable :: String -> (a -> String) -> (b -> String) -> (a -> b) -> [a] -> String
fTable s xShow fxShow f xs =
unlines $
s : fmap (((++) . rjust w ' ' . xShow) <*> ((" -> " ++) . fxShow . f)) xs
where
rjust n c = drop . length <*> (replicate n c ++)
w = maximum (length . xShow <$> xs)
Output:
First duplicated character, if any:
                                     "" (0) -> None
                                    "." (1) -> None
                               "abcABC" (6) -> None
                              "XYZ ZYX" (7) -> 'X' (0x58) at 0, 6
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" (36) -> '0' (0x30) at 9, 24

Java[edit]

 
import java.util.HashMap;
import java.util.Map;
 
// Title: Determine if a string has all unique characters
 
public class StringUniqueCharacters {
 
public static void main(String[] args) {
System.out.printf("%-40s  %2s  %10s  %8s  %s  %s%n", "String", "Length", "All Unique", "1st Diff", "Hex", "Positions");
System.out.printf("%-40s  %2s  %10s  %8s  %s  %s%n", "------------------------", "------", "----------", "--------", "---", "---------");
for ( String s : new String[] {"", ".", "abcABC", "XYZ ZYX", "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"} ) {
processString(s);
}
}
 
 
 
private static void processString(String input) {
Map<Character,Integer> charMap = new HashMap<>();
char dup = 0;
int index = 0;
int pos1 = -1;
int pos2 = -1;
for ( char key : input.toCharArray() ) {
index++;
if ( charMap.containsKey(key) ) {
dup = key;
pos1 = charMap.get(key);
pos2 = index;
break;
}
charMap.put(key, index);
}
String unique = dup == 0 ? "yes" : "no";
String diff = dup == 0 ? "" : "'" + dup + "'";
String hex = dup == 0 ? "" : Integer.toHexString(dup).toUpperCase();
String position = dup == 0 ? "" : pos1 + " " + pos2;
System.out.printf("%-40s  %-6d  %-10s  %-8s  %-3s  %-5s%n", input, input.length(), unique, diff, hex, position);
}
 
}
 
Output:
String                                    Length  All Unique  1st Diff  Hex  Positions
------------------------                  ------  ----------  --------  ---  ---------
                                          0       yes                             
.                                         1       yes                             
abcABC                                    6       yes                             
XYZ ZYX                                   7       no          'Z'       5A   3 5  
1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ      36      no          '0'       30   10 25

JavaScript[edit]

(() => {
'use strict';
 
// duplicatedCharIndices :: String -> Maybe (Char, [Int])
const duplicatedCharIndices = s => {
const
duplicates = filter(g => 1 < g.length)(
groupBy(on(eq)(snd))(
sortOn(snd)(
zip(enumFrom(0))(chars(s))
)
)
);
return 0 < duplicates.length ? Just(
fanArrow(compose(snd, fst))(map(fst))(
sortOn(compose(fst, fst))(
duplicates
)[0]
)
) : Nothing();
};
 
// ------------------------TEST------------------------
const main = () =>
console.log(
fTable('First duplicated character, if any:')(
s => `'${s}' (${s.length})`
)(maybe('None')(tpl => {
const [c, ixs] = Array.from(tpl);
return `'${c}' (0x${showHex(ord(c))}) at ${ixs.join(', ')}`
}))(duplicatedCharIndices)([
"", ".", "abcABC", "XYZ ZYX",
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"
])
);
 
 
// -----------------GENERIC FUNCTIONS------------------
 
// Just :: a -> Maybe a
const Just = x => ({
type: 'Maybe',
Nothing: false,
Just: x
});
 
// Nothing :: Maybe a
const Nothing = () => ({
type: 'Maybe',
Nothing: true,
});
 
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a => b => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});
 
// chars :: String -> [Char]
const chars = s => s.split('');
 
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
x => fs.reduceRight((a, f) => f(a), x);
 
// enumFrom :: Enum a => a -> [a]
function* enumFrom(x) {
let v = x;
while (true) {
yield v;
v = 1 + v;
}
}
 
// eq (==) :: Eq a => a -> a -> Bool
const eq = a => b => a === b;
 
// fanArrow (&&&) :: (a -> b) -> (a -> c) -> (a -> (b, c))
const fanArrow = f =>
// Compose a function from a simple value to a tuple of
// the separate outputs of two different functions.
g => x => Tuple(f(x))(g(x));
 
// filter :: (a -> Bool) -> [a] -> [a]
const filter = f => xs => xs.filter(f);
 
// fst :: (a, b) -> a
const fst = tpl => tpl[0];
 
// fTable :: String -> (a -> String) -> (b -> String)
// -> (a -> b) -> [a] -> String
const fTable = s => xShow => fxShow => f => xs => {
// Heading -> x display function ->
// fx display function ->
// f -> values -> tabular string
const
ys = xs.map(xShow),
w = Math.max(...ys.map(length));
return s + '\n' + zipWith(
a => b => a.padStart(w, ' ') + ' -> ' + b
)(ys)(
xs.map(x => fxShow(f(x)))
).join('\n');
};
 
// groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
const groupBy = fEq =>
// Typical usage: groupBy(on(eq)(f), xs)
xs => 0 < xs.length ? (() => {
const
tpl = xs.slice(1).reduce(
(gw, x) => {
const
gps = gw[0],
wkg = gw[1];
return fEq(wkg[0])(x) ? (
Tuple(gps)(wkg.concat([x]))
) : Tuple(gps.concat([wkg]))([x]);
},
Tuple([])([xs[0]])
);
return tpl[0].concat([tpl[1]])
})() : [];
 
// length :: [a] -> Int
const length = xs =>
// Returns Infinity over objects without finite length.
// This enables zip and zipWith to choose the shorter
// argument when one is non-finite, like cycle, repeat etc
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;
 
// map :: (a -> b) -> [a] -> [b]
const map = f => xs =>
(Array.isArray(xs) ? (
xs
) : xs.split('')).map(f);
 
// maybe :: b -> (a -> b) -> Maybe a -> b
const maybe = v =>
// Default value (v) if m is Nothing, or f(m.Just)
f => m => m.Nothing ? v : f(m.Just);
 
// on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
const on = f =>
g => a => b => f(g(a))(g(b));
 
// ord :: Char -> Int
const ord = c => c.codePointAt(0);
 
// showHex :: Int -> String
const showHex = n =>
n.toString(16);
 
// snd :: (a, b) -> b
const snd = tpl => tpl[1];
 
// sortOn :: Ord b => (a -> b) -> [a] -> [a]
const sortOn = f =>
// Equivalent to sortBy(comparing(f)), but with f(x)
// evaluated only once for each x in xs.
// ('Schwartzian' decorate-sort-undecorate).
xs => xs.map(
x => [f(x), x]
).sort(
(a, b) => a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0)
).map(x => x[1]);
 
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = n => xs =>
'GeneratorFunction' !== xs.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat.apply([], Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
 
// uncurry :: (a -> b -> c) -> ((a, b) -> c)
const uncurry = f =>
(x, y) => f(x)(y)
 
// zip :: [a] -> [b] -> [(a, b)]
const zip = xs => ys => {
const
lng = Math.min(length(xs), length(ys)),
vs = take(lng)(ys);
return take(lng)(xs)
.map((x, i) => Tuple(x)(vs[i]));
};
 
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
xs => ys => {
const
lng = Math.min(length(xs), length(ys)),
vs = take(lng)(ys);
return take(lng)(xs)
.map((x, i) => f(x)(vs[i]));
};
 
// MAIN ---
return main();
})();
Output:
First duplicated character, if any:
                                     '' (0) -> None
                                    '.' (1) -> None
                               'abcABC' (6) -> None
                              'XYZ ZYX' (7) -> 'X' (0x58) at 0, 6
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' (36) -> '0' (0x30) at 9, 24


Or, as an alternative to sorting and grouping – folding a string down to a dictionary of indices:

(() => {
'use strict';
 
// duplicatedCharIndices :: String -> Maybe (Char, [Int])
const duplicatedCharIndices = s =>
minimumByMay(
comparing(compose(fst, snd))
)(filter(x => 1 < x[1].length)(
Object.entries(
s.split('').reduce(
(a, c, i) => Object.assign(a, {
[c]: (a[c] || []).concat(i)
}), {}
)
)
));
 
// ------------------------TEST------------------------
const main = () =>
console.log(
fTable('First duplicated character, if any:')(
s => `'${s}' (${s.length })`
)(maybe('None')(tpl => {
const [c, ixs] = Array.from(tpl);
return `'${c}' (0x${showHex(ord(c))}) at ${ixs.join(', ')}`
}))(duplicatedCharIndices)([
"", ".", "abcABC", "XYZ ZYX",
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"
])
);
 
 
// -----------------GENERIC FUNCTIONS------------------
 
// Just :: a -> Maybe a
const Just = x => ({
type: 'Maybe',
Nothing: false,
Just: x
});
 
// Nothing :: Maybe a
const Nothing = () => ({
type: 'Maybe',
Nothing: true,
});
 
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a => b => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});
 
// chars :: String -> [Char]
const chars = s => s.split('');
 
// comparing :: (a -> b) -> (a -> a -> Ordering)
const comparing = f =>
x => y => {
const
a = f(x),
b = f(y);
return a < b ? -1 : (a > b ? 1 : 0);
};
 
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
x => fs.reduceRight((a, f) => f(a), x);
 
// enumFrom :: Enum a => a -> [a]
function* enumFrom(x) {
let v = x;
while (true) {
yield v;
v = 1 + v;
}
}
 
// filter :: (a -> Bool) -> [a] -> [a]
const filter = f => xs => xs.filter(f);
 
// fst :: (a, b) -> a
const fst = tpl => tpl[0];
 
// fTable :: String -> (a -> String) -> (b -> String)
// -> (a -> b) -> [a] -> String
const fTable = s => xShow => fxShow => f => xs => {
// Heading -> x display function ->
// fx display function ->
// f -> values -> tabular string
const
ys = xs.map(xShow),
w = Math.max(...ys.map(length));
return s + '\n' + zipWith(
a => b => a.padStart(w, ' ') + ' -> ' + b
)(ys)(
xs.map(x => fxShow(f(x)))
).join('\n');
};
 
// length :: [a] -> Int
const length = xs =>
// Returns Infinity over objects without finite length.
// This enables zip and zipWith to choose the shorter
// argument when one is non-finite, like cycle, repeat etc
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;
 
// maybe :: b -> (a -> b) -> Maybe a -> b
const maybe = v =>
// Default value (v) if m is Nothing, or f(m.Just)
f => m => m.Nothing ? v : f(m.Just);
 
// minimumByMay :: (a -> a -> Ordering) -> [a] -> Maybe a
const minimumByMay = f =>
xs => xs.reduce((a, x) =>
a.Nothing ? Just(x) : (
f(x)(a.Just) < 0 ? Just(x) : a
), Nothing());
 
// ord :: Char -> Int
const ord = c => c.codePointAt(0);
 
// showHex :: Int -> String
const showHex = n =>
n.toString(16);
 
// snd :: (a, b) -> b
const snd = tpl =>
tpl[1];
 
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = n =>
xs => 'GeneratorFunction' !== xs.constructor.constructor.name ? (
xs.slice(0, n)
) : [].concat.apply([], Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}));
 
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
xs => ys => {
const
lng = Math.min(length(xs), length(ys)),
vs = take(lng)(ys);
return take(lng)(xs)
.map((x, i) => f(x)(vs[i]));
};
 
// MAIN ---
return main();
})();
Output:
First duplicated character, if any:
                                     '' (0) -> None
                                    '.' (1) -> None
                               'abcABC' (6) -> None
                              'XYZ ZYX' (7) -> 'X' (0x58) at 0, 6
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' (36) -> '0' (0x30) at 9, 24

Julia[edit]

arr(s) = [c for c in s]
alldup(a) = filter(x -> length(x) > 1, [findall(x -> x == a[i], a) for i in 1:length(a)])
firstduplicate(s) = (a = arr(s); d = alldup(a); isempty(d) ? nothing : first(d))
 
function testfunction(strings)
println("String | Length | All Unique | First Duplicate | Positions\n" *
"-------------------------------------------------------------------------------------")
for s in strings
n = firstduplicate(s)
a = arr(s)
println(rpad(s, 38), rpad(length(s), 11), n == nothing ? "yes" :
rpad("no $(a[n[1]])", 26) * rpad(n[1], 4) * "$(n[2])")
end
end
 
testfunction([
"",
".",
"abcABC",
"XYZ ZYX",
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ",
"hétérogénéité",
"🎆🎃🎇🎈",
"😍😀🙌💃😍🙌",
"🐠🐟🐡🦈🐬🐳🐋🐡",
])
 
Output:
String                            | Length | All Unique | First Duplicate (Hex) | Positions
-------------------------------------------------------------------------------------------
                                      0          yes
.                                     1          yes
abcABC                                6          yes
XYZ ZYX                               7          no             X  (58)            1   7
1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ  36         no             0  (30)            10  25
hétérogénéité                         13         no             é  (e9)            2   4
🎆🎃🎇🎈                             4          yes
😍😀🙌💃😍🙌                        6          no           😍  (1f60d)         1   5
🐠🐟🐡🦈🐬🐳🐋🐡                   8          no           🐡  (1f421)         3   8

Kotlin[edit]

Translation of: Java
import java.util.HashMap
 
fun main() {
System.out.printf("%-40s  %2s  %10s  %8s  %s  %s%n", "String", "Length", "All Unique", "1st Diff", "Hex", "Positions")
System.out.printf("%-40s  %2s  %10s  %8s  %s  %s%n", "------------------------", "------", "----------", "--------", "---", "---------")
for (s in arrayOf("", ".", "abcABC", "XYZ ZYX", "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ")) {
processString(s)
}
}
 
private fun processString(input: String) {
val charMap: MutableMap<Char, Int?> = HashMap()
var dup = 0.toChar()
var index = 0
var pos1 = -1
var pos2 = -1
for (key in input.toCharArray()) {
index++
if (charMap.containsKey(key)) {
dup = key
pos1 = charMap[key]!!
pos2 = index
break
}
charMap[key] = index
}
val unique = if (dup.toInt() == 0) "yes" else "no"
val diff = if (dup.toInt() == 0) "" else "'$dup'"
val hex = if (dup.toInt() == 0) "" else Integer.toHexString(dup.toInt()).toUpperCase()
val position = if (dup.toInt() == 0) "" else "$pos1 $pos2"
System.out.printf("%-40s  %-6d  %-10s  %-8s  %-3s  %-5s%n", input, input.length, unique, diff, hex, position)
}
Output:
String                                    Length  All Unique  1st Diff  Hex  Positions
------------------------                  ------  ----------  --------  ---  ---------
                                          0       yes                             
.                                         1       yes                             
abcABC                                    6       yes                             
XYZ ZYX                                   7       no          'Z'       5A   3 5  
1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ      36      no          '0'       30   10 25

Nanoquery[edit]

def analyze(s)
s = str(s)
println "Examining [" + s + "] which has a length of " + str(len(s)) + ":"
 
if len(s) < 2
println "\tAll characters in the string are unique."
return
end
 
seen = list()
for i in range(0, len(s) - 2)
if s[i] in seen
println "\tNot all characters in the string are unique."
println "\t'" + s[i] + "' " + format("(0x%x)", ord(s[i])) +\
" is duplicated at positions " + str(i + 1) + " and " +\
str(s.indexOf(s[i]) + 1)
return
end
seen.append(s[i])
end
 
println "\tAll characters in the string are unique."
end
 
tests = {"", ".", "abcABC", "XYZ ZYX", "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"}
for s in tests
analyze(s)
end
Output:
Examining [] which has a length of 0:
        All characters in the string are unique.
Examining [.] which has a length of 1:
        All characters in the string are unique.
Examining [abcABC] which has a length of 6:
        All characters in the string are unique.
Examining [XYZ ZYX] which has a length of 7:
        Not all characters in the string are unique.
        'Z' (0x5a) is duplicated at positions 5 and 3
Examining [1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ] which has a length of 36:
        Not all characters in the string are unique.
        '0' (0x30) is duplicated at positions 25 and 10

Perl[edit]

use strict;
use warnings;
use feature 'say';
use utf8;
binmode(STDOUT, ':utf8');
use List::AllUtils qw(uniq);
use Unicode::UCD 'charinfo';
 
for my $str (
'',
'.',
'abcABC',
'XYZ ZYX',
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ',
'01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X',
'Δ👍👨👍Δ',
'ΔδΔ̂ΔΛ',
) {
my @S;
push @S, $1 while $str =~ /(\X)/g;
printf qq{\n"$str" (length: %d) has }, scalar @S;
if (@S != uniq @S ) {
say "duplicated characters:";
my %P;
push @{ $P{$S[$_]} }, 1+$_ for 0..$#S;
for my $k (sort keys %P) {
next unless @{$P{$k}} > 1;
printf "'%s' %s (0x%x) in positions: %s\n", $k, charinfo(ord $k)->{'name'}, ord($k), join ', ', @{$P{$k}};
}
} else {
say "no duplicated characters."
}
}
Output:
"" (length: 0) has no duplicated characters.

"." (length: 1) has no duplicated characters.

"abcABC" (length: 6) has no duplicated characters.

"XYZ ZYX" (length: 7) has duplicated characters:
'X' LATIN CAPITAL LETTER X (0x58) in positions: 1, 7
'Y' LATIN CAPITAL LETTER Y (0x59) in positions: 2, 6
'Z' LATIN CAPITAL LETTER Z (0x5a) in positions: 3, 5

"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" (length: 36) has duplicated characters:
'0' DIGIT ZERO (0x30) in positions: 10, 25

"01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X" (length: 39) has duplicated characters:
'0' DIGIT ZERO (0x30) in positions: 1, 11, 26, 38
'X' LATIN CAPITAL LETTER X (0x58) in positions: 35, 39

"Δ👍👨👍Δ" (length: 5) has duplicated characters:
'Δ' GREEK CAPITAL LETTER DELTA (0x394) in positions: 1, 5
'👍' THUMBS UP SIGN (0x1f44d) in positions: 2, 4

"ΔδΔ̂ΔΛ" (length: 5) has duplicated characters:
'Δ' GREEK CAPITAL LETTER DELTA (0x394) in positions: 1, 4

Phix[edit]

As with Determine_if_a_string_has_all_the_same_characters#Phix, you can use utf8_to_utf32() when needed.

procedure all_uniq(sequence s)
string msg = "all characters are unique"
for i=1 to length(s) do
integer si = s[i],
r = find(si,s,i+1) -- (or maybe rfind(si,s,i-1))
if r then
msg = sprintf(`first duplicate character "%c"(#%02x) at positions %d and %d`,{si,si,i,r})
exit
end if
end for
printf(1,"\"%s\" (length %d): %s\n",{s,length(s),msg})
end procedure
 
constant tests = {"",".","abcABC","XYZ ZYX","1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"}
for i=1 to length(tests) do all_uniq(tests[i]) end for
Output:
"" (length 0): all characters are unique
"." (length 1): all characters are unique
"abcABC" (length 6): all characters are unique
"XYZ ZYX" (length 7): first duplicate character "X"(#58) at positions 1 and 7
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" (length 36): first duplicate character "0"(#30) at positions 10 and 25

PicoLisp[edit]

(de burn (Lst)
(let P 0
(by
'((A)
(set A (inc 'P))
(put A 'P (char A)) )
group
Lst ) ) )
(de first (Lst)
(mini
'((L)
(nand
(cdr L)
(apply min (mapcar val L)) ) )
Lst ) )
(de uniq? (Str)
(let M (first (burn (chop Str)))
(ifn M
(prinl Str " (length " (length Str) "): all characters are unique")
(prin
Str " (length " (length Str) "): first duplicate character "
(car M)
" at positions " )
(println (mapcar val M)) ) ) )
(uniq?)
(uniq? ".")
(uniq? "abcABC")
(uniq? "XYZ ZYX")
(uniq? "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ")
Output:
 (length 0): all characters are unique
. (length 1): all characters are unique
abcABC (length 6): all characters are unique
XYZ ZYX (length 7): first duplicate character X at positions (1 7)
1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ (length 36): first duplicate character 0 at positions (10 25)

Prolog[edit]

report_duplicates(S) :-
duplicates(S, Dups),
format('For value "~w":~n', S),
report(Dups),
nl.
 
report(Dups) :-
maplist(only_one_position, Dups),
format(' All characters are unique~n').
 
report(Dups) :-
exclude(only_one_position, Dups, [c(Char,Positions)|_]),
reverse(Positions, PosInOrder),
atomic_list_concat(PosInOrder, ', ', PosAsList),
format(' The character ~w is non unique at ~p~n', [Char, PosAsList]).
 
only_one_position(c(_,[_])).
 
duplicates(S, Count) :-
atom_chars(S, Chars),
char_count(Chars, 0, [], Count).
 
char_count([], _, C, C).
char_count([C|T], Index, Counted, Result) :-
select(c(C,Positions), Counted, MoreCounted),
succ(Index, Index1),
char_count(T, Index1, [c(C,[Index|Positions])|MoreCounted], Result).
char_count([C|T], Index, Counted, Result) :-
\+ member(c(C,_), Counted),
succ(Index, Index1),
char_count(T, Index1, [c(C,[Index])|Counted], Result).
 
test :- report_duplicates('').
test :- report_duplicates('.').
test :- report_duplicates('abcABC').
test :- report_duplicates('XYZ ZYX').
test :- report_duplicates('1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ').
Output:
?- forall(test, true).
For value "":
    All characters are unique

For value ".":
    All characters are unique

For value "abcABC":
    All characters are unique

For value "XYZ ZYX":
    The character X is non unique at '0, 6'

For value "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ":
    The character 0 is non unique at '9, 24'

true.

?-

Python[edit]

Functional[edit]

Defined in terms of itertools.groupby:

'''Determine if a string has all unique characters'''
 
from itertools import groupby
 
 
# duplicatedCharIndices :: String -> Maybe (Char, [Int])
def duplicatedCharIndices(s):
'''Just the first duplicated character, and
the indices of its occurrence, or
Nothing if there are no duplications.
'''

def go(xs):
if 1 < len(xs):
duplicates = list(filter(lambda kv: 1 < len(kv[1]), [
(k, list(v)) for k, v in groupby(
sorted(xs, key=swap),
key=snd
)
]))
return Just(second(fmap(fst))(
sorted(
duplicates,
key=lambda kv: kv[1][0]
)[0]
)) if duplicates else Nothing()
else:
return Nothing()
return go(list(enumerate(s)))
 
 
# TEST ----------------------------------------------------
# main :: IO ()
def main():
'''Test over various strings.'''
 
def showSample(s):
return repr(s) + ' (' + str(len(s)) + ')'
 
def showDuplicate(cix):
c, ix = cix
return repr(c) + (
' (' + hex(ord(c)) + ') at ' + repr(ix)
)
 
print(
fTable('First duplicated character, if any:')(
showSample
)(maybe('None')(showDuplicate))(duplicatedCharIndices)([
'', '.', 'abcABC', 'XYZ ZYX',
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ'
])
)
 
 
# FORMATTING ----------------------------------------------
 
# fTable :: String -> (a -> String) ->
# (b -> String) -> (a -> b) -> [a] -> String
def fTable(s):
'''Heading -> x display function -> fx display function ->
f -> xs -> tabular string.
'''

def go(xShow, fxShow, f, xs):
ys = [xShow(x) for x in xs]
w = max(map(len, ys))
return s + '\n' + '\n'.join(map(
lambda x, y: y.rjust(w, ' ') + ' -> ' + fxShow(f(x)),
xs, ys
))
return lambda xShow: lambda fxShow: lambda f: lambda xs: go(
xShow, fxShow, f, xs
)
 
 
# GENERIC -------------------------------------------------
 
# Just :: a -> Maybe a
def Just(x):
'''Constructor for an inhabited Maybe (option type) value.
Wrapper containing the result of a computation.
'''

return {'type': 'Maybe', 'Nothing': False, 'Just': x}
 
 
# Nothing :: Maybe a
def Nothing():
'''Constructor for an empty Maybe (option type) value.
Empty wrapper returned where a computation is not possible.
'''

return {'type': 'Maybe', 'Nothing': True}
 
 
# fmap :: (a -> b) -> [a] -> [b]
def fmap(f):
'''fmap over a list.
f lifted to a function over a list.
'''

return lambda xs: [f(x) for x in xs]
 
 
# fst :: (a, b) -> a
def fst(tpl):
'''First member of a pair.'''
return tpl[0]
 
 
# head :: [a] -> a
def head(xs):
'''The first element of a non-empty list.'''
return xs[0] if isinstance(xs, list) else next(xs)
 
 
# maybe :: b -> (a -> b) -> Maybe a -> b
def maybe(v):
'''Either the default value v, if m is Nothing,
or the application of f to x,
where m is Just(x).
'''

return lambda f: lambda m: v if (
None is m or m.get('Nothing')
) else f(m.get('Just'))
 
 
# second :: (a -> b) -> ((c, a) -> (c, b))
def second(f):
'''A simple function lifted to a function over a tuple,
with f applied only to the second of two values.
'''

return lambda xy: (xy[0], f(xy[1]))
 
 
# snd :: (a, b) -> b
def snd(tpl):
'''Second member of a pair.'''
return tpl[1]
 
 
# swap :: (a, b) -> (b, a)
def swap(tpl):
'''The swapped components of a pair.'''
return (tpl[1], tpl[0])
 
 
# MAIN ---
if __name__ == '__main__':
main()
Output:
First duplicated character, if any:
                                     '' (0) -> None
                                    '.' (1) -> None
                               'abcABC' (6) -> None
                              'XYZ ZYX' (7) -> 'X' (0x58) at [0, 6]
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' (36) -> '0' (0x30) at [9, 24]


Or, as an alternative to sorting and grouping, folding a string down to a dictionary with reduce:

'''Determine if a string has all unique characters'''
 
from functools import reduce
 
 
# duplicatedCharIndices :: String -> Maybe (Char, [Int])
def duplicatedCharIndices(s):
'''Just the first duplicated character, and
the indices of its occurrence, or
Nothing if there are no duplications.
'''

def go(dct, ic):
i, c = ic
return dict(
dct,
**{c: dct[c] + [i] if c in dct else [i]}
)
duplicates = [
(k, v) for (k, v)
in reduce(go, enumerate(s), {}).items()
if 1 < len(v)
]
return Just(
min(duplicates, key=compose(head, snd))
) if duplicates else Nothing()
 
 
# And another alternative here would be to fuse the 1 < len(v)
# filtering, and the min() search for the earliest duplicate,
# down to a single `earliestDuplication` fold:
 
# duplicatedCharIndices_ :: String -> Maybe (Char, [Int])
def duplicatedCharIndices_(s):
'''Just the first duplicated character, and
the indices of its occurrence, or
Nothing if there are no duplications.
'''

def positionRecord(dct, ic):
i, c = ic
return dict(
dct,
**{c: dct[c] + [i] if c in dct else [i]}
)
 
def earliestDuplication(sofar, charPosns):
c, indices = charPosns
return (
maybe(Just((c, indices)))(
lambda kxs: Just((c, indices)) if (
# Earlier duplication ?
indices[0] < kxs[1][0]
) else sofar
)(sofar)
) if 1 < len(indices) else sofar
 
return reduce(
earliestDuplication,
reduce(
positionRecord,
enumerate(s),
{}
).items(),
Nothing()
)
 
 
# TEST ----------------------------------------------------
# main :: IO ()
def main():
'''Test over various strings.'''
 
def showSample(s):
return repr(s) + ' (' + str(len(s)) + ')'
 
def showDuplicate(cix):
c, ix = cix
return repr(c) + (
' (' + hex(ord(c)) + ') at ' + repr(ix)
)
 
print(
fTable('First duplicated character, if any:')(
showSample
)(maybe('None')(showDuplicate))(duplicatedCharIndices_)([
'', '.', 'abcABC', 'XYZ ZYX',
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ'
])
)
 
 
# FORMATTING ----------------------------------------------
 
# fTable :: String -> (a -> String) ->
# (b -> String) -> (a -> b) -> [a] -> String
def fTable(s):
'''Heading -> x display function -> fx display function ->
f -> xs -> tabular string.
'''

def go(xShow, fxShow, f, xs):
ys = [xShow(x) for x in xs]
w = max(map(len, ys))
return s + '\n' + '\n'.join(map(
lambda x, y: y.rjust(w, ' ') + ' -> ' + fxShow(f(x)),
xs, ys
))
return lambda xShow: lambda fxShow: lambda f: lambda xs: go(
xShow, fxShow, f, xs
)
 
 
# GENERIC -------------------------------------------------
 
# Just :: a -> Maybe a
def Just(x):
'''Constructor for an inhabited Maybe (option type) value.
Wrapper containing the result of a computation.
'''

return {'type': 'Maybe', 'Nothing': False, 'Just': x}
 
 
# Nothing :: Maybe a
def Nothing():
'''Constructor for an empty Maybe (option type) value.
Empty wrapper returned where a computation is not possible.
'''

return {'type': 'Maybe', 'Nothing': True}
 
 
# compose :: ((a -> a), ...) -> (a -> a)
def compose(*fs):
'''Composition, from right to left,
of a series of functions.
'''

return lambda x: reduce(
lambda a, f: f(a),
fs[::-1], x
)
 
 
# head :: [a] -> a
def head(xs):
'''The first element of a non-empty list.'''
return xs[0] if isinstance(xs, list) else next(xs)
 
 
# maybe :: b -> (a -> b) -> Maybe a -> b
def maybe(v):
'''Either the default value v, if m is Nothing,
or the application of f to x,
where m is Just(x).
'''

return lambda f: lambda m: v if (
None is m or m.get('Nothing')
) else f(m.get('Just'))
 
 
# snd :: (a, b) -> b
def snd(tpl):
'''Second member of a pair.'''
return tpl[1]
 
 
# MAIN ---
if __name__ == '__main__':
main()
Output:
First duplicated character, if any:
                                     '' (0) -> None
                                    '.' (1) -> None
                               'abcABC' (6) -> None
                              'XYZ ZYX' (7) -> 'X' (0x58) at [0, 6]
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' (36) -> '0' (0x30) at [9, 24]

Raku[edit]

(formerly Perl 6)

Works with: Rakudo version 2019.07.1

Raku works with unicode natively and handles combining characters and multi-byte emoji correctly. In the last string, notice the the length is correctly shown as 11 characters and that the delta with a combining circumflex in position 6 is not the same as the deltas without in positions 5 & 9.

  -> $str {
my $i = 0;
print "\n{$str.perl} (length: {$str.chars}), has ";
my %m;
%m{$_}.push: ++$i for $str.comb;
if any(%m.values) > 1 {
say "duplicated characters:";
say "'{.key}' ({.key.uninames}; hex ordinal: {(.key.ords).fmt: "0x%X"})" ~
" in positions: {.value.join: ', '}" for %m.grep( *.value > 1 ).sort( *.value[0] );
} else {
say "no duplicated characters."
}
} for
'',
'.',
'abcABC',
'XYZ ZYX',
'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ',
'01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X',
'🦋🙂👨‍👩‍👧‍👦🙄ΔΔ̂ 🦋Δ👍👨‍👩‍👧‍👦'
Output:
"" (length: 0), has no duplicated characters.

"." (length: 1), has no duplicated characters.

"abcABC" (length: 6), has no duplicated characters.

"XYZ ZYX" (length: 7), has duplicated characters:
'X' (LATIN CAPITAL LETTER X; hex ordinal: 0x58) in positions: 1, 7
'Y' (LATIN CAPITAL LETTER Y; hex ordinal: 0x59) in positions: 2, 6
'Z' (LATIN CAPITAL LETTER Z; hex ordinal: 0x5A) in positions: 3, 5

"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" (length: 36), has duplicated characters:
'0' (DIGIT ZERO; hex ordinal: 0x30) in positions: 10, 25

"01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X" (length: 39), has duplicated characters:
'0' (DIGIT ZERO; hex ordinal: 0x30) in positions: 1, 11, 26, 38
'X' (LATIN CAPITAL LETTER X; hex ordinal: 0x58) in positions: 35, 39

"🦋🙂👨‍👩‍👧‍👦🙄ΔΔ̂ 🦋Δ👍👨‍👩‍👧‍👦" (length: 11), has duplicated characters:
'🦋' (BUTTERFLY; hex ordinal: 0x1F98B) in positions: 1, 8
'👨‍👩‍👧‍👦' (MAN ZERO WIDTH JOINER WOMAN ZERO WIDTH JOINER GIRL ZERO WIDTH JOINER BOY; hex ordinal: 0x1F468 0x200D 0x1F469 0x200D 0x1F467 0x200D 0x1F466) in positions: 3, 11
'Δ' (GREEK CAPITAL LETTER DELTA; hex ordinal: 0x394) in positions: 5, 9

REXX[edit]

/*REXX pgm determines if a string is comprised of all unique characters (no duplicates).*/
@.= /*assign a default for the @. array. */
parse arg @.1 /*obtain optional argument from the CL.*/
if @.1='' then do; @.1= /*Not specified? Then assume defaults.*/
@.2= .
@.3= 'abcABC'
@.4= 'XYZ ZYX'
@.5= '1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ'
end
 
do j=1; if j\==1 & @.j=='' then leave /*String is null & not j=1? We're done*/
say copies('─', 79) /*display a separator line (a fence). */
say 'Testing for the string (length' length(@.j)"): " @.j
say
dup= isUnique(@.j)
say 'The characters in the string' word("are aren't", 1 + (dup>0) ) 'all unique.'
if dup==0 then iterate
 ?= substr(@.j, dup, 1)
say 'The character '  ? " ('"c2x(?)"'x) at position " dup ,
' is repeated at position ' pos(?, @.j, dup+1)
end /*j*/
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
isUnique: procedure; parse arg x /*obtain the character string.*/
do k=1 to length(x) - 1 /*examine all but the last. */
p= pos( substr(x, k, 1), x, k + 1) /*see if the Kth char is a dup*/
if p\==0 then return k /*Find a dup? Return location.*/
end /*k*/
return 0 /*indicate all chars unique. */
output   when using the internal defaults
───────────────────────────────────────────────────────────────────────────────
Testing for the string (length 0):

The characters in the string are all unique.
───────────────────────────────────────────────────────────────────────────────
Testing for the string (length 1):  .

The characters in the string are all unique.
───────────────────────────────────────────────────────────────────────────────
Testing for the string (length 6):  abcABC

The characters in the string are all unique.
───────────────────────────────────────────────────────────────────────────────
Testing for the string (length 7):  XYZ ZYX

The characters in the string aren't all unique.
The character  X  ('58'x)  at position  1  is repeated at position  7
───────────────────────────────────────────────────────────────────────────────
Testing for the string (length 36):  1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ

The characters in the string aren't all unique.
The character  0  ('30'x)  at position  10  is repeated at position  25

Ruby[edit]

strings = ["",
".",
"abcABC",
"XYZ ZYX",
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ",
"01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X",
"hétérogénéité",
"🎆🎃🎇🎈",
"😍😀🙌💃😍🙌",
"🐠🐟🐡🦈🐬🐳🐋🐡",]
 
strings.each do |str|
seen = {}
print "#{str.inspect} (size #{str.size}) "
res = "has no duplicates." #may change
str.chars.each_with_index do |c,i|
if seen[c].nil?
seen[c] = i
else
res = "has duplicate char #{c} (#{'%#x' % c.ord}) on #{seen[c]} and #{i}."
break
end
end
puts res
end
 
Output:
"" (size 0) has no duplicates.
"." (size 1) has no duplicates.
"abcABC" (size 6) has no duplicates.
"XYZ ZYX" (size 7) has duplicate char Z (0x5a) on 2 and 4.
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ" (size 36) has duplicate char 0 (0x30) on 9 and 24.
"01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X" (size 39) has duplicate char 0 (0x30) on 0 and 10.
"hétérogénéité" (size 13) has duplicate char é (0xe9) on 1 and 3.
"🎆🎃🎇🎈" (size 4) has no duplicates.
"😍😀🙌💃😍🙌" (size 6) has duplicate char 😍 (0x1f60d) on 0 and 4.
"🐠🐟🐡🦈🐬🐳🐋🐡" (size 8) has duplicate char 🐡 (0x1f421) on 2 and 7.

Sidef[edit]

func index_duplicates(str) {
gather {
for k,v in (str.chars.kv) {
var i = str.index(v, k+1)
take([k, i]) if (i != -1)
}
}
}
 
var strings = [
"", ".", "abcABC", "XYZ ZYX",
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ",
"01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X",
"hétérogénéité", "🎆🎃🎇🎈", "😍😀🙌💃😍🙌",
"🐠🐟🐡🦈🐬🐳🐋🐡"
]
 
strings.each {|str|
print "\n'#{str}' (size: #{str.len}) "
var dups = index_duplicates(str)
say "has duplicated characters:" if dups
for i,j in (dups) {
say "#{str[i]} (#{'%#x' % str[i].ord}) in positions: #{i}, #{j}"
}
say "has no duplicates." if !dups
}
Output:
'' (size: 0) has no duplicates.

'.' (size: 1) has no duplicates.

'abcABC' (size: 6) has no duplicates.

'XYZ ZYX' (size: 7) has duplicated characters:
X (0x58) in positions: 0, 6
Y (0x59) in positions: 1, 5
Z (0x5a) in positions: 2, 4

'1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' (size: 36) has duplicated characters:
0 (0x30) in positions: 9, 24

'01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X' (size: 39) has duplicated characters:
0 (0x30) in positions: 0, 10
0 (0x30) in positions: 10, 25
0 (0x30) in positions: 25, 37
X (0x58) in positions: 34, 38

'hétérogénéité' (size: 13) has duplicated characters:
é (0xe9) in positions: 1, 3
t (0x74) in positions: 2, 11
é (0xe9) in positions: 3, 7
é (0xe9) in positions: 7, 9
é (0xe9) in positions: 9, 12

'🎆🎃🎇🎈' (size: 4) has no duplicates.

'😍😀🙌💃😍🙌' (size: 6) has duplicated characters:
😍 (0x1f60d) in positions: 0, 4
🙌 (0x1f64c) in positions: 2, 5

'🐠🐟🐡🦈🐬🐳🐋🐡' (size: 8) has duplicated characters:
🐡 (0x1f421) in positions: 2, 7

Tcl[edit]

package require Tcl 8.6 ; # For binary encode
 
array set yesno {1 Yes 2 No}
 
set test {
{}
{.}
{abcABC}
{XYZ ZYX}
{1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ}
{hétérogénéité}
}
 
# Loop through test strings
foreach str $test {
set chars [dict create] ; # init dictionary
set num_chars 1 ; # In case of empty string
 
# Loop through characters in string
for {set i 0} {$i < [string length $str]} {incr i} {
set c [string index $str $i] ; # get char at index
dict lappend chars $c $i ; # add index to a running list for key=char
set indexes [dict get $chars $c] ; # get the whole running list
set num_chars [llength $indexes] ; # count the # of indexes
if {$num_chars > 1} {
break ; # Found a duplicate, break out of the loop
}
}
 
# Handle Output
puts [format "Tested: %38s (len: %2d). All unique? %3s. " \
"'$str'" [string length $str] $yesno($num_chars)]
if {$num_chars > 1} {
puts [format " --> Character '%s' (hex: 0x%s) reappears at indexes: %s." \
$c [binary encode hex $c] $indexes]
}
}
 
Output:
Tested:                                     '' (len:  0). All unique? Yes.
Tested:                                    '.' (len:  1). All unique? Yes.
Tested:                               'abcABC' (len:  6). All unique? Yes.
Tested:                              'XYZ ZYX' (len:  7). All unique?  No.
 --> Character 'Z' (hex: 0x5a) reappears at indexes: 2 4.
Tested: '1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ' (len: 36). All unique?  No.
 --> Character '0' (hex: 0x30) reappears at indexes: 9 24.
Tested:                        'hétérogénéité' (len: 13). All unique?  No.
 --> Character 'é' (hex: 0xe9) reappears at indexes: 1 3.

XPL0[edit]

include xpllib;                 \contains StrLen function
 
proc StrUnique(S); \Show if string has unique chars
char S;
int L, I, J, K;
[L:= StrLen(S);
IntOut(0, L); Text(0, ": ^""); Text(0, S); ChOut(0, ^"); CrLf(0);
for I:= 0 to L-1 do
for J:= I+1 to L-1 do
[if S(I) = S(J) then
[ChOut(0, \tab\ 9);
for K:= 0 to I do ChOut(0, ^ );
ChOut(0, ^^);
for K:= 0 to J-I-2 do ChOut(0, ^ );
ChOut(0, ^^);
Text(0, " Duplicate character: ");
ChOut(0, S(I));
Text(0, ", hex ");
SetHexDigits(2);
HexOut(0, S(I));
CrLf(0);
return;
];
];
Text(0, " Unique, no duplicates"); CrLf(0);
];
 
[Text(0, "Length"); CrLf(0);
StrUnique("");
StrUnique(".");
StrUnique("abcABC");
StrUnique("XYZ ZYX");
StrUnique("1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ");
StrUnique("thequickbrownfoxjumps");
]
Output:
Length
0:      ""
        Unique, no duplicates
1:      "."
        Unique, no duplicates
6:      "abcABC"
        Unique, no duplicates
7:      "XYZ ZYX"
         ^     ^ Duplicate character: X, hex 58
36:     "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"
                  ^              ^ Duplicate character: 0, hex 30
21:     "thequickbrownfoxjumps"
             ^            ^ Duplicate character: u, hex 75

zkl[edit]

fcn stringUniqueness(str){  // Does not handle Unicode
sz,unique,uz,counts := str.len(), str.unique(), unique.len(), str.counts();
println("Length %d: \"%s\"".fmt(sz,str));
if(sz==uz or uz==1) println("\tAll characters are unique");
else // counts is (char,count, char,count, ...)
println("\tDuplicate: ",
counts.pump(List,Void.Read,fcn(str,c,n){
if(n>1){
is,z:=List(),-1; do(n){ is.append(z=str.find(c,z+1)) }
"'%s' (0x%x)[%s]".fmt(c,c.toAsc(),is.concat(","))
}
else Void.Skip
}.fp(str)).concat(", "));
}
testStrings:=T("", ".", "abcABC", "XYZ ZYX", 
"1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ",
"01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X");
foreach s in (testStrings){ stringUniqueness(s) }
Output:
Length 0: ""
	All characters are unique
Length 1: "."
	All characters are unique
Length 6: "abcABC"
	All characters are unique
Length 7: "XYZ ZYX"
	Duplicate: 'X' (0x58)[0,6], 'Y' (0x59)[1,5], 'Z' (0x5a)[2,4]
Length 36: "1234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ"
	Duplicate: '0' (0x30)[9,24]
Length 39: "01234567890ABCDEFGHIJKLMN0PQRSTUVWXYZ0X"
	Duplicate: '0' (0x30)[0,10,25,37], 'X' (0x58)[34,38]