2013-08-07 2 views
0

Возвращаясь за советом. Я написал perl-скрипт, который подсчитывает количество обращений определенных чисел в определенные пользователем ячейки. Например, это мой файл данных:Perl hit counter для нескольких диапазонов, варианты на других языках?

12 
14 
15 
20 
21 

И я хочу знать, сколько хитов у меня есть в следующих диапазонах:

1-19 
20-29 
30-39 

Так результаты будут как

1-19 3 
20-29 2 
30-39 0 

Я сделал такую ​​вещь, кустик, сохраняя мои данные в хэш (datahash), затем сохраняя мои диапазоны в другой хеш (rangehash), а затем в основном просматривая все точки данных в datahash и проверяя, что значение попадает в диапазон s диапазона.

Проблема в том, что для каждого datapoint в datahash я просматриваю все значения диапазона и выхожу, как только я нахожу диапазон, в который падает датапоток. Это полезно для нескольких точек данных, но теперь у меня есть файлы с не менее чем 2 миллионами данных и 50 000 диапазонов, поэтому перебирать все это просто нужно навсегда.

Мне было интересно, если бы у кого-то было бы лучшее решение, а не просто зацикливание всего этого. Предложения для других языков хорошо приняты !!!

Беста,

Шакть

+0

Crossposted at http: // www.perlmonks.org/?node_id=1048441. – choroba

ответ

3

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

use strict; 
use warnings; 
use List::Util 'sum'; 

my %nums; 
while (<DATA>) { 
    s/\D+//g;  # remove junk 
    $nums{$_}++; # count number 
} 
my $low = 1; 
for my $high (qw(19 29 39)) { 
    my $sum = sum(0,      # to avoid undef return value 
        grep defined,   # avoid uninitialized warnings 
        @nums{$low .. $high}); # hash slice for our range 
    print "$low - $high : $sum\n"; 
    $low = $high + 1;      # set new low range 
} 

__DATA__ 
12 
14 
15 
20 
21 

Выход:

1 - 19 : 3 
20 - 29 : 2 
30 - 39 : 0 
+0

Спасибо TLP! Проверите это и проверьте, как это уменьшает время вычислений, спасибо! – Sakti

5

Следующая будет супер быстрый, хотя и предполагает ноль не произойдет:

my @buckets = (0) x 4; 
++$buckets[ $_/10 ] while <>: 
print " 1-19: ".($buckets[0] + $buckets[1])."\n"; 
print "20-29: $buckets[2]\n"; 
print "30-39: $buckets[3]\n"; 

более общее решение может быть на самом деле быстрее :

use List::Util qw(sum); 
++$counts[$_] while <>: 
print " 1-19: ".(sum 0, @counts[ 1..19])."\n"; 
print "20-29: ".(sum 0, @counts[20..29])."\n"; 
print "30-39: ".(sum 0, @counts[30..39])."\n"; 
+0

Инициализация массива избыточна. '++' автоматически переведет undef в 0. Кроме того, 'map 0, 0..3' aka' (0) x 4'. Вероятно, вы также не хотели использовать '%', но '/'. – TLP

+0

@TLP. Это не избыточно, поскольку оно предотвращает попытки печати undef. – ikegami

+0

@TLP, Исправлен неправильный оператор. – ikegami

0

Это относится только к определенным пользователем ячейкам, то есть к единицам, которые не могут быть легко рассчитаны как int($x/100)*100 или тому подобное.

В последнее время здесь был аналогичный вопрос или на perlmonks (который я не могу найти легко), и лучший ответ IMO был «сортировкой бункеров», а затем использовал двоичный поиск ».

Для 50K бункеров это примерно 16 if s за точку данных, которая, вероятно, хорошо (конечно, не «навсегда»).

В зависимости от данных может быть использовано некоторое кэширование для достижения дополнительной скорости. Например. можно округлить данные до 1/1000 ожидаемого интервала (последний бит - 1-й бит) и только проверять ячейки, которые покрывают эту часть. (Я только что сделал это, но это может сработать. Или нет.).