[Python] Funktion som kan returnera index av en viss karaktär från en sträng?

Permalänk

[Python] Funktion som kan returnera index av en viss karaktär från en sträng?

Hej, jag letar efter en funktion som kan returnera indexet av en viss karaktär (eller flera). Jag ska kunna välja vilket "o" (exempel) som jag ska kolla indexet ifrån. Har för mig att det finns en funktion som kan göra detta.

Exempel:
Sträng = "oslo"

förstaOIndex = funktion("o", 1) # förstaOIndex ska få värdet 0

andraOIndex = funktion("o", 2) # andraOIndex ska få värdet 3

Slut

Jag kanske har förklarat lite luddigt men hoppas ändå ni förstår.

Permalänk
Hedersmedlem

Du kan exempelvis använda strängens find-metod:

find(...) S.find(sub[, start[, end]]) -> int Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation. Return -1 on failure.

(Se även help(str.index).)

och hitta indexen genom:

>>> def find_character_indices(haystack, needle): ... indices = [] ... i = -1 ... while True: ... i = haystack.find(needle, i + 1) ... if i == -1: ... break ... indices.append(i) ... return indices ... >>> find_character_indices('oslo', 'o') [0, 3]

Dock ser detta inte speciellt "Pythonskt" ut (i en viss pedagogisk medvetenhet från min sida; "There must be a better way!" (länk till föredraget "Beyond PEP-8" av Raymond Hettinger från PyCon 2015 — rekommenderad tittning)). Liknande loopar med förinitialiserade variabler kan allt som oftast skrivas bättre (mer läsbart) som en "list comprehension" i stället.

En sträng i Python är en sekvens (av tecken), och sekvenser är iterabla. enumerate() tar en iterabel och mappar varje element till ett sekventiellt numeriskt index. Testet för de index vi vill "spara" är enkelt: vi vill spara de index som hör till ett tecken som matchar det vi söker. Vi kan skriva det i stort sett som en "one-liner" med just en "list comprehension":

>>> def find_character_indices(haystack, needle): ... return [i for i, character in enumerate(haystack) if character == needle] ... >>> find_character_indices('oslo', 'o') [0, 3]

Byter man [] mot () så får man en generator i stället för en lista, vilket är mer naturligt om man ändå tänkt iterera över indexen i ett senare tillfälle (och väldigt användbart om man vill förbereda sig på gigantiska "haystack"-strängar).

För att hämta exempelvis den andra förekomsten av ett tecken i en sträng så är det bara att fråga efter det andra elementet i den returnerade listan (skrevs funktionen som en generator kan man konvertera generatorn till en lista med list(), men om man bara tänkt använda den som en lista är det ju smidigare att låta funktionen returnera en sådan).

Det går även att lösa detta genom reguljära uttryck, men det är som att skjuta myggor med kanoner .

Länk till Hettinger-föredrag kan vara trevligt.
Visa signatur

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.

Permalänk

@phz: Jag förstod inte riktigt allt men jag förstod din funktion. Har inte hållet på med programmering så länge (jag är 14). Jag hade fått för mig att det fanns en inbyggd funktion som gör detta och tyckte det kändes onödigt att skriva den själv. Men nu är allt löst, tack

Permalänk
Hedersmedlem
Skrivet av Eskil Winstedt:

@phz: Jag förstod inte riktigt allt men jag förstod din funktion. Har inte hållet på med programmering så länge (jag är 14). Jag hade fått för mig att det fanns en inbyggd funktion som gör detta och tyckte det kändes onödigt att skriva den själv. Men nu är allt löst, tack

Det är fritt fram att fråga gällande saker som var oklara om du undrar över något. Speciellt "list comprehensions" känns rätt magiska första gångerna man ser dem, men de beskriver ofta algoritmer på ett väldigt enkelt sätt, och bör användas där de passar.

Det kan kännas som onödigt mycket "jobb" att läsa in sig på hur en lösning fungerar när den väl gör vad du vill, men just för att det är ett så enkelt exempel så är det extra lärorikt att se till att man har full koll på vad som händer. I mer komplexa exempel så blir det mycket svårare att börja bena i koden.

Jag vet ingen direkt funktion i Pythons standardbibliotek som gör exakt det du frågar efter. Så som jag skrev min funktion så returnerar den en lista, ur vilken man sedan kan välja index enligt "vanlig" listsyntax.

Man skulle alternativt kunna skapa en funktion som tog ett ordningstal som ett extra argument och direkt returnerade ett index, men generellt är det trevligt att försöka skriva Python-kod som använder de mekanismer som språket erbjuder, vilket inkluderar []-konstruktionen för att välja listelement.

Om man letar efter den andra förekomsten av tecknet 'o' i strängen 'oslo' så skulle skillnaden mellan två sådana lösningar vara, om jag först upprepar den jag skrev ovan:

>>> def find_character_indices(haystack, needle): ... return [i for i, character in enumerate(haystack) if character == needle] ... >>> find_character_indices('oslo', 'o')[1] 3

där vi använder listsyntaxen på den lista av träffar som returnerats. Nu den andra varianten:

>>> def find_character_index(haystack, needle, n): ... return [i for i, character in enumerate(haystack) if character == needle][n - 1] ... >>> find_character_index('oslo', 'o', 2) 3

(notera den lilla skillnaden i funktionsnamn) där man alltså ger ordningstalet (2 för "den andra") för den träff man vill få indexet för.

Än enklare så skulle man kunna erbjuda båda alternativen och låta den andra varianten ärva funktion från den första:

>>> def find_character_indices(haystack, needle): ... return [i for i, character in enumerate(haystack) if character == needle] ... >>> def find_character_index(haystack, needle, n): ... return find_character_indices(haystack, needle)[n - 1] ... >>> find_character_index('oslo', 'o', 2) 3

Måhända överdriven omfaktorering här, men det kan ofta vara trevligt att låta varje funktion sköta "så lite som möjligt" på egen hand, så att de alla är fullkomligt självklar att beskriva (och testa!). När man skriver på detta sätt så ser man också tydligt att den andra funktionen, vars enda uppgift är att plocka ut ett listindex, känns rätt blek och att man troligen klarar sig utan den. Koden i den första funktionen känns vettigare att ha i en återanvändbar funktion.

Visa signatur

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.

Permalänk

@phz: Nu förstår jag "oneline" koden bättre. Listor är förvirrande tycker jag, börjar på 0 och det glömmer jag ibland. Men de är riktigt bra när jag får de att funka :).
En bit av ett program jag håller på med ska ta en sträng och sortera ut allt som ligger mellan newLine tecknens ("\n") och lägga de i en lista. De var det jag ville ha funktionen som du skrev till (Den är snyggt skriven ). Så tack för hjälpen, nu har jag lärt mig något nytt.

Permalänk
Hedersmedlem
Skrivet av Eskil Winstedt:

@phz: Nu förstår jag "oneline" koden bättre. Listor är förvirrande tycker jag, börjar på 0 och det glömmer jag ibland. Men de är riktigt bra när jag får de att funka :).
En bit av ett program jag håller på med ska ta en sträng och sortera ut allt som ligger mellan newLine tecknens ("\n") och lägga de i en lista. De var det jag ville ha funktionen som du skrev till (Den är snyggt skriven ). Så tack för hjälpen, nu har jag lärt mig något nytt.

Med den mer specifika beskrivningen så kan du enklare titta på split, eller kanske än mer splitlines:

>>> 'Never gonna give you up\nNever gonna let you down\nNever gonna run around and desert you'.splitlines() ['Never gonna give you up', 'Never gonna let you down', 'Never gonna run around and desert you']

Ifall det du läser "strömmas" från exempelvis en fil så finns det mer direkta sätt att läsa rad för rad. Om vi antar att vi har en fil astley med samma text som teststrängen ovan så kan du antingen läsa in hela filen i en lista där varje rad blir ett element med

>>> with open('astley') as f: ... lines = f.readlines() ... >>> lines ['Never gonna give you up\n', 'Never gonna let you down\n', 'Never gonna run around and desert you\n']

(notera att radbrytningen är en del av varje element; se nedan för ett sätt att strippa den) eller så kan du läsa filen sekventiellt och agera på varje rad efterhand:

>>> with open('astley') as f: ... for line in f: ... print('[RA]: {}'.format(line.rstrip('\n'))) ... [RA]: Never gonna give you up [RA]: Never gonna let you down [RA]: Never gonna run around and desert you

där jag använde rstrip för att rensa bort den avslutande extra radbrytningen för varje rad (beroende på exakt vad man vill göra kanske man vill sköta detta annorlunda).

Visa signatur

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.