| Bytes | Lang | Time | Link |
|---|---|---|---|
| 095 | Forth gforth | 250512T204736Z | reffu |
| 030 | Raku Perl 6 rakudo | 250416T173420Z | xrs |
| 063 | AWK | 241203T145100Z | xrs |
| 096 | JavaScript Node.js | 230707T094702Z | Fhuvi |
| 164 | Go | 241203T153548Z | bigyihsu |
| 016 | Uiua SBCS | 240702T081936Z | chunes |
| 180 | Racket | 230707T133031Z | Ed The & |
| 004 | Thunno 2 | 230525T174116Z | The Thon |
| 051 | Julia 1.0 | 230324T222622Z | Ashlin H |
| 8517 | Nibbles | 230516T222110Z | DLosc |
| 071 | Haskell | 230316T192343Z | DLosc |
| 056 | R | 230510T110754Z | Kirill L |
| nan | Scala | 230419T014124Z | 138 Aspe |
| 047 | Clojure | 230509T124418Z | Kirill L |
| 134 | Pascal FPC | 230306T145121Z | roblogic |
| 077 | Lua | 230409T212827Z | bluswimm |
| 029 | Pyth | 230327T170956Z | princesa |
| nan | And any other shell that supports ${var | 230316T124648Z | bxm |
| 023 | Pip | 230306T162722Z | Baby_Boy |
| 166 | Java 16+ JDK | 230316T144732Z | Fhuvi |
| 087 | C gcc | 230304T212851Z | ErikF |
| 021 | Raku | 230310T170520Z | Sean |
| 062 | PowerShell Core | 230307T004246Z | Julian |
| 082 | Excel | 230306T161051Z | Engineer |
| 087 | Excel ms365 | 230307T121806Z | JvdV |
| 029 | Retina 0.8.2 | 230303T235604Z | Neil |
| 019 | CJam | 230306T185013Z | emirps |
| 358 | Pascal | 230306T000013Z | Kai Burg |
| 110 | Common Lisp | 230306T140249Z | coredump |
| 013 | Japt v2.0a0 | 230304T165327Z | noodle p |
| 048 | Ruby | 230306T123034Z | G B |
| 060 | Python | 230304T113207Z | The Thon |
| 008 | Jelly | 230305T052630Z | Unrelate |
| 037 | Perl 5 n | 230304T223607Z | Kjetil S |
| 135 | Java JDK | 230303T214143Z | Unmitiga |
| 006 | 05AB1E | 230304T081622Z | The Thon |
| 032 | J | 230304T120240Z | ovs |
| 068 | JavaScript | 230304T042556Z | EzioMerc |
| 082 | JavaScript ES6 | 230303T234356Z | Arnauld |
| nan | 230304T081144Z | The Thon | |
| 066 | jq | 230304T044020Z | GammaFun |
| 035 | J9.4 | 230304T001140Z | south |
| 015 | Charcoal | 230304T000101Z | Neil |
| 034 | Arturo | 230303T222340Z | chunes |
| 034 | Mathematica Wolfram Language | 230303T205211Z | dirvine |
| 004 | Vyxal | 230303T205940Z | math sca |
Forth (gforth), 95 bytes
: f 'Z 'A do 2dup 0 -rot bounds do i c@ toupper j = - loop ?dup if cr i emit ." :". then loop ;
Explanation
Goes through every letter A-Z and counts the occurrences of that letter in the string, then outputs non-zero values alphabetically in the format A:1, B:2, etc.. separated by newlines.
Code Explanation
: f \ Start word definition
'Z 'A do \ Start a counted loop from 65 - 90
2dup 0 -rot \ copy string and put 0 to use as the count behind the string on the stack
bounds do \ start a counted loop through the string
i c@ toupper \ get the current character in the string and convert to uppercase
j = - \ compare to current letter and subtract result (-1 = true in forth)
loop \ end inner counted loop
?dup if \ duplicate result if > 0, start conditional if so
cr i emit \ output a newline and the current letter
." :". \ output a colon (:) followed by the result
then \ end conditional
loop \ end outer loop
; \ end word definition
JavaScript (Node.js), 96 116 bytes
-20 bytes using golfing knowledge gained through these last two years
This solution focuses on using ASCII codes to filter non-letter characters, and the use of Object.entries(...) to produce the result
s=>[...(B=Buffer)(s)].map(g=c=>(c=c|32)>96&c<123?g[c=B([c])]=-~g[c]:0)&&Object.entries(g).sort()
Node.js' Buffer helps with converting string to ASCII values both ways, and we store it as the B function.
g is declared as a function but will always be used as an object (it's shorter this way).
c|32 puts letters in lowercase, and we check if it's in the bounds a-z (in ASCII) to determine if it's a letter.
Then we add each letter as properties to the g object if they don't already exist, and increment the corresponding values by one (by using -~g[c] which is equivalent to a +1 with the added benefit of considering undefined as a 0 when each property is declared)
At the end, we output the array of properties of the g object sorted alphabetically.
Go, 164 bytes
import(."strings";u"unicode")
type D=map[rune]int
func f(s string)D{d:=make(D)
for _,r:=range ToLower(s){if u.IsLetter(r){if _,o:=d[r];!o{d[r]=0};d[r]++}}
return d}
Returns a map of runes to ints.
Uiua SBCS, 16 bytes
⊏⊸⍏⊕{⊃⊢⧻}⊸⊛⌵▽⌵⊸±
Explanation
⊏⊸⍏⊕{⊃⊢⧻}⊸⊛⌵▽⌵⊸±
▽⌵⊸± # remove non-letters
⌵ # uppercase
⊸⊛ # assign unique index to each unique value
⊕{⊃⊢⧻} # boxed group by letter count
⊏⊸⍏ # sort
Racket -- 180 bytes
#!racket
(let([s hash-set])(foldl(λ(c a)(if(hash-has-key? a c)(s a c(+ 1(hash-ref a c)))(s a c 1)))#hasheq()(map char-downcase(filter char-alphabetic?(string->list(read-line))))))
Explanation
This program returns a hash table of key-value pairs. The keys in the hash table are the characters and the values are the number of occurences in the string.
Characters are represented by a #\ prefix. So the string "Apple" can be thought of as:
(display "Apple")
(display (string #\A #\p #\p #\l #\e))
The pseudo-code of my answer (algorithm?) is:
- Read user input.
- Filter only the alphabetic characters.
- Convert all left-over characters into lowercase.
- Iterate left-to-right using a hash table.
- If the hash table has the character as a key, increment the key's value.
- Otherwise, add a new key representing the character and set the value to 1.
- Return the resulting hash table.
#lang racket
(foldl (lambda (char acc)
(if (hash-has-key? acc char)
(hash-set acc char (add1 (hash-ref acc char)))
(hash-set acc char 1)))
#hasheq()
(map char-downcase
(filter char-alphabetic?
(string->list (read-line)))))
Thunno 2, 4 bytes
ỊLƈṠ
Explanation
ỊLƈṠ # Implicit input
Ị # Remove non-alphabets
L # Lowercase
ƈ # Counts
Ṡ # Sort
# Implicit output
Julia 1.0, 51 bytes
~s=(x='A':'Z';y=@.sum(in(x=>x+32),s);(x.=>y)[y.>0])
- Thanks to MarcMush for the following:
- replace
sum(==(x),uppercase(s))withsum(in(x=>x+32),s) - replace Tuples with Pairs (e.g.,
(a,b)vs.a=>b).
- replace
54 byte solution
~s=(y='A':'Z'.|>x->x=>sum(in(x=>x+32),s))[@.last(y)>0]
55 byte solution
~s=[x=>y for x='A':'Z' for y=sum(in(x=>x+32),s) if y>0]
- Thanks to Kirill L. for suggesting comprehensions and generator expressions!
Nibbles, 8.5 bytes (17 nibbles)
=~|.@`($\$a$~/$$,
I could save 0.5 bytes by returning (count, letter) pairs instead of (letter, count).
Explanation
There's probably a better way to do this.
=~|.@`($\$a$~/$$,$
@ First line of stdin
. Map to each character:
`($ Lowercase
| Filter by this function:
\$a Is the character a letter?
=~ Group and sort by:
$ Each letter
We now have the lowercase letters from the input, grouped
by letter (giving a list of strings) and sorted
For each string of identical letters (implicit):
~ Return a tuple of:
/$$ The first character of the string
,$ The length of the string
The / ... $ idiom used in /$$ means "right-fold on the identity function," which yields the leftmost element of a list.
Haskell, 75 71 bytes
-4 bytes thanks to Lynn
f s=[(l,n)|(u,l)<-zip['A'..'Z']['a'..],n<-[sum[1|c<-s,l==c||u==c]],n>0]
Explanation
List comprehensions seem to be shorter than map and filter.
f s=
Define our function, f, which takes a string s.
zip['A'..'Z']['a'..]
Create a list of (uppercase letter, lowercase letter) tuples.
(u,l)<-___
For each uppercase letter u and lowercase letter l in that list...
,n<-sum[1|c<-s,l==c||u==c]
Let n be the number of characters c in s for which c equals one of l and u...
[(l,n)|____,n>0]
Return a list of tuples of l and the corresponding n where n is greater than 0.
A solution using toLower from Data.Char comes in at 76 bytes:
import Data.Char
f s=[(l,n)|l<-['a'..'z'],n<-[sum[1|c<-s,l==toLower c]],n>0]
Scala, 112 108 bytes
Saved 4 bytes thanks to the comment of @Kirill L.
Golfed version. Try it online!
def f(s:String)=s.toUpperCase.filter(c=>c>='A'&&c<='Z').groupBy(x=>x).mapValues(_.length).toSeq.sortBy(_._1)
Ungolfed version. Try it online!
import scala.collection.mutable
object Main {
def main(args: Array[String]): Unit = {
println(frequency("hello world"))
println(frequency("The quick brown fox jumps over the lazy dog"))
}
def frequency(s: String): Seq[(Char, Int)] = {
val uppercaseChars = s.toUpperCase
val counts = mutable.Map.empty[Char, Int].withDefaultValue(0)
uppercaseChars.foreach(c => {
if (c >= 'A' && c <= 'Z') {
counts(c) += 1
}
})
counts.toSeq.sortBy(_._1)
}
}
Clojure, 47 bytes
#(frequencies(re-seq #"[a-z]"(.toLowerCase %)))
Returns a Map as allowed by OP in the comments. To obtain a sorted colllection this should be additionally wrapped with sort.
Pascal (FPC), 185 ... 134 bytes
var c,d:char;s:string;i:word;begin;read(s);for c:=^to^do begin;i:=0;for d in upcase(s)do i+=ord(c=d);if i>0then writeln(c,i)end;end.
Try it online! (134b, courtesy of @michael-tkach)
Iterates over the alphabet and the input string s, comparing chars d and c. The for <char> in <string> syntax was handy. Implemented some of the Pascal golfing tips.
Lua, 77 bytes
s=...for i=65,90 do c=s.char(i)_,n=s:upper():gsub(c,c)f=n>0 and print(c,n)end
s=... -- Take argument as input
for i=65,90 do
c=s.char(i) -- Get the char represented by charcode i
_,n=s:upper():gsub(c,c) -- n stores the number of occurrences of char c
f=n>0 and print(c,n) -- if n>0, print char/count pair
end
Pyth, 29 bytes
FNS{rzZI&<"`"N <N"{"(N /rzZN)
Explanation
Used a for loop F to iterate over the letters which are valid by
I&<"`"N <N"{" which checks if a letter in the string is within backtick and open curly bracket.
rzZ - converts the input string in lower i.e. input().lower() in Python.
S{ - used to sort the string.
And any other shell that supports ${var,,} to downcase.
Bash, 35 bytes
grep -o [a-z]<<<${*,,}|sort|uniq -c
Pip, 21 23 bytes
YSSLCaFiUQy@XL;Pi.(iNy)
How?
YSSLCaFiUQy@XL;Pi.(iNy) : One arg; str
a : First input
LC : To lowercase
SS : Sort
Y : Yank
Fi : For i in
UQy : Unique values of y
@XL : Only lowercase alphabet characters
P : Print
i : Letter
. : Concatenated with
iN : Letter in
y : Input
Java 16+ (JDK), 166 bytes
Tried to use only inline stream as a different approach, but couldn't beat the best Java answer.
s->s.toLowerCase().chars().distinct().filter(e->e>96&e<123).sorted().mapToObj(e->new String[]{(char)e+"",s.toLowerCase().chars().filter(o->o==e).count()+""}).toList()
JDoodle (because TIO currently only supports Java up to v12)
C (gcc), 97 94 87 bytes
- -3 thanks to Unrelated String
- -7 thanks to c--
Uses uppercase as the challenge only states case-insensitive.
f(char*s){for(int a[96]={},i=64;i<90;)*s?a[*s++&95]++:a[++i]&&printf("%c:%d ",i,a[i]);}
Ungolfed:
f(char*s){
for(int a[96]={},i=64;
i<90;) // for all characters in string, then from 'A' to 'Z':
*s? // done processing string?
a[*s++&95]++: // no, uppercase letter and increment count
a[++i]&&printf("%c:%d ",i,a[i]); // yes, print counts
}
Raku, 21 bytes
*.lc.comb(/<:L>/).Bag
.lcconverts the input string to lowercase..comb(/<:L>/)produces a list of all non-overlapping matches for the regex<:L>(a letter) in that string..Bagconverts that list to a Bag, a set with multiplicity, which is also an associative mapping and thus allowed as a return value per comments added to the challenge.
PowerShell Core, 62 bytes
$args-match'[a-z]'|%{"$_"|% *l*r}|group|%{,($_.Name,$_.Count)}
$args-match'[a-z]' # For each character argument, exclude non letters characters
|%{"$_"|% *l*r} # Converts to lowercase
|group # Groups them
|%{,($_.Name,$_.Count)} # build pairs of letters and counts
Excel, 85 82 bytes
Saved 3 bytes thanks to JvdV
=LET(c,CHAR(ROW(65:90)),q,LEN(A1)-LEN(SUBSTITUTE(UPPER(A1),c,)),FILTER(c&"="&q,q))
The LET() function allows you to assign variables to values and reference them by name later. Every term is a pair except the last which is the output.
c,CHAR(ROW(65:90))creates an array of the stringsAthroughZ.q,LEN(a)-LEN(SUBSTITUTE(UPPER(a),c,))creates an array of how many times each letter appears by measuring the length of the input vs. the length if we replace that letter with nothing.SUBSTITUTE()is case-sensitive so we transform the input to uppercase.FILTER(c&"="&q,q)combines the array of letters with the count of each character and then filter for just those where the count (q) was truthy (anything not zero).
Excel (ms365), 87 bytes
Formula in B1:
=LET(x,CHAR(ROW(97:122)),y,LEN(A1)-LEN(SUBSTITUTE(LOWER(A1),x,)),FILTER(HSTACK(x,y),y))
And 83 bytes if you don't want tupples, but a concatenation would do:
=LET(x,CHAR(ROW(97:122)),y,LEN(A1)-LEN(SUBSTITUTE(LOWER(A1),x,)),FILTER(x&"-"&y,y))
Retina 0.8.2, 38 29 bytes
T`Llp`ll_
O`.
(.)\1*
$1, $.&¶
Try it online! Output includes trailing newlines. Saved 9 bytes thank to @FryAmTheEggman. Explanation:
T`Llp`ll_
Convert letters to lower case and delete all other printable ASCII.
O`.
Sort the characters.
(.)\1*
$1, $.&¶
Output the count of each letter.
Previous 38-byte version worked with any input, not just printable ASCII:
T`L`l
O`.
M!`([a-z])\1*
(.)\1*
$1, $.&
Try it online! Explanation:
T`L`l
Lowercase the input string.
O`.
Sort the characters.
M!`([a-z])\1*
Extract the runs of identical letters.
(.)\1*
$1, $.&
Output the count of each letter.
31 30 bytes in Retina 1:
.
$L
O`.
L$`([a-z])\1*
$1, $.&
Try it online! Explanation:
.
$L
Lowercase the input.
O`.
Sort the characters.
L$`([a-z])\1*
For each run of identical letters...
$1, $.&
... output the count of each letter.
CJam, 19 bytes
el_'{,97>--$e`{W%}%
Returns strings in the form of {char}{count}
Explanation
el_'{,97>--$e`{W%}% # a function taking an input string
el_ -- # remove all elements of the input which do not occur in
'{,97> # the character range lowercase a-z
$ # sort
e` # run-length encode (1h1e2l as an example)
{W%}% # to meet the output specification of {char}{count} and not {count}{char}
# we reverse each of the arrays in this sorted array
Pascal, 590 358 bytes
This full program requires a processor supporting features of ISO standard 10206 “Extended Pascal”.
Furthermore,
- the implementation-dependent behavior of the selection order in a
for … inset member iteration statement must be ascending, - the implementation-defined default
TotalWidthforinteger-typewritearguments must be sufficiently large in order to leave some space between a character and the number, and - the implementation-defined set of available characters contains both upper and lowercase characters.
The GPC (GNU Pascal Compiler) fulfills these conditions on an x86‑64 Linux system if compiling with the ‑‑extended-pascal option.
program p(input,output);var c:char;f:array[char]of integer value[otherwise 0];begin while not EOF do begin read(c);c:=(c+'ABCDEFGHIJKLMNOPQRSTUVWXYZ')[index('abcdefghijklmnopqrstuvwxyz',c)+1];f[c]:=f[c]+1 end;for c in['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']do if 0<f[c]then writeLn(c,f[c])end.
Ungolfed:
program letterFrequency(input, output);
var
c: char;
letterFrequency: array[char] of integer value [otherwise 0];
begin
while not EOF(input) do
begin
{ `read(input, c)` is equivalent to `c := input^; get(input)`. }
read(input, c);
{ There is no built-in “change case” function as Pascal does not
guarantee the coexistence of lower and uppercase letters. The EP
function `index(source, pattern)` returns the index of the first
occurence of `pattern` in `source`. If `pattern` is not found,
zero is returned. Remember, `string` indices are 1-based, thus
add `1` to select the prepended value of `c`. }
c := (c + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
[index('abcdefghijklmnopqrstuvwxyz', c) + 1];
letterFrequency[c] := letterFrequency[c] + 1;
end;
for c in ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] do
begin
if letterFrequency[c] > 0 then
begin
writeLn(c, letterFrequency[c]);
end;
end;
end.
See also FreePascal implementation.
Common Lisp, 110 bytes
(lambda(s)(reduce(lambda(s c &aux(e(assoc c s)))(if e(prog1 s(incf(cdr e)))(acons c 1 s)))s :initial-value()))
The ungolfed version is:
(defun frequencies (string)
(reduce (lambda (map char)
(let ((entry (assoc char map)))
(if entry
(prog1 map
(incf (cdr entry)))
(acons char 1 map))))
string
:initial-value nil))
For example:
> (frequencies "abcbad")
((#\d . 1) (#\c . 1) (#\b . 2) (#\a . 2))
Japt v2.0a0, 15 13 bytes
v r\L ü ËÎ+DÊ
-2 by Shaggy
Outputs a list of “ab” strings where a is the character and b is the number of occurrences
Japt, 7 bytes
ü ËÎ+DÊ
Doesn’t remove non-letters
Made on My iPhone
ü ËÎ+DÊ # input string
ü # list of same characters grouped together
Ë # map each string of same char to:
Î # the character
+DÊ # append length of group
Python, 60 bytes
lambda s:{c:s.lower().count(c)for c in s.lower()if'`'<c<'{'}
Returns a dictionary. Suggested by @JonathanAllan.
Python, 70 bytes
lambda s:sorted((c,s.lower().count(c))for c in{*s.lower()}if'`'<c<'{')
Returns a sorted list of tuples.
Python, 75 bytes
lambda s:sorted({*zip(x:=[*filter(str.isalpha,s.lower())],map(x.count,x))})
Returns a sorted list of tuples.
Commented
lambda s: sorted( # Define an anonymous function which takes a string, s,
# and returns a sorted version of the following:
(c, s.lower().count(c)) # a tuple of the character, c, and its count
# in the lowercase version of s
for c in {*s.lower()} # for each character, c, in the lowercase version of s uniquified
if '`' < c < '{' ) # but only keep it if it is between the characters
# '`' (right before 'a') and '{' (right after 'z')
# i.e., it's alphabetic
lambda s: sorted( # Define an anonymous function which takes a string, s,
# and returns a sorted version of the following:
{*zip( # a uniquified version of two iterables zipped together:
x := [*filter( # filter by
str.isalpha, # is the character in the alphabet
s.lower() # apply to a lowercase version of s
)], # and store in x
map(x.count, x) )}) # the counts of each character in x
Jelly, 8 bytes
ŒlḟŒuṢŒr
At least slightly less boring than if I'd used Øa. Feels ever so vaguely beatable, still...
Œl The lowercased input,
ḟŒu with elements of the uppercased input filtered out.
ṢŒr Sort and run-length encode.
Java (JDK), 151 142 135 bytes
s->{var m=new java.util.TreeMap();for(var c:s.replaceAll("[^a-zA-Z]","").toCharArray())m.merge(c|=32,1,(a,b)->(int)a+(int)b);return m;}
Saved 15 bytes thanks to Kevin Cruijssen.
05AB1E, 9 6 bytes
álТøê
Input as a list of characters.
Alternative 6 bytes answer with input as a string: (suggested by @KevinCruijssen)
ál{Åγø
-3 thanks to @KevinCruijssen
Explanation
á # Filter for alphabetic characters only
lÐ # Convert to lowercase and triplicate
¢ # Count the occurences of each character
øê # Zip, sort, and uniquify
á # Filter for alphabetic characters only
l{ # Convert to lowercase and sort
Åγ # Run-length encode the string
ø # Zip
J, 32 bytes
(u:97+i.26)(e.#[;"+1#.=/)tolower
u:97+i.26 A character vector of all lowercase letters.
tolower Input converted to lowercase.
(u:97+i.26)(...)tolower Apply inner function with lowercase alphabet as left argument and lowercased string as right argument.
=/ Equality table.
1#. Sum each row. This gives a vector of length 26 with the count of each letter in the input.
[;"+ Pair up each letter with its count.
e.# Only keep the pairs where the letter is an element of the input.
JavaScript, 68 bytes
s=>s.toLowerCase(m={}).match(/[a-z]/g).sort().map(l=>m[l]=-~m[l])&&m
Try it:
f=s=>s.toLowerCase(m={}).match(/[a-z]/g).sort().map(l=>m[l]=-~m[l])&&m
;[
'hello world',
'The tuick brown fox jumps over the lazy dog'
].forEach(s=>console.log(f(s)))
UPD 73 -> 68
Thanks to Shaggy for the tips (one, two) to reduce bytes count
JavaScript (ES6), 82 bytes
Probably takes a list of pairs a bit too literally... ¯\_(ツ)_/¯
s=>[...s.toLowerCase()].sort().join``.match(/([a-z])\1*/g).map(s=>[s[0],s.length])
Thunno, \$18\log_{256}(96)\approx\$ 14.82 bytes
UgAzsAqkztcsZZZUz:
Explanation
U # Lowercase
gAzsAqk # Alphabetic characters only
zt # Triplicate
cs # Counts of each
ZZ # Zip
ZU # Uniquify
z: # Sort
jq, 66 bytes
ascii_downcase|reduce match("[a-z]";"g").string as $c({};.[$c]+=1)
reduce is just too good.
J9.4, 35 bytes
'\pL'(~.,:<@#/.~)@/:~@rxall tolower
J903, 37 bytes
'[a-z]'(~.,:<@#/.~)@/:~@rxall tolower
Explanation is the same, but the PCRE engine was updated which makes the regex 2 bytes shorter.
'\pL'(~.,:<@#/.~)@/:~@rxall tolower
tolower NB. lowercase the input
'\pL' rxall NB. match every letter
/:~@ NB. then sort
( )@ NB. then invoke a monadic fork
#/.~ NB. count the occurrences of each letter
<@ NB. then box each result
~. NB. uniquify the matches
,: NB. stack matches on top of counts
Charcoal, 15 bytes
⭆¹ΦEβ⟦λ№↧θλ⟧§λ¹
Try it online! Link is to verbose version of code. Explanation:
β Predefined variable lowercase alphabet
E Map over letters
λ Current letter
⟦ ⟧ Make into tuple with
№ Count of
λ Current letter in
θ Input string
↧ Lowercased
Φ Filtered where
§λ¹ Count was nonzero
⭆¹ Pretty-print
Mathematica (Wolfram Language), 34 bytes
KeySort@*LetterCounts@*ToLowerCase
TIO.run is in the comments below. Edited to save 4 bytes thanks to a suggestion from ZaMoC.
Vyxal, 4 bytes
ǍɽĊs
How it works:
ǍɽĊs
Ǎɽ Remove non-alphabetical chars and lowercase
Ċ Count of each element
s Sorted

