2 votes

J'essaie de définir des plages dans un fichier texte afin de pouvoir associer les résultats de la recherche à un chapitre particulier.

Je sais qu'il existe des approches plus réalisables pour résoudre ce problème (db : mysql, oracle, etc...), et j'ai un fichier db mysql (Bible KJV), que je peux rechercher via le code PHP. Cependant, je veux ouvrir le fichier Bible.txt en Python et rechercher certaines chaînes de caractères et retourner la ligne et le numéro de ligne. En outre, (le défi pour moi) je veux également retourner le livre dans lequel la ligne a été trouvée (à partir d'un fichier plat). J'ai lu et essayé de me familiariser avec Python. Malheureusement, je n'ai toujours pas les connaissances et les compétences nécessaires pour résoudre efficacement les problèmes. Voici ce que j'ai trouvé : J'ai pensé que si j'utilisais la méthode range pour définir le début et la fin d'un chapitre (représentant les numéros de ligne), je pourrais coder en dur un nom pour chaque livre/chapitre (par exemple range(38, 4805) toutes les lignes comprises dans ce range sont Genesis). Cela semble fonctionner ; je n'ai essayé que pour quelques livres. Mais le code est très verbeux (déclarations elif). Quelqu'un connaît-il une approche plus efficace ? Voici un exemple de code que j'ai écrit pour essayer quelques livres, et le fichier KJV.txt peut être obtenu du Projet Gutenberg .

 import os
 import sys
 import re

 word_search = raw_input(r'Enter a word to search: ')
 book = open("KJV.txt", "r")
 regex = re.compile(word_search)
 bibook = ''

 for i, line in enumerate(book.readlines()):
     result = regex.search(line)
     ln = i
     if result:
         if ln in range(36, 4809):
            bibook = 'Genesis'
         elif ln in range(4812, 8859):
            bibook = 'Exodus'
         elif ln in range(8867, 11741):
            bibook =  'Leviticus'
         elif ln in range(11749, 15713):
            bibook = 'Numbers'

         template = "\nLine: {0}\nString: {1}\nBook: {2}\n"
         output = template.format(ln, result.group(), bibook)
         print output

2voto

senderle Points 41607

C'est un début très solide. J'ai quelques suggestions, cependant.

Premièrement, votre utilisation de readlines est un peu inefficace. readlines crée une nouvelle liste de lignes à partir du fichier -- elle stocke le fichier entier en mémoire. Mais vous n'êtes pas obligé de faire cela ; si tout ce que vous voulez faire est d'itérer sur les lignes d'un fichier, vous pouvez simplement dire for line in file ou dans votre cas :

for i, line in enumerate(book):

Sinon, si vous souhaitez vraiment conserver le fichier en mémoire, peut-être pour des recherches répétées, enregistrez le résultat de la commande readlines à une variable :

booklines = book.readlines()
for i, line in enumerate(booklines):

Vous pouvez également stocker le texte comme une chaîne unique avec read mais cela n'est pas très utile dans ce cas, car il faudrait encore le diviser :

booktxt = book.read()
booklines = book.splitlines() #
for i, line in enumerate(booklines)

Deuxièmement, je dirais que plutôt que d'utiliser i comme variable d'index, puis l'enregistrer séparément dans le fichier ln Si vous avez besoin d'un nom de variable, utilisez simplement un nom de variable significatif dès le départ. ln est bien, line_number est plus clair mais verbeux, lineno est un bon compromis. Tenons-nous en à ln ici puisque nous savons tous ce que cela signifie.

for ln, line in enumerate(book):

Troisièmement, comme utdemir l'a souligné dans les commentaires, vous n'avez pas vraiment besoin de regex pour cela. Il est possible que cela ait un sens si vous voulez que votre utilisateur puisse entrer des recherches plus sophistiquées, mais les RE sont suffisamment compliqués pour faire une ui par défaut discutable. J'utiliserais simplement in pour une simple correspondance de sous-chaîne, comme dans :

    if word_search in line: 

Les autres instructions if sont correctes, et dans certains cas, c'est la meilleure chose à faire. Cependant, souvent, dans des situations qui nécessiteraient (disons) case les déclarations, il est en fait préférable d'utiliser un dictionnaire. Bien sûr, ici vous avez des plages, donc nous devons être un peu plus intelligents.

Commençons par un dictionnaire des pages de démarrage. Comme cela est probablement évident, il doit précéder la boucle afin que nous ne redéfinissions pas le dictionnaire à chaque fois.

first_lines = {36: 'Genesis', 4812: 'Exodus', 8867: 'Leviticus', 11749: 'Numbers'}

Maintenant, nous devons établir une carte ln à l'une de ces valeurs de dictionnaire. Mais il y a de fortes chances que ln n'est pas égal à l'un des nombres ci-dessus, et nous ne pouvons donc pas l'introduire directement dans le dictionnaire. Nous avons pourrait utiliser un for boucle pour itérer sur les clés du dictionnaire ( for key in first_lines ), stocker la clé précédente dans prev_key tester si ln > key et, si c'est le cas, renvoyer prev_key . Mais il existe en fait une façon beaucoup plus agréable de le faire en Python. Au lieu d'écrire une boucle normale, nous filtre la liste, en utilisant soit la fonction intégrée filter ou une compréhension de liste pour supprimer de la liste les valeurs qui sont plus grandes que ln . Ensuite, nous trouvons le max .

first_line = max(filter(lambda l: l < ln, first_lines))

Ici first_lines agit comme une liste non ordonnée de ses clés ; en général, vous pouvez itérer sur les clés d'un dictionnaire comme vous le feriez pour une liste, avec la réserve que les clés peuvent être dans n'importe quel ordre. lambda est un moyen de définir une fonction courte : cette fonction prend x comme argument et renvoie le résultat de x < ln . Nous devons le faire de cette façon parce que filter veut une fonction comme premier argument. Elle retourne une liste contenant toutes les valeurs de first_lines qui donnent un True résultat.

Comme cela peut être un peu difficile à lire, surtout lorsque lambda est impliquée, il est probablement préférable d'utiliser une liste de compréhension ici. Les compréhensions de listes sont très lisibles et intuitives pour la plupart des gens.

first_line = max([l for l in first_lines if l < ln])

Nous pouvons même laisser de côté les parenthèses dans ce cas, puisque nous le passons directement à une fonction. Python interprète cela comme une "expression génératrice", qui ressemble à une compréhension de liste mais qui calcule les valeurs à la volée, au lieu de les stocker dans une liste au préalable.

first_line = max(l for l in first_lines if l < ln)

Maintenant, pour obtenir le nom du livre, tout ce que vous avez à faire est d'utiliser first_line comme une clé :

bibook = first_lines[first_line]

Le résultat final :

import os
import sys
import re

word_search = raw_input(r'Enter a word to search: ')
book = open("KJV.txt", "r")
first_lines = {36: 'Genesis', 4812: 'Exodus', 8867: 'Leviticus', 11749: 'Numbers'}

for ln, line in enumerate(book):
    if word_search in line:
        first_line = max(l for l in first_lines if l < ln)
        bibook = first_lines[first_line]

        template = "\nLine: {0}\nString: {1}\nBook: {2}\n"
        output = template.format(ln, word_search, bibook)
        print output

1voto

utdemir Points 9107

Juste une version légèrement modifiée de votre code.

word_search = raw_input(r'Enter a word to search: ')

with open("KJV.txt", "r") as book:
    #using with is always better when messing with files.
    bibook = ''
    for pos, line in enumerate(book):
    #a file object is already an iterable, so i don't think we need readlines.
        if result in line:
        #if result is always in ranges in your question, no need to check other limits.
        #also comparision operators is a lot faster than in.
            if pos < 4809:
                bibook = 'Genesis'
            elif pos < 8859:
                bibook = 'Exodus'
            elif pos < 11741:
                bibook = 'Leviticus'
            else:
                bibook = 'Numbers'
            #you can use string templates, but i think no need for that
            out = "\nLine: {0}\nString: {1}\nBook: {2}".format(
                                            pos, line, book)

            print(out)

Edit :

Maintenant je lis votre fichier d'exemple. Je pense que séparer la première partie "1:2" et l'utiliser pour apprendre le livre et le numéro de ligne serait une meilleure option.

1voto

Whatang Points 1570

Vous pourriez essayer quelque chose comme ça. Notez que les livres apparaissent l'un après l'autre, il vous suffit donc de noter quel est le livre que vous êtes en train de regarder. De plus, votre approche consistant à vérifier si le numéro de ligne se trouve dans un fichier de type range est très coûteux, car pour chaque ligne du fichier texte, vous construisez chaque plage, puis vous effectuez un balayage linéaire pour voir si le numéro de ligne y figure.

books = [("Introduction",36),("Genesis",4809),("Exodus",8859),
         ("Leviticus",11741),("Numbers",15713)]

import os
import sys
import re

word_search = raw_input(r'Enter a word to search: ')
book = open("KJV.txt", "r")
bookIndex = 0
bookEnd = books[bookIndex][1]

for lineNum, line in enumerate(book):
    if lineNum > bookEnd:
        bookIndex += 1
        bookEnd = books[bookIndex][1]
    if word_search in line:
        template = "\nLine: {0}\nString: {1}\nBook: {2}\n"
        output = template.format(lineNum, line, books[bookIndex][0])
        print output

L'un des commentaires a souligné que vous pourriez adopter une approche plus axée sur les données, plutôt que de coder en dur les positions des livres. Chaque livre commence-t-il par une ou plusieurs lignes dans un format reconnaissable ? Si c'est le cas, vous pourriez essayer de le vérifier et d'enregistrer quel est le livre actuel que vous regardez.

1voto

Dan D. Points 17448
     if ln in range(36, 4809):
        bibook = 'Genesis'
     elif ln in range(4812, 8859):
        bibook = 'Exodus'
     elif ln in range(8867, 11741):
        bibook =  'Leviticus'
     elif ln in range(11749, 15713):
        bibook = 'Numbers'

est mieux écrit comme :

#      (start, end, book)
tab = [(36, 4809, 'Genesis'), 
       (4812, 8859, 'Exodus'),
       (8867, 11741, 'Leviticus'),
       (11749, 15713, 'Numbers')]
for start, end, book in tab:
    if start <= ln < end:
        bibook = book
        break

1voto

Jochen Ritzel Points 42916

Un moyen simple d'éviter le elifs est une boucle. Il est également beaucoup plus efficace de tester si un nombre est dans l'intervalle avec start <= ln < stop au lieu d'utiliser - range retournent une liste et Python doit comparer chaque élément.

import os
import sys
import re

word_search = raw_input(r'Enter a word to search: ')
book = open("KJV.txt", "r")
regex = re.compile(word_search)
bibook = ''

bookranges = [
    ((36, 4809),  'Genesis'),
    ((4812, 8859), 'Exodus'),
    ((8867, 11741), 'Leviticus'),
    ((11749, 15713), 'Numbers')
]

for ln, line in enumerate(book.readlines()):
    result = regex.search(line)
    if result:
        for (start, stop), bibook in bookranges:
            if start <= ln <= stop:
                # found the book, so end the loop and use it later
                break
        else:
            # didnt find any range that matches.
            bibook = 'Somewhere between books'

     template = "\nLine: {0}\nString: {1}\nBook: {2}\n"
     output = template.format(ln, result.group(), bibook)
     print output

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X