Unique characters

From Rosetta Code
Unique characters is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Task

Given a list of strings,   find characters appearing only in one string and once only.

The result should be given in alphabetical order.


Use the following list for this task:

        ["133252abcdeeffd",  "a6789798st",  "yxcdfgxcyz"]


Other tasks related to string operations:
Metrics
Counting
Remove/replace
Anagrams/Derangements/shuffling
Find/Search/Determine
Formatting
Song lyrics/poems/Mad Libs/phrases
Tokenize
Sequences



11l[edit]

DefaultDict[Char, Int] d
L(s) [‘133252abcdeeffd’, ‘a6789798st’, ‘yxcdfgxcyz’]
   L(c) s
      d[c]++

L(k) sorted(d.keys())
   I d[k] == 1
      print(k, end' ‘’)
Output:
156bgstz

8080 Assembly[edit]

puts:	equ	9		; CP/M print syscall
TERM:	equ	'$'		; CP/M string terminator	
	org	100h
	jmp	demo
	;;;	Given a list of strings, find characters appearing only
	;;;	in one string and once only.
	;;;	Input: DE = list of strings, BC = start of output
unique:	xra	a		; Zero out the workspace
	mvi	h,upage
	mov	l,a
uzbuf:	mov	m,a
	inr	l
	jnz	uzbuf
	push	d 
ustr:	pop 	h
	mov	e,m		; Load next string pointer
	inx	h
	mov 	d,m
	inx	h
	mov	a,d		; End of list?
	ora	e
	jz	uclat		; Then go find the uniques
	push	h		; Otherwise, keep list pointer
	mvi	h,upage
uchar:	ldax	d		; Get character
	cpi	TERM		; Done?
	jz	ustr		; Then do next string
	mov	l,a		; Otherwise, count the character
	inr	m
	inx	d		; Next character
	jmp 	uchar
uclat:	lxi	h,upage*256	; Start of page
utst:	dcr	m		; Is this character included?
	jnz	uskip
	mov	a,l		; If so add it to the output
	stax	b
	inx	b
uskip:	inr	l		; Try next character
	jnz	utst
	mvi	a,TERM		; CP/M string terminator
	stax	b
	ret 
	;;;	Demo code
demo:	lxi 	b,outbuf	; Set BC to location of output buffe 
	lxi	d,list		; Set DE to the list of strings
	call	unique		; Call the code
	mvi	c,puts		; Print the result
	lxi	d,outbuf
	jmp	5
	;;;	List of strings
list:	dw	str1, str2, str3, 0
str1:	db	'133252abcdeeffd', TERM
str2:	db	'a6789798st', TERM 
str3:	db	'yxcdfgxcyz', TERM
	;;;	Memory 
upage:	equ	($/256)+1	; Workspace for 'unique'
outbuf:	equ	(upage+1)*256	; Output
Output:
156bgstz

8086 Assembly[edit]

	cpu	8086
	org	100h
puts:	equ	9		; MS-DOS syscall to print a string
TERM:	equ	'$'		; String terminator
section	.text
	jmp	demo
	;;;	Given a list of strings, find characters appearing
	;;;	only in one string and once only.
	;;;	Input: BX = list of strings, DX = start of output
	;;;	Assuming DS = ES 
unique:	mov 	cx,128		; Zero out array
	mov	di,uniqws
	xor	ax,ax
	rep	stosw
.str:	mov	si,[bx]		; Get next string
	inc	bx
	inc	bx
	test	si,si		; Done?
	jz	.fltr		; Then start writing to output
.char:	lodsb			; Read character
	cmp 	al,TERM		; Done?
	je	.str		; Then get next string
	mov	di,ax		; Otherwise, count the character
	inc	byte [di+uniqws]
	jmp	.char
.fltr:	mov	di,dx
	xor	bx,bx
.fchr:	dec	byte [bx+uniqws]
	jnz	.skip		; Character occurs once?
	mov	al,bl		; If so, write it
	stosb
.skip:	inc	bl		; Any more?
	jnz	.fchr
	mov	[di],byte TERM	; Terminate string
	ret
	;;;	Demo code
demo:	mov	bx,list		; Find unique characters
	mov	dx,outbuf 
	call	unique
	mov	ah,puts		; Print result
	mov	dx,outbuf
	int 	21h
	ret
section	.data
list:	dw	.s1, .s2, .s3, 0
.s1:	db	'133252abcdeeffd', TERM
.s2:	db	'a6789798st', TERM 
.s3:	db	'yxcdfgxcyz', TERM
section	.bss
uniqws:	resb	256
outbuf:	resb	256
Output:
156bgstz

Action![edit]

DEFINE MAX="128"
CHAR ARRAY counts(MAX)

BYTE FUNC GetCount(CHAR ARRAY s CHAR c)
  BYTE count,i

  count=0
  FOR i=1 TO s(0)
  DO
    IF s(i)=c THEN
      count==+1
    FI
  OD
RETURN (count)

PROC UpdateCounts(CHAR ARRAY s)
  BYTE i,c

  FOR i=1 TO s(0)
  DO
    c=s(i)
    counts(c)==+GetCount(s,c)
  OD
RETURN

PROC Main()
  DEFINE PTR="CARD"
  DEFINE CNT="3"
  PTR ARRAY l(CNT)
  INT i

  l(0)="133252abcdeeffd"
  l(1)="a6789798st"
  l(2)="yxcdfgxcyz"

  SetBlock(counts,MAX,0)
  FOR i=0 TO CNT-1
  DO
    UpdateCounts(l(i))
  OD
  FOR i=0 TO MAX-1
  DO
    IF counts(i)=1 THEN
      Put(i) Put(32)
    FI
  OD
RETURN
Output:

Screenshot from Atari 8-bit computer

1 5 6 b g s t z

Ada[edit]

with Ada.Text_Io;

procedure Unique_Characters is

   List : array (Character) of Natural := (others => 0);

   procedure Count (Item : String) is
   begin
      for C of Item loop
         List (C) := List (C) + 1;
      end loop;
   end Count;

   procedure Put_Only_Once is
      use Ada.Text_Io;
   begin
      for C in List'Range loop
         if List (C) = 1 then
            Put (C);
            Put (' ');
         end if;
      end loop;
      New_Line;
   end Put_Only_Once;

begin
   Count ("133252abcdeeffd");
   Count ("a6789798st");
   Count ("yxcdfgxcyz");
   Put_Only_Once;
end Unique_Characters;
Output:
1 5 6 b g s t z

ALGOL 68[edit]

Case sensitive. This assumes a small character set (e.g. ASCII where max abs char is 255). Would probably need some work if CHAR is Unicode.

BEGIN # find the characters that occur once only in a list of stings        #
    # returns the characters that occur only once in the elements of s      #
    OP UNIQUE = ( []STRING s )STRING:
       BEGIN
            [ 0 : max abs char ]INT counts; # counts of used characters     #
            FOR i FROM LWB counts TO UPB counts DO counts[ i ] := 0 OD;
            # count the occurances of the characters in the elements of s   #
            FOR i FROM LWB s TO UPB s DO
                FOR j FROM LWB s[ i ] TO UPB s[ i ] DO
                    counts[ ABS s[ i ][ j ] ] +:= 1
                OD
            OD;
            # construct a string of the characters that occur only once     #
            STRING result := "";
            FOR i FROM LWB counts TO UPB counts DO
                IF counts[ i ] = 1 THEN result +:= REPR i FI
            OD;
            result
       END; # UNIQUE #
    # task test case                                                        #
    print( ( UNIQUE []STRING( "133252abcdeeffd",  "a6789798st",  "yxcdfgxcyz" ), newline ) )
END
Output:
156bgstz

AppleScript[edit]

AppleScriptObjC[edit]

The filtering here is case sensitive, the sorting dependent on locale.

on uniqueCharacters(listOfStrings)
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to ""
    set countedSet to current application's class "NSCountedSet"'s setWithArray:((listOfStrings as text)'s characters)
    set AppleScript's text item delimiters to astid
    set mutableSet to current application's class "NSMutableSet"'s setWithSet:(countedSet)
    tell countedSet to minusSet:(mutableSet)
    tell mutableSet to minusSet:(countedSet)
    set sortDescriptor to current application's class "NSSortDescriptor"'s sortDescriptorWithKey:("self") ¬
        ascending:(true) selector:("localizedStandardCompare:")
    
    return (mutableSet's sortedArrayUsingDescriptors:({sortDescriptor})) as list
end uniqueCharacters
Output:
{"1", "5", "6", "b", "g", "s", "t", "z"}

Core language only[edit]

This isn't quite as fast as the ASObjC solution above, but it can be case-insensitive if required. (Simply leave out the 'considering case' statement round the call to the handler). The requirement for AppleScript 2.3.1 is just for the 'use' command which loads the "Heap Sort" script. If "Heap Sort"'s loaded differently or compiled directly into the code, this script will work on systems at least as far back as Mac OS X 10.5 (Leopard) and possibly earlier. Same output as above.

use AppleScript version "2.3.1" -- OS X 10.9 (Mavericks) or later
use sorter : script "Heap Sort" -- <https://www.rosettacode.org/wiki/Sorting_algorithms/Heapsort#AppleScript>

on uniqueCharacters(listOfStrings)
    script o
        property allCharacters : {}
        property uniques : {}
    end script
    
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to ""
    set o's allCharacters to text items of (listOfStrings as text)
    set AppleScript's text item delimiters to astid
    
    set characterCount to (count o's allCharacters)
    tell sorter to sort(o's allCharacters, 1, characterCount)
    
    set i to 1
    set currentCharacter to beginning of o's allCharacters
    repeat with j from 2 to characterCount
        set thisCharacter to item j of o's allCharacters
        if (thisCharacter is not currentCharacter) then
            if (j - i = 1) then set end of o's uniques to currentCharacter
            set i to j
            set currentCharacter to thisCharacter
        end if
    end repeat
    if (i = j) then set end of o's uniques to currentCharacter
    
    return o's uniques
end uniqueCharacters

considering case
    return uniqueCharacters({"133252abcdeeffd", "a6789798st", "yxcdfgxcyz"})
end considering


Functional[edit]

Composing a solution from existing generic primitives, for speed of drafting and refactoring, and for high levels of code reuse.

use framework "Foundation"


-------------------- UNIQUE CHARACTERS -------------------

-- uniques :: [String] -> String
on uniques(xs)
    script single
        on |λ|(x)
            if 1 = length of x then
                item 1 of x
            else
                {}
            end if
        end |λ|
    end script
    
    concatMap(single, ¬
        group(sort(concatMap(my chars, xs))))
end uniques



--------------------------- TEST -------------------------
on run
    uniques({"133252abcdeeffd", "a6789798st", "yxcdfgxcyz"})
    
    --> {"1", "5", "6", "b", "g", "s", "t", "z"}
end run


------------------------- GENERIC ------------------------

-- chars :: String -> [Char]
on chars(s)
    characters of s
end chars


-- 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
    if {text, string} contains class of xs then
        acc as text
    else
        acc
    end if
end concatMap


-- eq (==) :: Eq a => a -> a -> Bool
on eq(a, b)
    a = b
end eq


-- 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


-- group :: Eq a => [a] -> [[a]]
on group(xs)
    script eq
        on |λ|(a, b)
            a = b
        end |λ|
    end script
    
    groupBy(eq, xs)
end group


-- groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
on groupBy(f, xs)
    -- Typical usage: groupBy(on(eq, f), xs)
    set mf to mReturn(f)
    
    script enGroup
        on |λ|(a, x)
            if length of (active of a) > 0 then
                set h to item 1 of active of a
            else
                set h to missing value
            end if
            
            if h is not missing value and mf's |λ|(h, x) then
                {active:(active of a) & {x}, sofar:sofar of a}
            else
                {active:{x}, sofar:(sofar of a) & {active of a}}
            end if
        end |λ|
    end script
    
    if length of xs > 0 then
        set dct to foldl(enGroup, {active:{item 1 of xs}, sofar:{}}, rest of xs)
        if length of (active of dct) > 0 then
            sofar of dct & {active of dct}
        else
            sofar of dct
        end if
    else
        {}
    end if
end groupBy


-- 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


-- 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


-- sort :: Ord a => [a] -> [a]
on sort(xs)
    ((current application's NSArray's arrayWithArray:xs)'s ¬
        sortedArrayUsingSelector:"compare:") as list
end sort
Output:
{"1", "5", "6", "b", "g", "s", "t", "z"}

APL[edit]

uniques  (⍋⌷⊣)((/⍨)(1=(≢⊢)))
Output:
      uniques '133252abcdeeffd' 'a6789798st' 'yxcdfgxcyz'
156bgstz

Arturo[edit]

arr: ["133252abcdeeffd" "a6789798st" "yxcdfgxcyz"]
str: join arr

print sort select split str 'ch -> 1 = size match str ch
Output:
1 5 6 b g s t z

AWK[edit]

# syntax: GAWK -f UNIQUE_CHARACTERS.AWK
#
# sorting:
#   PROCINFO["sorted_in"] is used by GAWK
#   SORTTYPE is used by Thompson Automation's TAWK
#
BEGIN {
    PROCINFO["sorted_in"] = "@ind_str_asc" ; SORTTYPE = 1
    n = split("133252abcdeeffd,a6789798st,yxcdfgxcyz",arr1,",")
    for (i=1; i<=n; i++) {
      str = arr1[i]
      printf("%s\n",str)
      total_c += leng = length(str)
      for (j=1; j<=leng; j++) {
        arr2[substr(str,j,1)]++
      }
    }
    for (c in arr2) {
      if (arr2[c] == 1) {
        rec = sprintf("%s%s",rec,c)
      }
    }
    printf("%d strings, %d characters, %d different, %d unique: %s\n",n,total_c,length(arr2),length(rec),rec)
    exit(0)
}
Output:
133252abcdeeffd
a6789798st
yxcdfgxcyz
3 strings, 35 characters, 20 different, 8 unique: 156bgstz

BASIC[edit]

10 DEFINT A-Z
20 DIM C(255)
30 READ A$: IF A$="" GOTO 90
40 FOR I=1 TO LEN(A$)
50 A=ASC(MID$(A$,I,1))
60 C(A)=C(A)+1
70 NEXT I
80 GOTO 30
90 FOR I=1 TO 255
100 IF C(I)=1 THEN A$=A$+CHR$(I)
110 NEXT I
120 PRINT A$
130 DATA "133252abcdeeffd"
140 DATA "a6789798st"
150 DATA "yxcdfgxcyz"
160 DATA ""
Output:
156bgstz

BCPL[edit]

get "libhdr"

let uniques(strings, out) be
$(  let counts = vec 255
    for i=0 to 255 do counts!i := 0
    until !strings = 0
    $(  let string = !strings
        strings := strings + 1
        for i=1 to string%0 do
            counts!(string%i) := counts!(string%i) + 1
    $)
    out%0 := 0
    for i=0 to 255 if counts!i = 1
    $(  out%0 := out%0 + 1
        out%(out%0) := i
    $)
$)

let start() be
$(  let strings = vec 3 and out = vec 1+255/BYTESPERWORD

    strings!0 := "133252abcdeeffd"
    strings!1 := "a6789798st"
    strings!2 := "yxcdfgxcyz"
    strings!3 := 0
    
    uniques(strings, out)
    writef("%S*N", out)
$)
Output:
156bgstz

BQN[edit]

Uniq ← (⍷/˜1=/⁼∘⊐)∧∘∾
Output:
   Uniq "133252abcdeeffd"‿"a6789798st"‿"yxcdfgxcyz"
"156bgstz"

C[edit]

#include <stdio.h>
#include <string.h>

char *uniques(char *str[], char *buf) {
    static unsigned counts[256];
    unsigned i;
    char *s, *o = buf;
    memset(counts, 0, 256 * sizeof(unsigned));
    
    for (; *str; str++)
        for (s = *str; *s; s++)
            counts[(unsigned) *s]++;
    
    for (i=0; i<256; i++)
        if (counts[i] == 1)
            *o++ = (char) i;
    
    *o = '\0';
    return buf;
}

int main() {
    char buf[256];
    char *strings[] = {
        "133252abcdeeffd",
        "a6789798st",
        "yxcdfgxcyz",
        NULL
    };
    
    printf("%s\n", uniques(strings, buf));
    return 0;
}
Output:
156bgstz

C++[edit]

#include <iostream>
#include <map>

int main() {
    const char* strings[] = {"133252abcdeeffd", "a6789798st", "yxcdfgxcyz"};
    std::map<char, int> count;
    for (const char* str : strings) {
        for (; *str; ++str)
            ++count[*str];
    }
    for (const auto& p : count) {
        if (p.second == 1)
            std::cout << p.first;
    }
    std::cout << '\n';
}
Output:
156bgstz

Factor[edit]

Works with: Factor version 0.99 build 2074
USING: io sequences sets.extras sorting ;

{ "133252abcdeeffd" "a6789798st" "yxcdfgxcyz" }
concat non-repeating natural-sort print
Output:
156bgstz


FreeBASIC[edit]

Dim As Integer c(255), i, a
Dim As String s
Do
    Read s
    For i = 1 To Len(s)
        a = Asc(Mid(s,i,1))
        c(a) += 1
    Next i
Loop Until s = ""

For i = 1 To 255
    If c(i) = 1 Then s &= Chr(i)
Next i
Print s

Data "133252abcdeeffd", "a6789798st", "yxcdfgxcyz", ""
Sleep
Output:
156bgstz

FutureBasic[edit]

window 1, @"Unique characters"

void local fn DoIt
  CountedSetRef      set   = fn CountedSetWithCapacity(0)
  CFArrayRef         array = @[@"133252abcdeeffd",@"a6789798st",@"yxcdfgxcyz"]
  CFStringRef        string, chr
  long               index
  CFMutableArrayRef  mutArray = fn MutableArrayWithCapacity(0)
  
  for string in array
    for index = 0 to len(string) - 1
      CountedSetAddObject( set, mid(string,index,1) )
    next
  next
  
  for chr in set
    if ( fn CountedSetCountForObject( set, chr ) == 1 )
      MutableArrayAddObject( mutArray, chr )
    end if
  next
  
  MutableArraySortUsingSelector( mutArray, @"compare:" )
  
  print fn ArrayComponentsJoinedByString( mutArray, @"" )
end fn

fn DoIt

HandleEvents
Output:
156bgstz

Go[edit]

package main

import (
    "fmt"
    "sort"
)

func main() {
    strings := []string{"133252abcdeeffd", "a6789798st", "yxcdfgxcyz"}
    m := make(map[rune]int)
    for _, s := range strings {
        for _, c := range s {
            m[c]++
        }
    }
    var chars []rune
    for k, v := range m {
        if v == 1 {
            chars = append(chars, k)
        }
    }
    sort.Slice(chars, func(i, j int) bool { return chars[i] < chars[j] })
    fmt.Println(string(chars))
}
Output:
156bgstz

Haskell[edit]

import Data.List (group, sort)

uniques :: [String] -> String
uniques ks =
  [c | (c : cs) <- (group . sort . concat) ks, null cs]

main :: IO ()
main =
  putStrLn $
    uniques
      [ "133252abcdeeffd",
        "a6789798st",
        "yxcdfgxcyz"
      ]
Output:
156bgstz


Or folding the strings down to a hash of character frequencies:

import qualified Data.Map.Strict as M

--------- UNIQUE CHARACTERS FROM A LIST OF STRINGS -------

uniqueChars :: [String] -> String
uniqueChars =
  M.keys . M.filter (1 ==)
    . foldr (M.unionWith (+) . charCounts) M.empty

charCounts :: String -> M.Map Char Int
charCounts =
  foldr
    (flip (M.insertWith (+)) 1)
    M.empty

--------------------------- TEST -------------------------
main :: IO ()
main =
  putStrLn $
    uniqueChars
      [ "133252abcdeeffd",
        "a6789798st",
        "yxcdfgxcyz"
      ]
Output:
156bgstz

JavaScript[edit]

(() => {
    "use strict";

    // ---------------- UNIQUE CHARACTERS ----------------

    // uniques :: [String] -> [Char]
    const uniques = xs =>
        group(
            xs.flatMap(x => [...x])
            .sort()
        )
        .flatMap(
            x => 1 === x.length ? (
                x
            ) : []
        );

    // ---------------------- TEST -----------------------
    // main :: IO ()
    const main = () =>
        uniques([
            "133252abcdeeffd",
            "a6789798st",
            "yxcdfgxcyz"
        ]);


    // --------------------- GENERIC ---------------------

    // group :: Eq a => [a] -> [[a]]
    const group = xs => {
        // A list of lists, each containing only equal elements,
        // such that the concatenation of these lists is xs.
        const go = ys =>
            0 < ys.length ? (() => {
                const
                    h = ys[0],
                    i = ys.findIndex(y => h !== y);

                return i !== -1 ? (
                    [ys.slice(0, i)].concat(go(ys.slice(i)))
                ) : [ys];
            })() : [];

        return go(xs);
    };


    // MAIN ---
    return JSON.stringify(main());
})();
Output:
["1","5","6","b","g","s","t","z"]


Or, folding the strings (with Array.reduce) down to a hash of character frequencies:

(() => {
    "use strict";

    // uniqueChars :: [String] -> [Char]
    const uniqueChars = ws =>
        Object.entries(
            ws.reduce(
                (dict, w) => [...w].reduce(
                    (a, c) => Object.assign({}, a, {
                        [c]: 1 + (a[c] || 0)
                    }),
                    dict
                ), {}
            )
        )
        .flatMap(
            ([k, v]) => 1 === v ? (
                [k]
            ) : []
        );

    // ---------------------- TEST -----------------------
    const main = () =>
        uniqueChars([
            "133252abcdeeffd", "a6789798st", "yxcdfgxcyz"
        ]);


    return JSON.stringify(main());
})();
Output:
["1","5","6","b","s","t","g","z"]

J[edit]

The simple approach here is to merge the argument strings and find characters which occur exactly once in that intermediate result:

uniques=: ~.#~1=#/.~

In other words, ~. finds the distinct characters, #/.~ finds the corresponding counts of those characters, so 1=#/.~ is true for the characters which occur exactly once, and #~ filters the distinct characters based on those truth values.

Output:
   uniques;'133252abcdeeffd';'a6789798st';'yxcdfgxcyz'
156bgstz

Here, ; as a separator between quoted strings builds a list of the strings, and ; as a prefix of that list merges the contents of the strings of that list into a single string. We could just as easily have formed a single string, but that's not what the task asked for. (Since uniques is a verb (aka a "function"), it's not a list element in this context.)

jq[edit]

Works with: jq

Works with gojq, the Go implementation of jq

The following "bag-of-words" solution is quite efficient as it takes advantage of the fact that jq implements JSON objects as a hash.
# bag of words
def bow(stream): 
  reduce stream as $word ({}; .[($word|tostring)] += 1);

# Input: an array of strings
# Output: an array of the characters that appear just once
def in_one_just_once:
  bow( .[] | explode[] | [.] | implode) | with_entries(select(.value==1)) | keys;

The task

["133252abcdeeffd", "a6789798st", "yxcdfgxcyz"]
| in_one_just_once
Output:
["1","5","6","b","g","s","t","z"]

Julia[edit]

list = ["133252abcdeeffd", "a6789798st", "yxcdfgxcyz"]

function is_once_per_all_strings_in(a::Vector{String})
    charlist = collect(prod(a))
    counts = Dict(c => count(x -> c == x, charlist) for c in unique(charlist))
    return sort([p[1] for p in counts if p[2] == 1])
end

println(is_once_per_all_strings_in(list))
Output:

['1', '5', '6', 'b', 'g', 's', 't', 'z']

One might think that the method above suffers from too many passes through the text with one pass per count, but with a small text length the dictionary lookup takes more time. Compare times for a single pass version:

function uniquein(a)
    counts = Dict{Char, Int}()
    for c in prod(list)
        counts[c] = get!(counts, c, 0) + 1
    end
    return sort([c for (c, n) in counts if n == 1])
end

println(uniquein(list))

using BenchmarkTools
@btime is_once_per_all_strings_in(list)
@btime uniquein(list)
Output:

['1', '5', '6', 'b', 'g', 's', 't', 'z']

 1.740 μs (28 allocations: 3.08 KiB)
 3.763 μs (50 allocations: 3.25 KiB)

This can be rectified (see Phix entry) if we don't save the counts as we go but just exclude entries with duplicates:

function uniquein2(a)
    s = sort(collect(prod(list)))
    l = length(s)
    return [p[2] for p in enumerate(s) if (p[1] == 1 || p[2] != s[p[1] - 1]) && (p[1] == l || p[2] != s[p[1] + 1])]
end

println(uniquein2(list))

@btime uniquein2(list)
Output:

['1', '5', '6', 'b', 'g', 's', 't', 'z']

 1.010 μs (14 allocations: 1.05 KiB)


Lua[edit]

local strings = {"133252abcdeeffd",  "a6789798st",  "yxcdfgxcyz"}
unpack = unpack or table.unpack -- compatibility for all Lua versions

local map = {}
for i, str in ipairs (strings) do
	
	for i=1, string.len(str) do
		local char = string.sub(str,i,i)
		if map[char] == nil then
			map[char] = true
		else
			map[char] = false
		end
	end
	
end

local list = {}
for char, bool in pairs (map) do
	if bool then
		table.insert (list, char)
	end
end
table.sort (list)
print (unpack (list))
Output:
1	5	6	b	g	s	t	z

Mathematica/Wolfram Language[edit]

Select[Tally[Sort[Characters[StringJoin[{"133252abcdeeffd", "a6789798st", "yxcdfgxcyz"}]]]], Last /* EqualTo[1]][[All, 1]]
Output:
{"1", "5", "6", "b", "g", "s", "t", "z"}

Nim[edit]

One solution, but others are possible, for instance concatenating the strings and building the count table from it rather than merging several count tables. And to build the last sequence, we could have used something like sorted(toSeq(charCount.pairs).filterIt(it[1] == 1).mapIt(it[0])), which is a one liner but less readable and less efficient than our solution using “collect”.

import algorithm, sugar, tables

var charCount: CountTable[char]

for str in ["133252abcdeeffd", "a6789798st", "yxcdfgxcyz"]:
  charCount.merge str.toCountTable

let uniqueChars = collect(newSeq):
                    for ch, count in charCount.pairs:
                      if count == 1: ch

echo sorted(uniqueChars)
Output:
@['1', '5', '6', 'b', 'g', 's', 't', 'z']

Pascal[edit]

Works with: Extended Pascal
program uniqueCharacters(output);

type
	{ `integer…` prefix to facilitate sorting in documentation tools }
	integerNonNegative = 0..maxInt;
	{ all variables of this data type will be initialized to `1` }
	integerPositive = 1..maxInt value 1;
	
	charFrequency = array[char] of integerNonNegative;
	
	{ This “discriminates” the `string` _schema_ data type. }
	line = string(80);
	{ A schema data type definition looks sort of like a function: }
	lines(length: integerPositive) = array[1..length] of line;

{ `protected` in Extended Pascal means, you can’t modify this parameter. }
function createStatistics(protected sample: lines): charFrequency;
var
	i, n: integerPositive;
	{ The `value …` clause will initialize this variable to the given value. }
	statistics: charFrequency value [chr(0)..maxChar: 0];
begin
	{ You can inspect the discriminant of schema data type just like this: }
	for n := 1 to sample.length do
	begin
		{ The length of a `string(…)` though needs to be queried: }
		for i := 1 to length(sample[n]) do
		begin
			statistics[sample[n, i]] := statistics[sample[n, i]] + 1
		end
	end;
	
	{ There needs to be exactly one assignment to the result variable: }
	createStatistics := statistics
end;

{ === MAIN ============================================================= }
var
	c: char;
	sample: lines(3) value [1: '133252abcdeeffd';
		2: 'a6789798st'; 3: 'yxcdfgxcyz'];
	statistics: charFrequency;
begin
	statistics := createStatistics(sample);
	
	for c := chr(0) to maxChar do
	begin
		{
			The colon (`:`) specifies the display width of the parameter.
			While for `integer` and `real` values it means the _minimum_ width,
			for `string` and `char` values it is the _exact_ width.
		}
		write(c:ord(statistics[c] = 1))
	end;
	
	writeLn
end.
Output:
156bgstz

Perl[edit]

Translation of: Raku
# 20210506 Perl programming solution

use strict;
use warnings;
use utf8;
use Unicode::Collate 'sort';

my %seen;
binmode(STDOUT, ':encoding(utf8)');
map { s/(\X)/$seen{$1}++/egr } 
   "133252abcdeeffd", "a6789798st", "yxcdfgxcyz", "AАΑSäaoö٥🤔👨‍👩‍👧‍👧";
my $uca = Unicode::Collate->new();
print $uca->sort ( grep { $seen{$_} == 1 } keys %seen )
Output:
👨‍👩‍👧‍👧🤔15٥6AäbgoösStzΑА

Phix[edit]

function once(integer ch, i, string s)
    integer l = length(s)
    return (i=1 or ch!=s[i-1])
       and (i=l or ch!=s[i+1])
end function

sequence set = {"133252abcdeeffd","a6789798st","yxcdfgxcyz"},
         res = filter(sort(join(set,"")),once)
printf(1,"found %d unique characters: %s\n",{length(res),res})
Output:
found 8 unique characters: 156bgstz

PicoLisp[edit]

(de uni (Lst
   (let R NIL
      (mapc
         '((L)
            (mapc
               '((L) (accu 'R L 1)) L ) )
         (mapcar chop Lst) )
      (mapcar
         car
         (by
            car
            sort
            (filter '((L) (=1 (cdr L))) R) ) ) ) )
(println
   (uni
      (quote
         "133252abcdeeffd"
         "a6789798st"
         "yxcdfgxcyz" ) ) )
Output:
("1" "5" "6" "b" "g" "s" "t" "z")

PL/M[edit]

100H:
BDOS: PROCEDURE (FN, ARG); DECLARE FN BYTE, ARG ADDRESS; GO TO 5; END BDOS;
EXIT: PROCEDURE; CALL BDOS(0,0); END EXIT;
PRINT: PROCEDURE (S); DECLARE S ADDRESS; CALL BDOS(9,S); END PRINT;

/* FIND SORTED UNIQUE CHARACTERS IN ARRAY OF STRINGS */
UNIQUES: PROCEDURE (STRINGS, OUT);
    DECLARE (STRINGS, OUT) ADDRESS;
    DECLARE STRING BASED STRINGS ADDRESS; 
    DECLARE IPTR ADDRESS;
    DECLARE ICHAR BASED IPTR BYTE;
    DECLARE OCHAR BASED OUT BYTE;
    DECLARE COUNT (256) BYTE;
    DECLARE I ADDRESS;
    
    DO I=0 TO 255; COUNT(I)=0; END;
    I = 0;
    DO WHILE STRING(I) <> 0;
        IPTR = STRING(I);
        DO WHILE ICHAR <> '$';
            COUNT(ICHAR) = COUNT(ICHAR) + 1;
            IPTR = IPTR + 1;
        END;
        I = I + 1;
    END;
    
    DO I=0 TO 255;
        IF COUNT(I) = 1 THEN DO;
            OCHAR = I;
            OUT = OUT + 1;
        END;
    END;
    
    OCHAR = '$';
END UNIQUES;

/* INPUT ARRAY */
/* USING UPPERCASE LETTERS BECAUSE PLM-80 DOES NOT SUPPORT LOWERCASE */
DECLARE STRINGS (4) ADDRESS;
STRINGS(0) = .'133252ABCDEEFFD$';
STRINGS(1) = .'A6789798ST$';
STRINGS(2) = .'YXCDFGXCYZ$';
STRINGS(3) = 0;

DECLARE BUFFER (255) BYTE;
CALL UNIQUES(.STRINGS, .BUFFER);
CALL PRINT(.BUFFER);
CALL EXIT;
EOF
Output:
156BGSTZ

Python[edit]

'''Unique characters'''

from itertools import chain, groupby


# uniques :: [String] -> [Char]
def uniques(xs):
    '''Characters which occur only once
       across the given list of strings.
    '''
    return [
        h for h, (_, *tail) in
        groupby(sorted(chain(*xs)))
        if not tail
    ]


# ------------------------- TEST -------------------------
# main :: IO ()
def main():
    '''Characters occurring only once
       across a list of 3 given strings.
    '''
    print(
        uniques([
            "133252abcdeeffd",
            "a6789798st",
            "yxcdfgxcyz"
        ])
    )


# MAIN ---
if __name__ == '__main__':
    main()
Output:
['1', '5', '6', 'b', 'g', 's', 't', 'z']

Or reducing the given strings down to a hash of character frequencies:

'''Unique characters'''

from functools import reduce


# uniqueChars :: [String] -> [Char]
def uniqueChars(ws):
    '''Characters which occur only once
        across the given list of strings.
    '''
    def addedWord(dct, w):
        return reduce(updatedCharCount, w, dct)

    def updatedCharCount(a, c):
        return dict(
            a, **{
                c: 1 + a[c] if c in a else 1
            }
        )

    return sorted([
        k for k, v in reduce(addedWord, ws, {}).items()
        if 1 == v
    ])


# ------------------------- TEST -------------------------
# main :: IO ()
def main():
    '''Test'''
    print(
        uniqueChars([
            "133252abcdeeffd",
            "a6789798st",
            "yxcdfgxcyz"
        ])
    )


# MAIN ---
if __name__ == '__main__':
    main()
Output:
['1', '5', '6', 'b', 'g', 's', 't', 'z']

Raku[edit]

One has to wonder where the digits 0 through 9 come in the alphabet... 🤔 For that matter, What alphabet should they be in order of? Most of these entries seem to presuppose ASCII order but that isn't specified anywhere. What to do with characters outside of ASCII (or Latin-1)? Unicode ordinal order? Or maybe DUCET Unicode collation order? It's all very vague.

my @list = <133252abcdeeffd a6789798st yxcdfgxcyz>;

for @list, (@list, 'AАΑSäaoö٥🤔👨‍👩‍👧‍👧') {
    say "$_\nSemi-bogus \"Unicode natural sort\" order: ",
    .map( *.comb ).Bag.grep( *.value == 1 )».key.sort( { .unival, .NFKD[0], .fc } ).join,
    "\n        (DUCET) Unicode collation order: ",
    .map( *.comb ).Bag.grep( *.value == 1 )».key.collate.join, "\n";
}
Output:
133252abcdeeffd a6789798st yxcdfgxcyz
Semi-bogus "Unicode natural sort" order: 156bgstz
        (DUCET) Unicode collation order: 156bgstz

133252abcdeeffd a6789798st yxcdfgxcyz AАΑSäaoö٥🤔👨‍👩‍👧‍👧
Semi-bogus "Unicode natural sort" order: 15٥6ASäbgoöstzΑА👨‍👩‍👧‍👧🤔
        (DUCET) Unicode collation order: 👨‍👩‍👧‍👧🤔ä15٥6AbögosStzΑА

REXX[edit]

This REXX program doesn't assume ASCII (or any other) order.   This example was run on an ASCII machine.

If this REXX program is run on an  ASCII  machine,   it will use the   ASCII   order of characters,   in this case,
decimal digits,   uppercase Latin letters,   and then lowercase Latin letters,   with other characters interspersed.

On an  EBCDIC  machine,   the order would be lowercase Latin letters,   uppercase Latin letters,   and then the
decimal digits,   with other characters interspersed.

On an  EBCDIC  machine,   the lowercase letters and the uppercase letters   aren't   contiguous.

/*REXX pgm finds and shows characters that are unique to only one string  and once only.*/
parse arg $                                      /*obtain optional arguments from the CL*/
if $='' | $=","  then $= '133252abcdeeffd'  "a6789798st"  'yxcdfgxcyz'   /*use defaults.*/
if $=''  then do;   say "***error*** no lists were specified.";   exit 13;   end
@=                                               /*will be a list of all unique chars.  */

    do j=0  for 256;     x= d2c(j)               /*process all the possible characters. */
                         if x==' '  then iterate /*ignore blanks which are a delimiter. */
    _= pos(x, $);        if _==0    then iterate /*character not found,  then skip it.  */
    _= pos(x, $, _+1);   if _ >0    then iterate /*Character is a duplicate?  Skip it.  */
    @= @ x
    end   /*j*/                                  /*stick a fork in it,  we're all done. */

@@= space(@, 0);         L= length(@@)           /*elided superfluous blanks; get length*/
if @@==''  then @= " (none)"                     /*if none were found, pretty up message*/
if L==0    then L= "no"                          /*do the same thing for the # of chars.*/
say 'unique characters are: '   @                /*display the unique characters found. */
say
say 'Found '   L   " unique characters."         /*display the # of unique chars found. */
output   when using the default inputs:
unique characters are:   1 5 6 b g s t z

Found  8  unique characters.

Ring[edit]

see "working..." + nl
see "Unique characters are:" + nl
row = 0
str = ""
cList = []
uniqueChars = ["133252abcdeeffd", "a6789798st","yxcdfgxcyz"]
for n = 1 to len(uniqueChars)
    str = str + uniqueChars[n]
next
for n = 1 to len(str)
    ind = count(str,str[n])
    if ind = 1
       row = row + 1
       add(cList,str[n])
    ok
next
cList = sort(cList)
for n = 1 to len(cList)
    see "" + cList[n] + " "
next
see nl

see "Found " + row + " unique characters" + nl
see "done..." + nl

func count(cString,dString)
     sum = 0
     while substr(cString,dString) > 0
           sum++
           cString = substr(cString,substr(cString,dString)+len(string(sum)))
     end
     return sum
Output:
working...
Unique characters are:
1 5 6 b g s t z 
Found 8 unique characters
done...

Transd[edit]

#lang transd

MainModule: {
    v: ["133252abcdeeffd",  "a6789798st",  "yxcdfgxcyz"],

    _start: (λ
        (for p in (group-by (split (join v "") "")) do
            (if (== (size (snd p)) 1) (textout (fst p))))
    )
}
Output:
156bgstz

Vlang[edit]

fn main() {
    strings := ["133252abcdeeffd", "a6789798st", "yxcdfgxcyz"]
    mut m := map[rune]int{}
    for s in strings {
        for c in s {
            m[c]++
        }
    }
    mut chars := []rune{}
    for k, v in m {
        if v == 1 {
            chars << k
        }
    }
chars.sort_with_compare(fn(i &rune, j &rune) int {
if *i<*j {
return -1
}
if *i>*j {
return 1
}
return 0
})
  println(chars.string())
}
Output:
156bgstz

Wren[edit]

Library: Wren-seq
Library: Wren-sort
import "/seq" for Lst
import "/sort" for Sort

var strings = ["133252abcdeeffd", "a6789798st","yxcdfgxcyz"]
var totalChars = strings.reduce { |acc, str| acc + str }.toList
var uniqueChars = Lst.individuals(totalChars).where { |l| l[1] == 1 }.map { |l| l[0] }.toList
Sort.insertion(uniqueChars)
System.print("Found %(uniqueChars.count) unique character(s), namely:")
System.print(uniqueChars.join(" "))
Output:
Found 8 unique character(s), namely:
1 5 6 b g s t z


Yabasic[edit]

Translation of: FreeBASIC
dim c(255)
data "133252abcdeeffd", "a6789798st", "yxcdfgxcyz", ""
repeat
    read s$
    for i = 1 to len(s$)
        a = asc(mid$(s$,i,1))
        c(a) = c(a) + 1
    next i
until s$ = ""

for i = 1 to 255
    if c(i) = 1 then s$ = s$ + chr$(i) : fi
next i
print s$
end
Output:
Igual que la entrada de FreeBASIC.


XPL0[edit]

int     List, I, N, C;
char    Tbl(128), Str;
string  0;
[List:= ["133252abcdeeffd", "a6789798st","yxcdfgxcyz"];
for I:= 0 to 127 do Tbl(I):= 0;
for N:= 0 to 2 do
        [Str:= List(N);
        I:= 0;
        loop    [C:= Str(I);
                if C = 0 then quit;
                I:= I+1;
                Tbl(C):= Tbl(C)+1;
                ];
        ];
for I:= 0 to 127 do
        if Tbl(I) = 1 then ChOut(0, I);
]
Output:
156bgstz