Suchen Sie das erste Wort, das mit jedem Buchstaben beginnt

25

Suchen Sie bei gegebener Zeichenfolge das erste Wort, das mit jedem Buchstaben beginnt (Groß- / Kleinschreibung wird nicht berücksichtigt).

Probe

Verwendung Ferulas flourish in gorgeous gardens.als Eingabe:

"Ferulas flourish in gorgeous gardens."
 ^^^^^^^          ^^ ^^^^^^^^
 |                |  |
 |                |  --> is the first word starting with `g`
 |                --> is the first word starting with `i`
 --> is the first word starting with `f`

Dann sollte die Ausgabe für dieses Beispiel die übereinstimmenden Wörter sein, die durch ein einzelnes Leerzeichen verbunden sind:

"Ferulas in gorgeous"

Herausforderung

Sowohl Eingabe als auch Ausgabe müssen eine Zeichenfolgendarstellung sein oder die nächstgelegene Alternative in Ihrer Sprache.

Programm oder Funktion erlaubt.

Sie können ein Wort mindestens eine des Seins betrachten: lowercase or uppercase letters, digits, underscore.

Dies ist , die kürzeste Antwort in Bytes gewinnt.

Weitere Beispiele:

input: "Take all first words for each letter... this is a test"
output: "Take all first words each letter is"

input: "Look ^_^ .... There are 3 little dogs :)"
output: "Look _ There are 3 dogs"

input: "...maybe some day 1 plus 2 plus 20 could result in 3"
output: "maybe some day 1 plus 2 could result in 3"
entfernt
quelle
Sind nachgestellte / startende Leerzeichen erlaubt? <s> Kann ich davon ausgehen, dass Wörter in der ursprünglichen Zeichenfolge durch ein Leerzeichen getrennt sind? </ s>
Qwertiy
Ich habe es aus den Beispielen verstanden, daher steht <s> </ s> im Kommentar. Was ist mit dem Zuschneiden von Leerzeichen?
Qwertiy

Antworten:

17

Retina , 28 Bytes:

M! I` \ b (\ w) (? <! \ B \ 1. +) \ W *
¶
 
  • M! - Ordnen Sie jede Arbeit zu und drucken Sie alle durch Zeilenumbrüche getrennten Wörter.
  • i - Fall ignorieren.
  • \b(\w) - Erfassen Sie den ersten Buchstaben jedes Wortes
  • (?<!\b\1.+)- Überprüfen Sie nach dem Abgleichen des Buchstabens, ob es kein vorheriges Wort gibt, das mit demselben Buchstaben beginnt. \1.+Stellt mindestens zwei Zeichen sicher, so überspringen wir das aktuelle Wort.
  • \w*- Stimmen Sie mit dem Rest des Wortes überein.
    Das oben Gesagte stimmt nur mit Wörtern überein - alle anderen Zeichen werden entfernt.
  • ¶\n - Ersetzen Sie Zeilenumbrüche durch Leerzeichen.

Probieren Sie es online!

Kobi
quelle
9

Netzhaut , 45 Bytes

i` \ b ((\ w) \ w *) \ b (? <= \ b \ 2 \ w * \ b. +)

\ W +
 
^ | $

Verwenden Sie einfach einen einzelnen regulären Ausdruck, um spätere Wörter zu entfernen, die mit demselben \wZeichen beginnen (bei der iOption wird die Groß- / Kleinschreibung nicht beachtet), konvertieren Sie Läufe von \Win ein einzelnes Leerzeichen und entfernen Sie dann alle führenden / nachfolgenden Leerzeichen aus dem Ergebnis.

Probieren Sie es online!

Bearbeiten: Siehe @ Kobi Antwort für eine kürzere Version mitM!`

Sp3000
quelle
Verdammt, schlag mich kaum! Ich konnte jedoch nicht herausfinden, wie es aussah.
GamrCorps
3
Ich habe eine weitere Retina-Antwort hinzugefügt - ich denke , das ist in Ordnung, wenn sie sich genug unterscheiden (das Grundkonzept ist natürlich ähnlich).
Kobi
1
@Kobi Es ist viel besser, also bin ich froh, es zu sehen :) Lässt mich erkennen, wie viel mehr ich über die Linienoptionen von Retina lernen muss und was nicht.
Sp3000
Könnten Sie dies tun, um ein paar Bytes zu sparen? i` \b((\w)\w*)\b(?<=\b\2\w*\b.+)(ein Leerzeichen vor dem ersten \b) Sind die Zeilen danach unnötig?
Undichte Nonne
@KennyLau Leider glaube ich nicht, dass das funktioniert, weil Wörter nicht unbedingt durch Leerzeichen getrennt sind, zBa...a -> a
Sp3000
9

JavaScript (ES6), 73-71 Byte

s=>s.match(u=/\w+/g).filter(w=>u[n=parseInt(w[0],36)]?0:u[n]=1).join` `

2 Bytes gespart dank @ edc65!

Prüfung

var solution = s=>s.match(u=/\w+/g).filter(w=>u[n=parseInt(w[0],36)]?0:u[n]=1).join` `;
var testCases = [
  "Ferulas flourish in gorgeous gardens.",
  "Take all first words for each letter... this is a test",
  "Look ^_^ .... There are 3 little dogs :)",
  "...maybe some day 1 plus 2 plus 20 could result in 3"
];
document.write("<pre>"+testCases.map(t=>t+"\n"+solution(t)).join("\n\n")+"</pre>");

user81655
quelle
Verwenden parseInt("_",36) = NaN? Blasphemie!
Sp3000
1
Die lustige Tatsache ist: es funktioniert @ Sp3000
edc65
Die Verwendung von u = regexp ist sehr clever. Speichern Sie 2 Bytess=>s.match(u=/\w+/g).filter(w=>u[w=parseInt(w[0],36)]?0:u[w]=1).join' '
edc65
@ edc65 Danke. Eigentlich ist es praktisch, dass es 37 mögliche Ausgänge für eine einzelne Base-36-Ziffer gibt.
user81655
7

Pyth, 23 Bytes

J:z"\w+"1jdxDJhM.grhk0J

Probieren Sie es online aus: Demo oder Test Suite

J:z"\w+"1findet alle Wörter in der Eingabe unter Verwendung des regulären Ausdrucks \w+und speichert sie in J.

.grhk0Jgruppiert die Wörter nach dem Anfangsbuchstaben in Kleinbuchstaben, hMnimmt den ersten aus jeder Gruppe, xDJsortiert diese Wörter nach dem Index in der Eingabezeichenfolge und jdfügt Leerzeichen dazwischen ein.

Jakube
quelle
4

Perl 6, 39 Bytes

{.words.grep({!%.{.substr(0,1).lc}++})}
Ven
quelle
1
42 Bytes, die die Wörter \w+beheben, die zusammenpassen müssen, und den substrTeil Golf spielen
Jo King
3

C, 142 132 122 Bytes

10 Bytes leichter dank @tucuxi!

b[200],k;main(c){for(;~c;isalnum(c)|c==95?k&2?:(k|=!b[c|32]++?k&1?putchar(32):0,7:2),k&4?putchar(c):0:(k&=1))c=getchar();}

Gibt nach dem letzten Ausgabewort ein Leerzeichen aus.

mIllIbyte
quelle
1
Sie können die Schecks für c>47und c<58mit isalnumanstelle von rasierenisalpha
Tucuxi
3

MATL , 23 Bytes

'\w+'XXtck1Z)t!=XRa~)Zc

Dies ist eine Anlehnung an Jakubes Idee , einen regulären Ausdruck zu verwenden, um unerwünschte Zeichen zu entfernen und gleichzeitig zu teilen.

Die Eingabe ist eine Zeichenfolge, die in einfache Anführungszeichen eingeschlossen ist.

Probieren Sie es online!

Erläuterung

'\w+'XX  % find words that match this regexp. Gives a cell array
t        % duplicate
c        % convert into 2D char array, right-padded with spaces
k        % make lowercase
1Z)      % get first column (starting letter of each word)
t!=      % duplicate, transpose, test for equality: all combinations  
XR       % set diagonal and below to 0
a~       % true for columns that contain all zeros       
)        % use as a logical index (filter) of words to keep from the original cell array
Zc       % join those words by spaces
Luis Mendo
quelle
2

Vim 57 Tastenanschläge

:s/[^a-zA-Z_ ]//g<cr>A <cr>ylwv$:s/\%V\c<c-v><c-r>"\h* //eg<c-v><cr>@q<esc>0"qDk@q

Erläuterung:

:s/[^a-zA-Z_ ]//g                                 #Remove all invalid chars.
A <cr>                                            #Enter insert mode, and enter 
                                                  #a space and a newline at the end
ylwv$:s/\\c%V<c-v><c-r>"\h* //eg<c-v><cr>@q<esc>  #Enter all of this text on the 
                                                  #next line

0                                                 #Go to the beginning of the line
"qD                                               #Delete this line into register
                                                  #"q"
k@q                                               #Run "q" as a macro  

#Macro
ylw                                               #Yank a single letter
   v$                                             #Visual selection to end of line
     :s/                                          #Substitute regex
       \%V\c                                      #Only apply to the selection and 
                                                  #ignore case
            <c-v><c-r>"                           #Enter the yanked letter
                       \h*                        #All "Head of word" chars
                                                  #And a space
                           //                     #Replace with an empty string
                             eg                   #Continue the macro if not found
                                                  #Apply to all matches
                               <c-v><cr>          #Enter a <CR> literal
                                        @q<esc>   #Recursively call the macro

Ich bin wirklich enttäuscht, wie lange dieser ist. Die „Ungültig“ Zeichen (alles außer a-z, A-Z, _und Raum) wirft mir wirklich ab. Ich bin mir sicher, dass es einen besseren Weg gibt, dies zu tun:

:s/[^a-zA-Z_ ]//g

Da \hpasst das alles für den Raum, aber ich kann nicht herausfinden, wie man den Metacar in eine Reihe legt. Wenn jemand Tipps hat, würde ich sie gerne hören.

DJMcMayhem
quelle
3
warum a-zA-Z_nicht \w? Ziffern sind gültig
edc65
2

Julia, 165 155 151 129 102 Bytes

g(s,d=[])=join(filter(i->i!=0,[(c=lcfirst(w)[1])∈d?0:(d=[d;c];w)for w=split(s,r"\W",keep=1<0)])," ")

Dies ist eine Funktion, die eine Zeichenfolge akzeptiert und eine Zeichenfolge zurückgibt.

Ungolfed:

function g(s, d=[])
    # Split the string into an array on unwanted characters, then for
    # each word, if the first letter has been encountered, populate
    # this element of the array with 0, otherwise note the first letter
    # and use the word. This results in an array of words and zeros.
    x = [(c = lcfirst(w)[1])  d ? 0 : (d = [d; c]; w) for w = split(s, r"\W", keep=1<0)]

    # Remove the zeros, keeping only the words. Note that this works
    # even if the word is the string "0" since 0 != "0".
    z = filter(i -> i != 0, x)

    # Join into a string and return
    return join(z, " ")
end

53 Bytes mit Hilfe von Sp3000 gespeichert!

Alex A.
quelle
2

Jelly, 32 31 Bytes

ØB;”_
e€¢¬œṗf€¢¹ÐfµZḢŒlQi@€$ịj⁶

Probieren Sie es online!

Dennis
quelle
2

C # (LINQPAD) - 136 128 Bytes

var w=Util.ReadLine().Split(' ');string.Join(" ",w.Select(s=>w.First(f=>Regex.IsMatch(""+f[0],"(?i)"+s[0]))).Distinct()).Dump();
jzm
quelle
2

05AB1E , 40 Bytes

Code:

94L32+çJžj-DU-ð¡""Kvy¬Xsl©åï>iX®«Uy}\}ðý

Probieren Sie es online!

Erläuterung:

Wir generieren zunächst alle Zeichen, die aus dem Eingabe-String gelöscht werden sollen, mit 94L32+ç( Try here ). Wir verbinden diesen String mit Jund entfernen, [a-zA-Z0-9_]was in žj gespeichert ist ( versuchen Sie es hier ). Wir entfernen alle Zeichen, die sich in der zweiten Zeichenfolge befinden, aus der ersten Zeichenfolge.

!"#$%&'()*+,-./:;<=>?@[\]^`{|}~

Das kann auch getestet werden hier . Wir Dduplizieren dies und speichern es Xmit dem U-Befehl. Wir entfernen dann alle Zeichen, die in dieser Zeichenfolge enthalten sind, aus der Eingabe. Anschließend teilen wir Leerzeichen mit auf ð¡und entfernen alle leeren Zeichenfolgen (mit ""K). Wir haben jetzt diese .

Dies ist die saubere Version der Eingabe, mit der wir arbeiten werden. Wir bilden jedes Element mit ab v. Dies wird yals String-Variable verwendet. Wir nehmen das erste Zeichen der Zeichenkette mit ¬und drücken X, welches eine Zeichenkette mit allen verbotenen Zeichen ( !"#$%&'()*+,-./:;<=>?@[\]^`{|}~) enthält. Wir prüfen, ob die lGroß- / Kleinschreibung des ersten Zeichens (das auch ©in das Register eingetragen wird) in dieser Zeichenfolge enthalten ist å. In diesem Teil behandelt: ï>iWenn der erste Buchstabe in der Zeichenfolge der verbotenen Zeichen ( X) nicht vorhanden ist , fügen wir diesen Buchstaben der Liste der verbotenen Zeichen (fertig mit X®«U) hinzu und legen ihn yoben auf den Stapel.

Wenn die Zeichenfolgen gefiltert werden, verbinden wir den Stapel mit Leerzeichen ðý.

Adnan
quelle
1
... Erklärung? :-)
Luis Mendo
@ LuisMendo Danke, dass du mich daran erinnert hast! Fertig :)
Adnan
2

PHP

Inspiriert von der Verwendung von Regex in den meisten Antworten, habe ich ursprünglich versucht, dies zu tun, ohne Regex überhaupt zu verwenden, nur um eine ordentliche Variation vorzuführen, aber der Knackpunkt, keine sauberen Zeichenfolgen als Eingabe zu haben, ruinierte diese Idee. Traurig.

Mit Funktionswrapper 89 Bytes

function f($s){foreach(preg_split('/\W/',$s)as$w)$c[lcfirst($w)[0]]++?:$v.=" $w";echo$v;}

Ohne Funktions-Wrapper (benötigt $ s vordeklariert), 73 Bytes

foreach(preg_split('/\W/',$s)as$w)$c[lcfirst($w)[0]]++?:$v.=" $w";echo$v;

Erläuterung:

foreach(preg_split('/\W/',$s)as$w)$c[lcfirst($w)[0]]++?:$v.=" $w";echo$v;
        preg_split('/\w/',$s)                                             Break input on all non-word characters
foreach(                     as$w)                                        Loop through each 'word'
                                     lcfirst($w)[0]                       Take the first letter of the lowercase version of the word
                                  $c[              ]++?:                  Increment an array element with a key of that letter after checking if it's false-y (0)
                                                        $v.=" $w";        Add the word if the letter wasn't found (if the previous condition evaluated to false)
                                                                  echo$v; Print the new string to screen.

Ich bedaure nur, dass ich keinen schnelleren Weg gefunden habe, um die Groß- / Kleinschreibung zu überprüfen / zu konvertieren.

Xanderhall
quelle
2

Python, 103 Bytes

import re
lambda s,d=[]:[w for w in re.findall("\w+",s)if(d.append(w.lower()[0])or d[-1])not in d[:-1]]
orlp
quelle
1

Lua, 172 Bytes

Es endete viel länger, dass ich wollte ...

t={}(...):gsub("[%w_]+",function(w)b=nil for i=1,#t
do b=t[i]:sub(1,1):lower()==w:sub(1,1):lower()and 1 or b
end t[#t+1]=not b and w or nil end)print(table.concat(t," "))

Ungolfed

t={}                           -- initialise the accepted words list
(...):gsub("[%w_]+",function(w)-- iterate over each group of alphanumericals and underscores
  b=nil                        -- initialise b (boolean->do we have this letter or not)
  for i=1,#t                   -- iterate over t
  do
    b=t[i]:sub(1,1):lower()    -- compare the first char of t's i word
       ==w:sub(1,1):lower()    -- and the first char of the current word
           and 1               -- if they are equals, set b to 1
           or b                -- else, don't change it
  end
  t[#t+1]=not b and w or nil   -- insert w into t if b isn't set
end)

print(table.concat(t," "))     -- print the content of t separated by spaces
Katenkyo
quelle
1

Im Ernst, 43 Bytes

6╙¬▀'_+,;)-@s`;0@Eùk`M┬i;╗;lrZ`i@╜í=`M@░' j

Probieren Sie es online!

Der Mangel an Regex-Fähigkeiten machte dies viel schwieriger als nötig.

Erläuterung:

6╙¬▀'_+,;)-@s`;0@Eùk`M┬i;╗;lrZ`i@╜í=`M@░' j
6╙¬▀                                         push digits in base 62 (uppercase and lowercase letters and numbers)
    '_+                                      prepend underscore
       ,;)                                   push two copies of input, move one to bottom of stack
          -                                  get all characters in input that are not letters, numbers, or underscores
           @s                                split input on all occurrences of non-word characters
             `;0@Eùk`M                       for each word: push the first letter (lowercased)
                      ┬i                     transpose and flatten (TOS is list of first letters, then list of words)
                        ;╗                   push a copy of the first letters list to register 0
                          ;lrZ               zip the list of first letters with their positions in the list
                              `i@╜í=`M       for each first letter: push 1 if that is the first time the letter has been encountered (first index of the letter matches its own index) else 0
                                      @░     filter words (take words where corresponding element in the previous list is truthy)
                                        ' j  join on spaces
Mego
quelle
1

Ruby 76 Bytes

s;f={};s.scan(/(([\w])[\w]*)/).map{|h,i|f[j=i.upcase]?nil:(f[j]=!p; h)}.compact.*' '

Oder mit Methodendefinition 88 Bytes

def m s;f={};(s.scan(/((\w)\w*)/).map{|h,i|f[j=i.upcase]?nil:(f[j]=1; h)}-[p]).*' ';end

Ungolfed und mit Unit Test:

def m_long(s)
  #found  - Hash with already found initials
  f={}
  #h=hit, i=initial, j=i[0].downcase
  s.scan(/(([\w\d])[\w\d]*)/).map{|h,i| 
    f[j=i.upcase] ? nil : (f[j] = true; h)
  }.compact.join(' ')
end
#true == !p
#~ def m(s)
  #~ f={};s.scan(/(([\w\d])[\w\d]*)/).map{|h,i|f[j=i.upcase]?nil:(f[j]=!p; h)}.compact.join' '
#~ end
def m s;f={};s.scan(/(([\w\d])[\w\d]*)/).map{|h,i|f[j=i.upcase]?nil:(f[j]=!p; h)}.compact.join' ';end

#~ s = "Ferulas flourish in gorgeous gardens."
#~ p s.split

require 'minitest/autorun'
class FirstLetterTest < Minitest::Test
  def test_1
    assert_equal("Ferulas in gorgeous",m("Ferulas flourish in gorgeous gardens."))
    assert_equal("Ferulas in gorgeous",m_long("Ferulas flourish in gorgeous gardens."))
  end
  def test_2
    assert_equal("Take all first words each letter is",m("Take all first words for each letter... this is a test"))
    assert_equal("Take all first words each letter is",m_long("Take all first words for each letter... this is a test"))
  end
  def test_3
    assert_equal("Look _ There are 3 dogs",m("Look ^_^ .... There are 3 little dogs :)"))
    assert_equal("Look _ There are 3 dogs",m_long("Look ^_^ .... There are 3 little dogs :)"))
  end
  def test_4
    assert_equal("maybe some day 1 plus 2 could result in 3",m("...maybe some day 1 plus 2 plus 20 could result in 3"))
    assert_equal("maybe some day 1 plus 2 could result in 3",m_long("...maybe some day 1 plus 2 plus 20 could result in 3"))
  end
end
knut
quelle
\wEnthält in Regex Zahlenzeichen, [\w\d]kann also durch ersetzt werden \w. Wenn nilsich beim Aufrufen Werte in einem Array befinden join' '(oder besser noch *' 'eine Abkürzung, mit der Sie mehr Bytes speichern können), verschwinden diese, sodass der Aufruf von nicht compacterforderlich ist.
Wert Tinte
@ KevinLau Danke. Das \w\dist mir peinlich. Aber wenn ich das entferne, compactbekomme ich zusätzliche Leerzeichen (siehe ['x',nil,'x']*'y' == 'xyyx'). Oder habe ich etwas verpasst?
Knut
Hoppla, du hast recht. In diesem Fall werden (list-[p])Bytes gespeichert list.compact. Auch /\w/ist gleichbedeutend mit /[\w]/. Schließlich können Sie Ihre nilmit pund Ihre !pmit ersetzen 1(da Ihr Hash nur Wahrheitswerte darin benötigt)
Value Ink
Danke, ich fügte Ihre Bemerkungen hinzu, Der Ersatz nilmit pfunktioniert nicht. Wenn ich es in meinem Code verwende, erhalte ich einen Syntaxfehler. Ich muss gerne kapseln (p)- aber dann habe ich wieder 3 Zeichen.
Knut
Klappen Sie den ternären und dann funktioniert es ein Byte speichern: !f[j=i.upcase]?(f[j]=1;h):p. Auch nur daran gedacht, aber wegen der Indexierung von Strings, die Verwendung s.scan(/\w+/)und das Entfernen ivon h[0]zu Gunsten von Werken.
Wert Tinte
1

grep und awk, 68 56 bytes

Das Drehbuch:

echo `grep -o '\w*'|awk '!x[tolower(substr($0,1,1))]++'`

Erläuterung:

  • grep -o Stimmt mit den zulässigen Wörtern überein, wobei jedes Wort in einer eigenen Zeile gedruckt wird.

  • awkNimmt den ersten Buchstaben jeder Zeile mit substr, macht ihn klein und erhöht dann einen Hashtabelleneintrag mit dieser Taste. Wenn der Wert vor dem Inkrement nicht gesetzt war, wird die Zeile gedruckt.

  • echo ... verwandelt die Zeilen wieder in Worte

Ich habe versucht , vorher eine Lösung zu schaffen , ohne awk, mit uniq, sort, grepund bashdoch nur kurz fiel. Geschichte in den Bearbeitungen.

Vielen Dank an Dennis für einige Verbesserungen, die ich verpasst habe.

joeytwiddle
quelle
0

Python 3.5, 138 Bytes:

import re;lambda o,t=[]:''.join([y[0]for y in[(u+' ',t.append(u[0].lower()))for u in re.sub('\W+',' ',o).split()if u[0].lower()not in t]])

Grundsätzlich passiert was ist ..

  1. Mit einem einfachen regulären Ausdruck ersetzt das Programm alle Zeichen mit Ausnahme von Klein- oder Großbuchstaben, Ziffern oder Unterstrichen in der angegebenen Zeichenfolge durch Leerzeichen und teilt die Zeichenfolge dann an diesen Leerzeichen auf.
  2. Erstellen Sie dann mithilfe des Listenverständnisses eine Liste, die alle Wörter in der aufgeteilten Zeichenfolge durchläuft, und fügen Sie die ersten Buchstaben jedes Wortes zur Liste "t" hinzu.
  3. Befindet sich dabei der erste Buchstabe des aktuellen Wortes NICHT bereits in der Liste "t", werden dieses Wort und ein Leerzeichen zur aktuellen Liste hinzugefügt, die gerade erstellt wird. Andernfalls setzt die Liste das Anhängen der ersten Buchstaben jedes Wortes an die Liste "t" fort.
  4. Wenn alle Wörter in der Aufteilung durchlaufen wurden, werden die Wörter in der neuen Liste zu einer Zeichenfolge zusammengefasst und zurückgegeben.
R. Kap
quelle
0

PHP 120 Bytes

function a($s){foreach(preg_split('/\W/',$s)as$w)if(!$o[ucfirst($w[0])]){$o[ucfirst($w[0])]=$w;}return implode(" ",$o);}

Dies erzeugt eine Reihe von Warnungen, aber das ist in Ordnung.

user52869
quelle
Ist das functionnötig?
AL
0

Javascript ES6, 108 107 Zeichen

Bei 107 Zeichen wird die Ergebniszeichenfolge gekürzt

r=s=>s.split``.reverse().join``
f=s=>r(r(s).replace(/\b\w*(\w)\b(?=.*\1\b)/gi,'')).replace(/\W+/g,' ').trim()

Prüfung:

["Take all first words for each letter... this is a test",
"Look ^_^ .... There are 3 little dogs :)",
"...maybe some day 1 plus 2 plus 20 could result in 3"
].map(f) + '' == [
"Take all first words each letter is",
"Look _ There are 3 dogs",
"maybe some day 1 plus 2 could result in 3"
]
Qwertiy
quelle
f= zählt nicht .
Qwertiy
0

Tcl , 150 Bytes

proc F {s D\ {}} {lmap w [split $s] {regsub -all \[^\\w] $w "" f
if {![dict e $D [set k [string tol [string in $f 0]]]]} {dict se D $k $f}}
dict v $D}

Probieren Sie es online!

Sergiol
quelle