Hashtag Analyse

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
Renuschi
User
Beiträge: 6
Registriert: Samstag 17. Oktober 2020, 14:26

Hallo Miteinander,

Meine Aufgabe ist es eine Liste voller Strings nach hashtags zu durchsuchen und dann diese zu zählen. Das Resultat sollte aber ein "dictionary" sein, in dem die keys die Hashtagwörter sind und der Wert, die gezählten Werte.

Beispiel:

Code: Alles auswählen

[
    "hi #weekend",
    "good morning #germany #haus",
    "spend my #weekend in #germany",
    "#germany <3"
]
So etwas sollte als Antwort heraus kommen:

Code: Alles auswählen

{'weekend': 2, 'germany': 3, 'haus': 1}

Folgender Code ist meiner, ich komme nicht weiter, weil mir das know-how fehlt. Bitte daher um Ratschläge, Tips und vielleicht andere Beispiele.

Code: Alles auswählen

def analyze(posts):
    dic = {}
    counter = 0
    for i in posts:
        if "#" in i:
            hashtag = i.find("#")
            hashtagwort = i[hashtag:]
            hashtagwort = hashtagwort.replace("#", "")
            counter += 1
            dic[hashtagwort] = counter
            return dic
            break
    else:
        return None


posts = ["hi #weekend", "good morning #germany #haus", "spend my #weekend in #germany", "#germany <3"]
print(analyze(posts))
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast mehrere Aufgaben in eine Funktion gepackt.
Eine erste Funktion wäre, zu einem String alle Hashtags zu liefern.
Eine zweite Funktion wäre, die posts durchzugehen und für jeden die erste Funktion aufzurufen und das ganze zu zählen.
Ein `return` innerhalb einer for-Schleife ist meist falsch, da Du ja nicht die Suche abbrechen willst.
`counter` sollte ja ein Zähler für jedes hashtagwort sein, und nicht einer für alle.
`dic` ist ein schlechter Variablenname, weil er erstens eine kryptische Abkürzung von dict bzw. dictionary ist und zweitens den Typ und nicht den Inhalt der Variable beschreibt.
`i` ist ein besonders schlechter Name für einen Post, da man mit i immer einen Index verbindet.
hashtag ist der falsche Name für den Index des #-Zeichens.
Das replace wäre unnötig, wenn Du erst ab hashtag+1 den String beschneidest.

Die erste Funktion wäre ein Einzeiler, wenn Du reguläre Ausdrücke verwenden würdest. Wie ist denn ein Hashtag definiert. Welche Zeichen dürfen enthalten sein? Ist das Leerzeichen immer das Trennzeichen?
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

Mit `collections.Counter` ist das Zählen auch ein Einzeiler. Und `itertools.chain` könnte auch hilfreich sein.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich möchte lösen:

Code: Alles auswählen

from collections import Counter
from itertools import chain
import re

def get_hashtags(string):
    return re.findall(r"#(\w+)", string)

def count_tags(strings):
    return Counter(
        chain.from_iterable(
            map(get_hashtags, strings)
        )
    )

def main():
    strings = [
        "hi #weekend",
        "good morning #germany #haus",
        "spend my #weekend in #germany",
        "#germany <3"
    ]
    print(count_tags(strings))

if __name__ == "__main__":
    main()
Oder in der kompakten Version:

Code: Alles auswählen

Counter(chain.from_iterable(re.findall(r"#(\w+)", s) for s in strings))
Falls das für eine Hausaufgabe ist, dann bitte auch bedenken, dass die Aufgabe erklärt werden sollte. Nicht dass noch irgendwer denkt, man hätte eine Komplettlösung aus dem Internet übernommen...

PS: Das Pattern muss ggf noch angepasst werden, je nachdem wie ein Hashtag definiert ist (hatten ja die Vorposter auch schon gefragt).
nezzcarth
User
Beiträge: 1636
Registriert: Samstag 16. April 2011, 12:47

Es muss zunächst geklärt werden, welche Zeichen in den Hashtags erlaubt sind. Hashtags bekannter Dienste, insb. beispielsweise die von Twitter, sind teilweise nicht trivial. Da reicht ein einfaches \w+ nicht aus.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das ganze ist natürlich noch ausbaufähig, je nach Einsatzgebiet. Ich weiß von Twitter zB dass Groß- und Kleinschreibung ignoriert wird. Hierdurch ist #germany das gleiche wie #Germany oder #gErMaNy. Auch die Gleichbehandlung von ä->ae oder ß->ss könnte wichtig sein. Wir wissen ja nicht, worauf das am Ende hinauslaufen soll.

Unicode-Zeichen (von denen es inzwischen ja extrem viele gibt, die auch tatsächlich zum Einsatz kommen) sind sowieso eine spezielle Geschichte. Bei Twitter gibt es ein Hashtag #MaskeAuf unmittelbar gefolgt von einem Masken-Smiley. Das Tag kriegt man auch, wenn man den Smiley weglässt (was auch Sinn macht). Auch sowas sollte für professionelle Analysen natürlich mitgedacht werden.
Benutzeravatar
Renuschi
User
Beiträge: 6
Registriert: Samstag 17. Oktober 2020, 14:26

Danke für eure Hilfe!
An Snafu habe ich noch eine Frage. Ist es möglich, dass bei deinem Code nur ein Dictionary ausgegeben wird, sprich ohne dieses Counter({...})?
Und könnte man dieses Problem auch nur mit einer Funktion lösen?
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Renuschi: ja, ist möglich. Nur eine Funktion ist nicht sinnvoll, da jede Funktion am besten eine spezifische Aufgabe erfüllt.
Benutzeravatar
Trägheit
User
Beiträge: 12
Registriert: Samstag 24. Oktober 2020, 20:14

Sind inzwischen ja schon ein paar Antworten eingetrudelt. :)

Als Neuling in Python hätte ich es vermutlich so gelöst:
(unter der Annahme das vor/nach jedem Hashtag ein Leerzeichen steht - #welcome#home ist also nicht erlaubt)

Code: Alles auswählen

posts = ["hi #weekend", "good morning #germany #haus", "spend my #weekend in #germany", "#germany <3"]

hashtags = {}
for post in posts:
    for word in post.split(" "):
        if word.startswith("#"):
            hashtag = word[1:]
            """
            if hashtag in hashtags:
                hashtags[hashtag] = hashtags[hashtag] + 1
            else:
                hashtags[hashtag] = 1
            """
            hashtags[hashtag] = hashtags.get(hashtag, 0) + 1

print("hashtags:", hashtags)
Mit List Comprehension und etwas importierter Hilfe ginge es etwas kompakter - so:

Code: Alles auswählen

from collections import Counter

posts = ["hi #weekend", "good morning #germany #haus", "spend my #weekend in #germany", "#germany <3"]

hashtags = [list(filter(lambda word: word.startswith("#"), post.split(" "))) for post in posts]
hashtags = [hashtag[1:] for sublist in hashtags for hashtag in sublist]

hashtags = Counter(hashtags)

print("hashtags:", hashtags)
Den Umbruch am #-Zeichen vorzunehmen wäre auch noch möglich, nur verschwindet dann dieses mit str.split() - dann hat man das Problem, dass man nicht mehr weiß, welcher ein Hashtags war und welcher nicht.
Der nächste Ansatz, an den ich gedachte hatte, war per regulären Ausdrück ... mit dem Modul habe ich mich in Python aber (noch) nicht auseinander gesetzt. ;)


An die Python Profis: Wäre das ok?


PS: Das Prüfen auf Programm- / Modul-Ebene erspare ich mir bei Beispielen.
Zuletzt geändert von Trägheit am Dienstag 27. Oktober 2020, 00:43, insgesamt 2-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Renuschi:
Ich habe das ja schon als Einzeiler hingeschrieben. Das Ergebnis vom Counter kann man natürlich noch durch ein dict() jagen, was bloß wegen der Art der Ausgabe aber schon grenzwertig ist. Grundsätzlich könntest du auch direkt eine angepasste Ausgabe verwenden mithilfe einer for-Schleife und print(). Ein bisschen was kann man als Fragesteller auch mal mit eigenem Nachdenken lösen. :)
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@Trägheit: Im Großen und Ganzen ist das in Ordnung, würde ich sagen.

Ein paar Details lassen sich natürlich immer ausbessern: `collections.defaultdict` könnte man statt des `.get(..., 0)` benutzen, und im zweiten Beispiel ist der `list`-Aufruf unnötig.

Außerdem sind Strings keine Kommentare und es ist nicht so schön, dass du den Namen `hashtags` für drei verschiedene Dinge benutzt.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Trägheit:
Das ist auch völlig in Ordnung als Lösung. Gerade für Anfänger und/oder wenn der Leser sich nicht so gut mit regulären Ausdrücken auskennt. Da Python echt mächtig in Sachen String-Verarbeitung ist, lässt sich vieles auch ohne Regex-Engine lösen. Das ist oftmals Geschmackssache wie man solche Aufgaben angehen möchte.

EDIT:
Abgesehen von den Dingen natürlich, die narpfel angesprochen hat. Aber der Ansatz ist wie gesagt okay. ;)
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nochmal @Trägheit jetzt, wo ich wiederum deine Bearbeitung des Beitrags sehe: Die zweite Variante mag technisch gehen, ist aber nicht so leicht nachvollziehbar. Gerade das Gemurkse mit list(), filter() und lambda muss nicht sein. Wenn man sowieso schon eine List-Comprehension benutzt, dann sollte man filter() besser weglassen. Man schreibt jedenfalls nicht automatisch guten Python-Code, nur weil man viele LCs drin hat...
Benutzeravatar
Trägheit
User
Beiträge: 12
Registriert: Samstag 24. Oktober 2020, 20:14

narpfel hat geschrieben: Dienstag 27. Oktober 2020, 00:33 @Trägheit: Im Großen und Ganzen ist das in Ordnung, würde ich sagen.

Ein paar Details lassen sich natürlich immer ausbessern: `collections.defaultdict` könnte man statt des `.get(..., 0)` benutzen,
Aha. 'collections.defaultdict' kenne ich noch nicht, schau ich mal an.
narpfel hat geschrieben: Dienstag 27. Oktober 2020, 00:33 und im zweiten Beispiel ist der `list`-Aufruf unnötig.
Du hast Recht. Beim schrittweise durchgehen war mir die Ausgabe vom Filter-Objekt ein Dorn im Auge - das 'list' ist dann drin hängen geblieben. ;)
narpfel hat geschrieben: Dienstag 27. Oktober 2020, 00:33 Außerdem sind Strings keine Kommentare und es ist nicht so schön, dass du den Namen `hashtags` für drei verschiedene Dinge benutzt.
Du meinst bestimmt die drei Anführungszeichen (""")? Ich weiß das die nicht für mehrzeilige Kommentare gedacht sind (sowas scheint es in Python auch nicht zu geben; nur Einzeiler), für was aber dann ... da bin ich noch dran (ich glaub die werden im Zusammenhang mit Schnittstellenbeschreibungen benutzt).
narpfel hat geschrieben: Dienstag 27. Oktober 2020, 00:33 es ist nicht so schön, dass du den Namen `hashtags` für drei verschiedene Dinge benutzt.
Da bin ich gerade unschlüssig über das "Warum".
Den alten Inhalt von 'hashtags' brauche ich ja an den Stellen nicht mehr. Anstatt eine neue Variable einzuführen, kann ich doch die Gleiche weiter verwenden, oder nicht? (Der Garbage Collector sollte sich um den Rest kümmern.)
Allgemein scheint es so, dass in Python "Datentypen" keine wesentliche Rolle spielen... warum also bei den Variablen einen Unterschied machen - geht es hierbei um Ästhetik / Python Konventionen?
Benutzeravatar
Trägheit
User
Beiträge: 12
Registriert: Samstag 24. Oktober 2020, 20:14

snafu hat geschrieben: Dienstag 27. Oktober 2020, 00:44 Nochmal @Trägheit jetzt, wo ich wiederum deine Bearbeitung des Beitrags sehe: Die zweite Variante mag technisch gehen, ist aber nicht so leicht nachvollziehbar. Gerade das Gemurkse mit list(), filter() und lambda muss nicht sein. Wenn man sowieso schon eine List-Comprehension benutzt, dann sollte man filter() besser weglassen. Man schreibt jedenfalls nicht automatisch guten Python-Code, nur weil man viele LCs drin hat...
Wie würde man dann in einer List-Comprehension filtern?

Edit: Mit 'If' in der List-Comprehension hatte ich es nicht hinbekommen.
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Man hängt die Filter-Bedingung hinten dran:

Code: Alles auswählen

[word[1:] for string in strings for word in string.split() if word.startswith("#")]
Verschachtelte Schleifen schreibt man in einem durch, wie du siehst. Was passieren soll, steht davor und die Bedingung wie gesagt ganz am Ende.
Benutzeravatar
Trägheit
User
Beiträge: 12
Registriert: Samstag 24. Oktober 2020, 20:14

snafu hat geschrieben: Dienstag 27. Oktober 2020, 01:11 Man hängt die Filter-Bedingung hinten dran:

Code: Alles auswählen

[word[1:] for string in strings for word in string.split() if word.startswith("#")]
Verschachtelte Schleifen schreibt man in einem durch, wie du siehst. Was passieren soll, steht davor und die Bedingung wie gesagt ganz am Ende.
Hm, demnach so:

Code: Alles auswählen

from collections import Counter
posts = ["hi #weekend", "good   morning  #germany  #haus", "spend my #weekend in #germany", "#germany <3"]
hashtags = [word[1:] for string in posts for word in string.split() if word.startswith("#")]
print(Counter(hashtags))
Wow, das hab ich nicht sofort gesehen. :D
Danke Dir!

PS: Der größte K(r)ampf in Python ist einen Überblick über die verfügbaren Werkzeuge/Module zu bekommen und zu wissen wann man was davon einsetzen sollte. :lol:
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das sind alles Erfahrungswerte, die man sich im Laufe der Zeit aneignet. Python ist inzwischen ziemlich komplex geworden. Ich bin damals bei Version 2.5 dazu gestoßen. Da waren die Dinge noch etwas "übersichtlicher". Ich find's aber okay. Die Sprache ist inzwischen ziemlich populär und sie muss irgendwo auch mit der Zeit gehen. Ich sehe (bisher) nicht, dass man sich dabei total verrennt. Es war mal etwas kritisch in den ersten Jahren von Python 3.x, aber inzwischen haben die Entwickler das IMHO ganz gut gewuppt. :)
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Trägheit hat geschrieben: Dienstag 27. Oktober 2020, 01:01 Anstatt eine neue Variable einzuführen, kann ich doch die Gleiche weiter verwenden, oder nicht?
Wenn alle Leute Heinz heißen, ist es schwierig sie auseinanderzuhalten. Genauso ist es mit deinem Variablen. Du musst dich immer fragen, was ist denn der Unterschied; und der sollte im Variablennamen klar werden.

Trägheit hat geschrieben: Dienstag 27. Oktober 2020, 01:01 Allgemein scheint es so, dass in Python "Datentypen" keine wesentliche Rolle spielen...
Ganz im Gegenteil. Der Datentyp bestimmt die Eigenschaften eines Objekts.
Benutzeravatar
Trägheit
User
Beiträge: 12
Registriert: Samstag 24. Oktober 2020, 20:14

@Sirius3:
Im ersten Punkt verstehe ich schon was du meinst, jedoch musste da nichts außeinander gehalten werden. Ziel war es beim Counter-'Heinz' heraus zu kommen, da brauch ich die alten 'Heinze' nicht mehr. Falls ich die Liste mit den darin enthaltenen SubListen vielleicht später noch für was anderes gebrauchen würde, dann hätte ich in diesem Fall jenem einen eigenen Bezeichner gegeben.

Python besitzt kein stricktes 'type checking' und daher spielt der Datentyp in erster Linie keine Rolle.
So wie ich das sehe handelt es sich hier ehern um den Ansatz des Duck-Typing, wo es darauf ankommt, welche Methoden und Attribute implementiert sind. Und daraus ergibt sich dann, um was es sich handelt.
Antworten