Как расширить итератор бинарного поиска, чтобы потреблять несколько целей
-
20-09-2019 - |
Вопрос
У меня есть функция, binary_range_search
, это называется так:
my $brs_iterator = binary_range_search(
target => $range, # eg. [1, 200]
search => $ranges # eg. [ {start => 1, end => 1000},
); # {start => 500, end => 1500} ]
brs_iterator->()
Будет ли вытекать на все диапазоны @$, на которых $ range совпадает.
Я хотел бы продлить binary_range_search
Чтобы иметь возможность назвать это с несколькими диапазонами в качестве цели, например:
target => $target_ranges # eg. [ [1, 200], [50, 300], ... ]
search => $search_ranges # as above
Таким образом, когда поиск в $ range-> [0] исчерпан, он должен перейти к $ range-> [1] и т. Д. Вот рассматриваемая функция, в его исходном виде:
sub binary_range_search {
my %options = @_;
my $range = $options{target} || return;
my $ranges = $options{search} || return;
my ( $low, $high ) = ( 0, @{$ranges} - 1 );
while ( $low <= $high ) {
my $try = int( ( $low + $high ) / 2 );
$low = $try + 1, next if $ranges->[$try]{end} < $range->[0];
$high = $try - 1, next if $ranges->[$try]{start} > $range->[1];
my ( $down, $up ) = ($try) x 2;
my %seen = ();
my $brs_iterator = sub {
if ( $ranges->[ $up + 1 ]{end} >= $range->[0]
and $ranges->[ $up + 1 ]{start} <= $range->[1]
and !exists $seen{ $up + 1 } )
{
$seen{ $up + 1 } = undef;
return $ranges->[ ++$up ];
}
elsif ( $ranges->[ $down - 1 ]{end} >= $range->[0]
and $ranges->[ $down + 1 ]{start} <= $range->[1]
and !exists $seen{ $down - 1 }
and $down > 0 )
{
$seen{ $down - 1 } = undef;
return $ranges->[ --$down ];
}
elsif ( !exists $seen{$try} ) {
$seen{$try} = undef;
return $ranges->[$try];
}
else {
return;
}
};
return $brs_iterator;
}
return sub { };
}
Это стандартная стратегия бинарного поиска, пока она не найдет перекрывающийся диапазон. Затем он движется справа, исчерпает его, движется слева, исчерпает его и, наконец, сдается. В идеале это должно быть тогда, может быть shift
Я полагаю, что следующий целевой диапазон и повторно поиск (возможно, через рекурсию?). Моя проблема в том, что я не уверен, как заставить это работать со конструкцией итератора.
Решение
Я только что завернул ваше генератор итератора в петлю и создал множество функций итератора.
В зависимости от контекста я либо возвращаю мастер -итератор, либо список функций итератора. Я не был уверен, чего вы хотели.
use strict;
use warnings;
my $t = [ [1,200], [400,900] ];
my @r = (
{ start => 1, end => 100 },
{ start => 2, end => 500 },
{ start => 204, end => 500 },
{ start => 208, end => 500 },
{ start => 215, end => 1000 },
{ start => 150, end => 1000 },
{ start => 500, end => 1100 },
);
# Get a master iterator that will process each iterator in turn.
my $brs_iterator = binary_range_search(
targets => $t,
search => \@r,
);
# Get an array of iterators
my @brs_iterator = binary_range_search(
targets => $t,
search => \@r,
);
sub binary_range_search {
my %options = @_;
my $targets = $options{targets} || return;
my $ranges = $options{search} || return;
my @iterators;
TARGET:
for my $target ( @$targets ) {
my ( $low, $high ) = ( 0, $#{$ranges} );
RANGE_CHECK:
while ( $low <= $high ) {
my $try = int( ( $low + $high ) / 2 );
# Remove non-overlapping ranges
$low = $try + 1, next RANGE_CHECK
if $ranges->[$try]{end} < $target->[0];
$high = $try - 1, next RANGE_CHECK
if $ranges->[$try]{start} > $target->[1];
my ( $down, $up ) = ($try) x 2;
my %seen = ();
my $brs_iterator = sub {
if ( exists $ranges->[$up + 1]
and $ranges->[ $up + 1 ]{end} >= $target->[0]
and $ranges->[ $up + 1 ]{start} <= $target->[1]
and !exists $seen{ $up + 1 } )
{
$seen{ $up + 1 } = undef;
return $ranges->[ ++$up ];
}
elsif ( $ranges->[ $down - 1 ]{end} >= $target->[0]
and $ranges->[ $down + 1 ]{start} <= $target->[1]
and !exists $seen{ $down - 1 }
and $down > 0 )
{
$seen{ $down - 1 } = undef;
return $ranges->[ --$down ];
}
elsif ( !exists $seen{$try} ) {
$seen{$try} = undef;
return $ranges->[$try];
}
else {
return;
}
};
push @iterators, $brs_iterator;
next TARGET;
}
}
# In scalar context return master iterator that iterates over the list of range iterators.
# In list context returns a list of range iterators.
return wantarray
? @iterators
: sub {
while( @iterators ) {
if( my $range = $iterators[0]() ) {
return $range;
}
shift @iterators;
}
return;
};
}
Другие советы
Если вы хотите итерацию по всем значениям, которые перекрывают диапазоны поиска, вам не нужен бинарный поиск.
Сначала обычный фронт:
use warnings;
use strict;
use Carp;
Во -первых, проверьте, что у нас есть target
а также search
Параметры, и для каждого диапазона отправная точка не превышает его точку окончания. В противном случае мы отказываемся продолжать.
sub binary_range_search {
my %arg = @_;
my @errors;
my $target = $arg{target} || push @errors => "no target";
my $search = $arg{search} || push @errors => "no search";
for (@$target) {
my($start,$end) = @$_;
push @errors => "Target start ($start) is greater than end ($end)"
if $start > $end;
}
for (@$search) {
my($start,$end) = @{$_}{qw/ start end /};
push @errors => "Search start ($start) is greater than end ($end)"
if $start > $end;
}
croak "Invalid use of binary_range_search:\n",
map " - $_\n", @errors
if @errors;
Сам итератор - это закрытие, которое поддерживает следующее состояние:
my $i;
my($ta,$tb);
my($sa,$sb);
my $si = 0;
куда
$i
Если определено следующее значение из текущего диапазона перекрытия$ta
а также$tb
Являются ли начальные и окончательные точки для текущего целевого диапазона$sa
а также$sb
похожи на вышеизложенное, но для текущего диапазона поиска$si
является указателем в@$search
и определяет текущий диапазон поиска
Мы будем назначать и вернуть итератор $it
. Анкет Объявление и инициализация являются отдельными, поэтому итератор может вызвать себя при необходимости.
my $it;
$it = sub {
Мы закончили, если больше не осталось целевых диапазонов или если не было диапазонов поиска с самого начала.
return unless @$target && @$search;
Когда $i
определено, это означает, что мы нашли перекрытие и итерацию путем увеличения $i
до тех пор, пока он не будет больше конечной точки либо текущего целевого диапазона, либо текущего диапазона поиска.
if (defined $i) {
# iterating within a target range
if ($i > $tb || $i > $sb) {
++$si;
undef $i;
return $it->();
}
else {
return $i++;
}
}
В противном случае нам нужно определить, перекрывает ли следующий целевой диапазон любой диапазон поиска. Однако, если $i
не определен, и мы уже рассмотрели все диапазоны поиска, мы отбрасываем текущий целевой диапазон и начинаем снова.
else {
# does the next target range overlap?
if ($si >= @$search) {
shift @$target;
$si = 0;
return $it->();
}
Здесь мы вынимаем начальные и конечные точки обоих текущих целевых диапазонов (всегда в передней части @$target
) и текущий диапазон поиска (индексируемый $si
).
($ta,$tb) = @{ $target->[0] };
($sa,$sb) = @{ $search->[$si] }{qw/ start end /};
Теперь тестирование на перекрытие простое. Для диапазонов поиска в борьбе с ними мы игнорируем и движемся дальше. В противном случае мы находим самую левую точку в перекрытии и итерации оттуда.
if ($sb < $ta || $sa > $tb) {
# disjoint
++$si;
undef $i;
return $it->();
}
elsif ($sa >= $ta) {
$i = $sa;
return $i++;
}
elsif ($ta >= $sa) {
$i = $ta;
return $i++;
}
}
};
Наконец, мы возвращаем итератор:
$it;
}
Для примера, аналогичного тому в вашем вопросе
my $it = binary_range_search(
target => [ [1, 200], [50, 300] ],
search => [ { start => 1, end => 1000 },
{ start => 500, end => 1500 },
{ start => 40, end => 60 },
{ start => 250, end => 260 } ],
);
while (defined(my $value = $it->())) {
print "got $value\n";
}
Его вывод с внутренними точками Elided
got 1 [...] got 200 got 40 [...] got 60 got 50 [...] got 300 got 50 [...] got 60 got 250 [...] got 260
Разделите его на две функции, внешнюю функцию, которая переходит через диапазоны и вызывает внутреннюю функцию, которая реализует обычную бинарную отбивку.
Предупреждение: очень предвзятый C ++ Ответ:
Что вам нужно сделать, так это определить новый тип итератора, который является парой обычного итератора, и иерратор Segmemt (если у вас нет итератора сегмента, это пара константного указателя / REF на сегменты, и индекс, указывающий на правильный сегмент). Вы должны определить все понятия итератора случайного доступа (разница, добавление целого числа и т. Д.). Имейте в виду, что, по крайней мере, в Lingo C ++ это не настоящий случайный итератор, поскольку добавление целого числа на самом деле не является постоянным временем; такова жизнь.