Наиболее эффективный способ вычисления уникальности (в%) файла по сравнению с несколькими другими большими файлами - PullRequest
5 голосов
/ 25 января 2011

У меня около 30 файлов по 500 МБ, по одному слову в строке.У меня есть скрипт, который делает это, в псевдо-Bash:

for i in *; do
    echo "" > everythingButI
    for j in *-except-$i; do
        cat $j >> everythingButI
        sort everythingButI | uniq > tmp
        mv tmp everythingButI
    done
    comm $i everythingButI -2 -3 > uniqueInI

    percentUnique=$(wc -l uniqueInI) / $(wc -l $i) * 100
    echo "$i is $percentUnique% Unique"
done

Он вычисляет «уникальность» каждого файла (файлы уже отсортированы и уникальны в каждом файле).

Поэтому, если бы у меня были файлы:

file1    file2   file3
a        b       1
c        c       c
d        e       e
f        g
         h

файл1 был бы уникальным на 75% (потому что 1/4 его строк находится в другом файле), файл2 был бы уникальным на 60%, а файл3 был бы уникальным на 33,33%,Но сделайте 30 файлов по 500 МБ поп-музыки, и для запуска потребуется немного.

Я хотел бы написать скрипт на python, который делает это намного, намного быстрее, но мне интересно, какой алгоритм самый быстрыйдля этого на самом деле будет.(У меня есть только 2 ГБ оперативной памяти на ПК.)

У кого-нибудь есть мнения по поводу алгоритмов, или вы знаете более быстрый способ сделать это?

Ответы [ 3 ]

3 голосов
/ 25 января 2011

РЕДАКТИРОВАТЬ : Поскольку каждый из входов уже внутренне отсортирован и дедуплицирован, для этого на самом деле требуется объединение в n , а также упражнение по созданию хеша в предыдущей версииэтого поста довольно бессмысленно.

Слияние на n пути довольно сложно, если вы не будете осторожны.По сути, это работает примерно так:

  • Чтение в первой строке каждого файла и инициализация его уникального счетчика строк и счетчика общего количества строк равным 0.
  • Выполните это тело цикла:
    • Найдите наименьшее значение среди прочитанных строк.
    • Если это значение отличается от значения в любом из других файлов, увеличьте счетчик уникальных строк этого файла.
    • Для каждого файла, если наименьшее значение равно последнему прочитанному значению, прочитайте следующую строку и увеличьте счетчик общих строк этого файла.Если вы нажмете конец файла, с этим файлом покончено: удалите его из дальнейшего рассмотрения.
  • Зацикливайтесь, пока у вас не останется файлов для рассмотрения.На этом этапе у вас должен быть точный уникальный счетчик строк и счетчик общих строк для каждого файла.Процентные значения - это просто вопрос умножения и деления.

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

1 голос
/ 25 января 2011

Используйте модифицированный N / K-способ сортировки , который обрабатывает весь набор файлов сравнения за проход.Только подсчет и продвижение должны быть сделаны;Сама часть объединения может быть пропущена.

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

Счастливое кодирование.

С помощьюнемного хитрости Я считаю, что это также можно расширить, чтобы указать разницу в процентах между всеми файлами за один проход.Просто нужно отслеживать «запаздывающий» вход и счетчики для каждого набора отношений (мм против 1-м).

Код спойлера, который, кажется, работает для меня на данных, представленных в вопросе..

Конечно, я не проверял это на действительно больших файлах или, вообще, вообще."Это бежало".Определение «уникальный» выше было проще, чем я изначально думал, поэтому некоторые из предыдущих ответов не имеют большого значения.Этот код далек от совершенства.Используйте на свой страх и риск (как от взрыва компьютера, так и от скуки / отвращения, чтобы не провернуть что-то лучше!).Работает на Python 3.1.

import os
import itertools

# see: http://docs.python.org/dev/library/itertools.html#itertools-recipes
# modified for 3.x and eager lists
def partition(pred, iterable):
    t1, t2 = itertools.tee(iterable)
    return list(itertools.filterfalse(pred, t1)), list(filter(pred, t2))

# all files here
base = "C:/code/temp"
names = os.listdir(base)

for n in names:
    print("analyzing {0}".format(n))

# {name => file}
# files are removed from here as they are exhausted
files = dict([n, open(os.path.join(base,n))] for n in names)

# {name => number of shared items in any other list}
shared_counts = {}
# {name => total items this list}
total_counts = {}
for n in names:
    shared_counts[n] = 0
    total_counts[n] = 0

# [name, currentvalue] -- remains mostly sorted and is
# always a very small n so sorting should be lickity-split
vals = []
for n, f in files.items():
    # assumes no files are empty
    vals.append([n, str.strip(f.readline())])
    total_counts[n] += 1

while len(vals):
    vals = sorted(vals, key=lambda x:x[1])
    # if two low values are the same then the value is not-unique
    # adjust the logic based on definition of unique, etc.
    low_value = vals[0][1]
    lows, highs = partition(lambda x: x[1] > low_value, vals)
    if len(lows) > 1:
        for lname, _ in lows:
            shared_counts[lname] += 1
    # all lowest items discarded and refetched
    vals = highs
    for name, _ in lows:
        f = files[name]
        val = f.readline()
        if val != "":
            vals.append([name, str.strip(val)])
            total_counts[name] += 1
        else:
            # close files as we go. eventually we'll
            # dry-up the 'vals' and quit this mess :p
            f.close()
            del files[name]

# and what we want...
for n in names:
    unique = 1 - (shared_counts[n]/total_counts[n])
    print("{0} is {1:.2%} unique!".format(n, unique))

Ретроспективно я уже вижу недостатки!:-) Сортировка vals происходит по устаревшей причине, которая больше не применяется.Практически просто min будет работать нормально (и, вероятно, будет лучше для любого относительно небольшого набора файлов).

0 голосов
/ 27 июня 2011

Вот действительно ужасный псевдо-код, который выполняет n-way слияние

#!/usr/bin/python

import sys, os, commands
from goto import goto, label

def findmin(linesread):
    min = ""
    indexes = []
    for i in range(len(linesread)):
        if linesread[i] != "":
            min = linesread[i]
            indexes.append(i)
            break
    for i in range(indexes[0]+1, len(linesread)):
        if linesread[i] < min and linesread[i] != "":
            min = linesread[i]
            indexes = [i]
        elif linesread[i] == min:
            indexes.append(i)
    return min, indexes

def genUniqueness(path):
    wordlists = []
    linecount = []

    log = open(path + ".fastuniqueness", 'w')

    for root, dirs, files in os.walk(path):
        if root.find(".git") > -1 or root == ".":
            continue
        if root.find("onlyuppercase") > -1:
            continue

        for i in files:
            if i.find('lvl') >= 0 or i.find('trimmed') >= 0:
                wordlists.append( root + "/" + i );
                linecount.append(int(commands.getoutput("cat " + root + "/" + i + " | wc -l")))
                print root + "/" + i


    whandles = []
    linesread = []
    numlines = []
    uniquelines = []
    for w in wordlists:
        whandles.append(open(w, 'r'))
        linesread.append("")
        numlines.append(0)
        uniquelines.append(0)

    count = range(len(whandles))
    for i in count:
        linesread[i] = whandles[i].readline().strip()
        numlines[i] += 1

    while True:
        (min, indexes) = findmin(linesread)
        if len(indexes) == 1:
            uniquelines[indexes[0]] += 1
        for i in indexes:
            linesread[i] = whandles[i].readline().strip()
            numlines[i] += 1
            if linesread[i] == "":
                numlines[i] -= 1
                whandles[i] = 0
                print "Expiring ", wordlists[i]
        if not any(linesread):
            break


    for i in count:
        log.write(wordlists[i] + "," + str(uniquelines[i]) + "," + str(numlines[i]) + "\n")
        print wordlists[i], uniquelines[i], numlines[i]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...