Вернуть случайное слово из списка слов в Python - PullRequest
6 голосов
/ 22 сентября 2009

Я хотел бы получить случайное слово из файла, используя python, но я не верю, что мой следующий метод является лучшим или эффективным. Пожалуйста, помогите.

import fileinput
import _random
file = [line for line in fileinput.input("/etc/dictionaries-common/words")]
rand = _random.Random()
print file[int(rand.random() * len(file))],

Ответы [ 9 ]

17 голосов
/ 22 сентября 2009

Случайный модуль определяет choice (), который делает то, что вы хотите:

import random

words = [line.strip() for line in open('/etc/dictionaries-common/words')]
print(random.choice(words))

Обратите внимание, что это предполагает, что каждое слово находится на отдельной строке в файле. Если файл очень большой или вы часто выполняете эту операцию, вы можете обнаружить, что постоянное перечитывание файла отрицательно влияет на производительность вашего приложения.

9 голосов
/ 22 сентября 2009
>>> import random
>>> random.choice(list(open('/etc/dictionaries-common/words')))
'jaundiced\n'

Это эффективно с точки зрения человека и времени.

Кстати, ваша реализация совпадает с реализацией из stdlib random.py:

 def choice(self, seq):
    """Choose a random element from a non-empty sequence."""
    return seq[int(self.random() * len(seq))]  

Измерение времени выполнения

Мне было интересно, какова относительная производительность представленных решений. linecache - очевидный фаворит. Насколько медленнее однострочный random.choice по сравнению с честным алгоритмом, реализованным в select_random_line()?

# nadia_known_num_lines   9.6e-06 seconds 1.00
# nadia                   0.056 seconds 5843.51
# jfs                     0.062 seconds 1.10
# dcrosta_no_strip        0.091 seconds 1.48
# dcrosta                 0.13 seconds 1.41
# mark_ransom_no_strip    0.66 seconds 5.10
# mark_ransom_choose_from 0.67 seconds 1.02
# mark_ransom             0.69 seconds 1.04

(каждая функция вызывается 10 раз (производительность в кэше)).

Эти результаты показывают, что простое решение (dcrosta) в этом случае быстрее, чем более обдуманное (mark_ransom).

Код, который использовался для сравнения ( в качестве сущности ):

import linecache
import random
from timeit import default_timer


WORDS_FILENAME = "/etc/dictionaries-common/words"


def measure(func):
    measure.func_to_measure.append(func)
    return func
measure.func_to_measure = []


@measure
def dcrosta():
    words = [line.strip() for line in open(WORDS_FILENAME)]
    return random.choice(words)


@measure
def dcrosta_no_strip():
    words = [line for line in open(WORDS_FILENAME)]
    return random.choice(words)


def select_random_line(filename):
    selection = None
    count = 0
    for line in file(filename, "r"):
        if random.randint(0, count) == 0:
            selection = line.strip()
            count = count + 1
    return selection


@measure
def mark_ransom():
    return select_random_line(WORDS_FILENAME)


def select_random_line_no_strip(filename):
    selection = None
    count = 0
    for line in file(filename, "r"):
        if random.randint(0, count) == 0:
            selection = line
            count = count + 1
    return selection


@measure
def mark_ransom_no_strip():
    return select_random_line_no_strip(WORDS_FILENAME)


def choose_from(iterable):
    """Choose a random element from a finite `iterable`.

    If `iterable` is a sequence then use `random.choice()` for efficiency.

    Return tuple (random element, total number of elements)
    """
    selection, i = None, None
    for i, item in enumerate(iterable):
        if random.randint(0, i) == 0:
            selection = item

    return selection, (i+1 if i is not None else 0)


@measure
def mark_ransom_choose_from():
    return choose_from(open(WORDS_FILENAME))


@measure
def nadia():
    global total_num_lines
    total_num_lines = sum(1 for _ in open(WORDS_FILENAME))

    line_number = random.randint(0, total_num_lines)
    return linecache.getline(WORDS_FILENAME, line_number)


@measure
def nadia_known_num_lines():
    line_number = random.randint(0, total_num_lines)
    return linecache.getline(WORDS_FILENAME, line_number)


@measure
def jfs():
    return random.choice(list(open(WORDS_FILENAME)))


def timef(func, number=1000, timer=default_timer):
    """Return number of seconds it takes to execute `func()`."""
    start = timer()
    for _ in range(number):
        func()
    return (timer() - start) / number


def main():
    # measure time
    times = dict((f.__name__, timef(f, number=10))
                 for f in measure.func_to_measure)

    # print from fastest to slowest
    maxname_len = max(map(len, times))
    last = None
    for name in sorted(times, key=times.__getitem__):
        print "%s %4.2g seconds %.2f" % (name.ljust(maxname_len), times[name],
                                         last and times[name] / last or 1)
        last = times[name]


if __name__ == "__main__":
    main()
9 голосов
/ 22 сентября 2009

Другое решение заключается в использовании getline

import linecache
import random
line_number = random.randint(0, total_num_lines)
linecache.getline('/etc/dictionaries-common/words', line_number)

Из документации:

Модуль linecache позволяет получить любая строка из любого файла, в то время как пытаясь оптимизировать внутренне, с использованием кэша, общий случай, когда много строк читаются из одного файла

EDIT: Вы можете рассчитать общее число один раз и сохранить его, поскольку файл словаря вряд ли изменится.

3 голосов
/ 22 сентября 2009

Pythonizing мой ответ от Какой лучший способ вернуть случайную строку в текстовом файле с помощью C? :

import random

def select_random_line(filename):
    selection = None
    count = 0
    for line in file(filename, "r"):
        if random.randint(0, count) == 0:
            selection = line.strip()
        count = count + 1
    return selection

print select_random_line("/etc/dictionaries-common/words")

Редактировать: оригинальная версия моего ответа использовала readlines, которая не работала, как я думал, и была совершенно ненужной. Эта версия будет перебирать файл вместо чтения всего в память и делать это за один проход, что должно сделать его намного более эффективным, чем любой ответ, который я видел до сих пор.

Обобщенная версия

import random

def choose_from(iterable):
    """Choose a random element from a finite `iterable`.

    If `iterable` is a sequence then use `random.choice()` for efficiency.

    Return tuple (random element, total number of elements)
    """
    selection, i = None, None
    for i, item in enumerate(iterable):
        if random.randint(0, i) == 0:
            selection = item

    return selection, (i+1 if i is not None else 0)

Примеры

print choose_from(open("/etc/dictionaries-common/words"))
print choose_from(dict(a=1, b=2))
print choose_from(i for i in range(10) if i % 3 == 0)
print choose_from(i for i in range(10) if i % 11 == 0 and i) # empty
print choose_from([0]) # one element
chunk, n = choose_from(urllib2.urlopen("http://google.com"))
print (chunk[:20], n)

выход

('yeps\n', 98569)
('a', 2)
(6, 4)
(None, 0)
(0, 1)
('window._gjp && _gjp(', 10)
2 голосов
/ 22 сентября 2009

Эта статья может помочь

http://www.bryceboe.com/2009/03/23/random-lines-from-a-file/

1 голос
/ 22 сентября 2009

У меня нет кода для вас, но что касается алгоритма:

  1. Найти размер файла
  2. Произведите случайный поиск с помощью функции seek ()
  3. Найти следующий (или предыдущий) пробел
  4. Возвращает слово, начинающееся после этого пробела
1 голос
/ 22 сентября 2009

Вы можете сделать это без использования fileinput:

import random
data = open("/etc/dictionaries-common/words").readlines()
print random.choice(data)

Я также использовал data вместо file, потому что file - это предопределенный тип в Python.

0 голосов
/ 22 сентября 2009

Есть несколько способов оптимизировать эту проблему. Вы можете оптимизировать скорость или пространство.

Если вы хотите быстрое, но требовательное к памяти решение, прочитайте весь файл с помощью file.readlines (), а затем используйте random.choice ()

Если вы хотите решение с эффективным использованием памяти, сначала проверьте количество строк в файле, несколько раз вызывая somefile.readline (), пока он не вернет "", а затем сгенерируйте случайное число, меньшее количества строк (скажем, n ), вернитесь к началу файла и, наконец, вызовите somefile.readline () n раз. Следующий вызов somefile.readline () вернет нужную случайную строку. Этот подход не тратит память, удерживая «ненужные» строки. Конечно, если вы планируете получать много случайных строк из файла, это будет ужасно неэффективно, и лучше просто сохранить весь файл в памяти, как при первом подходе.

0 голосов
/ 22 сентября 2009

Эффективность и многословность не одно и то же в этом случае. Заманчиво пойти на самый красивый, питонический подход, который делает все в одну или две строки, но для файлового ввода-вывода, придерживайтесь классического fopen-стиля, низкоуровневого взаимодействия, даже если он занимает еще несколько строк кода .

Я мог бы скопировать и вставить некоторый код и заявить, что он мой (другие могут, если захотят), но взгляните на это: http://mail.python.org/pipermail/tutor/2007-July/055635.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...