Определите непересекающиеся местоположения с помощью Perl - PullRequest
2 голосов
/ 07 января 2011

У меня есть коллекция местоположений - вот пример структуры данных.

my $locations =
{
  loc_1 =>
  {
    start => 1,
    end   => 193,
  },
  loc_2 =>
  {
    start => 180,
    end   => 407,
  },
  loc_3 =>
  {
    start => 329,
    end   => 684,
  },
  loc_4 =>
  {
    start => 651,
    end   => 720,
  },
};

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

my $non_overlapping_locations =
[
  {
    loc_1 =>
    {
      start => 1,
      end   => 193,
    },
    loc_3 =>
    {
      start => 329,
      end   => 684,
    },
  },
  {
    loc_1 =>
    {
      start => 1,
      end   => 193,
    },
    loc_4 =>
    {
      start => 651,
      end   => 720,
    },
  },
  {
    loc_2 =>
    {
      start => 180,
      end   => 407,
    },
    loc_4 =>
    {
      start => 651,
      end   => 720,
    },
  }
];

Обновление : ответ ysth помог мне увидеть изъян в моемформулировка.Я думаю, меня не интересует // каждая возможная // комбинация неперекрывающихся местоположений, меня интересуют только решения, которые не являются подмножествами других решений.

Ответы [ 4 ]

1 голос
/ 07 января 2011
use strict;
use warnings;

my $locations =
{
  loc_1 =>
  {
    start => 1,
    end   => 193,
  },
  loc_2 =>
  {
    start => 180,
    end   => 407,
  },
  loc_3 =>
  {
    start => 329,
    end   => 684,
  },
  loc_4 =>
  {
    start => 651,
    end   => 720,
  },
};
my $non_overlapping_locations = [];
my @locations = sort keys %$locations;

get_location_combinations( $locations, $non_overlapping_locations, [], @locations );

use Data::Dumper;
print Data::Dumper::Dumper($non_overlapping_locations);

sub get_location_combinations {
    my ($locations, $results, $current, @remaining) = @_;

    if ( ! @remaining ) {
        if ( not_a_subset_combination( $results, $current ) ) {
            push @$results, $current;
        }
    }
    else {
        my $next = shift @remaining;
        if (can_add_location( $locations, $current, $next )) {
            get_location_combinations( $locations, $results, [ @$current, $next ], @remaining );
        }
        get_location_combinations( $locations, $results, [ @$current ], @remaining );
    }
}

sub can_add_location {
    my ($locations, $current, $candidate) = @_;

    # not clear if == is an overlap; modify to use >=  and <= if so.
    0 == grep $locations->{$candidate}{end} > $locations->{$_}{start} && $locations->{$candidate}{start} < $locations->{$_}{end}, @$current;
}

sub not_a_subset_combination {
    my ($combinations, $candidate) = @_;

    for my $existing (@$combinations) {
        my %candidate;
        @candidate{@$candidate} = ();
        delete @candidate{@$existing};
        if ( 0 == keys %candidate ) {
            return 0;
        }
    }
    return 1;
}

Относительно простой оптимизацией будет сортировка @locations по началу, а затем по окончанию, предварительному вычислению и сохранению в хэше (или просто в $ местоположениях -> {foo}) для каждого местоположения, сколько из следующих расположений конфликтует с это место. Затем в случае can_add ... срежьте это число от @remaining перед повторением.

Или предварительно рассчитайте для каждого местоположения хэш всех следующих местоположений, которые конфликтуют, и удалите их все с помощью grep перед повторением. (Хотя при таком подходе сохранение хэша начинает приобретать больше смысла.)

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

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

Сначала я собрал бы все отдельные точки (начало и конец каждой локации), отсортировал их и сохранил в списке. В вашем случае это будет:

1,180,193,329,407,651,684,720. 

Для каждого интервала в этом списке выясните, сколько сегментов перекрывают его. В вашем случае это будет:

1, 180 -> 1
180, 193 -> 2
193, 329 -> 1
329, 407 -> 2
407, 651 -> 1
651, 684 -> 2
684, 720 -> 1

и зацикливать, у каких сегментов больше 1 (в данном случае их 3). Таким образом, общее количество случаев составляет 2 x 2 x 2 = 8 решений (вы можете выбрать только один сегмент обработки нескольких интервалов в решении).

мы нашли 2, 2, 2 (или 2, 3, 4). Держите их в массиве и начинайте с последнего. Уменьшайте его, пока не достигнете 0. Когда вы достигнете 1, уменьшите предыдущее число и установите для первого числа начальное значение минус 1.

Предположим, что мы пронумеровали начальные сегменты: (в данном случае 1,2,3,4,5,6). Перекрывающиеся сегменты будут иметь следующие сегменты [1,2], [2,3], [3,4]. Итак, у нас есть 3 перекрывающихся сегмента. Теперь мы начинаем рекурсивный процесс выбора / исключения: На каждом этапе мы смотрим на перекрывающийся сегмент, который имеет несколько сегментов. Мы повторяем выбор и для каждого выбора делаем две вещи: исключаем из каждого последующего перекрывающегося сегмента сегменты, которые мы не выбрали сейчас, и устанавливаем текущий выбор сегмента в каждом последующем перекрывающемся сегменте, у которого этот выбор является возможным. Каждый сегмент, который становится неперекрывающимся, будет рассматриваться как новый выбор. Ищите следующий множественный выбор и повторяйте. Как только мы не можем найти выбор, у нас есть частичное решение. Нам нужно добавить к нему сегменты, которые не участвуют ни в каком перекрытии. Распечатай это.

В этом случае это будет выглядеть так: Первый шаг:

we are here [1,2], [2,3], [3,4]:
  chose 1 -> // eliminate 2 from rest and force 1 (3 is a single choice so we do the same)
      [1], [3], [3] -> [1, 3] solution 
  chose 2 -> // eliminate 1 from the rest and force 2 (2 single choice so we do the same). 
      [2], [2], [4] -> [2, 4] solution

Это должно работать правильно.

Теперь код, реализующий это (это не самый красивый Perl-код, который я предполагаю, но на самом деле я не Perl-парень):

#!/bin/perl

use strict;
use warnings;
use 5.010;
use Data::Dumper;

my $locs = {
  loc_1 => {
    start => 1,
    end   => 193,
  },
  loc_2 => {
    start => 180,
    end   => 407,
  },
  loc_3 => {
    start => 329,
    end   => 684,
  },
  loc_4 => {
            start => 651,
    end   => 720,
  }
};

my (%starts, %ends);
map {
        my ($start, $end) = ($locs->{$_}->{start}, $locs->{$_}->{end});

        push @{ $starts{$start} }, $_;
        push @{ $ends{$end} }, $_;
} keys %$locs;

my @overlaps, my %tmp;

map {
        map { $tmp{$_} = 1 } @{$starts{$_}};
        map { delete $tmp{$_} } @{$ends{$_}};

        my @segs = keys %tmp;
        push @overlaps, \@segs if 1 < @segs
} sort (keys %starts, keys %ends);

sub parse_non_overlapping {
  my ($array,$pos)=($_[0], $_[1]);

  my @node = @{$array->[$pos]};
  foreach my $value ( @node ) {

    my @work = map { [@$_] } @$array;
    $work[$pos] = [ $value ];

    my ($removed, $forced) = ( {}, {$value => 1});
    map { $removed->{$_} = 1 if $_ ne $value } @node;

    my ($i, $new_pos) = (0, -1);
    for ( $i = $pos + 1; $i <= $#work; $i++ ) {
        $_ = $work[$i];

        #apply map
        @$_ = grep { not defined($removed->{$_}) } @$_;
        if ( $#$_ == 0 ) { $forced->{@$_[0]} = 1 }

        #apply force
            my @tmp = grep { defined $forced->{$_} } @$_;
        if ( $#tmp == 0 ) {
             map { $removed->{$_} = 1 if $tmp[0] ne $_ } @$_;
             @$_ = @tmp;
        }

        if ( $#$_ > 0 && $new_pos == -1 ) {
                $new_pos = $i;
        }

        $work[$i] = $_;
    }

    if ( $new_pos != -1 ) {
      parse_non_overlapping(\@work, $new_pos);
    } else {
      print Dumper \@work
       # @work has the partial solution minux completely non overlapping segments.
    }
  }
}    

parse_non_overlapping(\@overlaps, 0);
1 голос
/ 07 января 2011

Я не специалист по CS, поэтому я не знаком со всеми лучшими алгоритмами, но мне интересно, есть ли лучший подход, чем:

my @location_keys = keys %{$locations};
while (my $key_for_checking = (shift @location_keys) {
    foreach my $key_to_compare (@location_keys) {
        if ( do_not_overlap($locations->{$key_for_checking}, 
                            $locations->{$key_to_compare} ) {
            add_to_output($key_for_checking, $key_to_compare);
        }
    }
}

С do_not_overlap и add_to_output подходит определено.

Если вам интересно проверить совпадение ... это довольно просто.A и B не перекрываются, если:

( (A->start < B->start) && (A->end < B->start) ) ||
( (A->start > B->end)   && (A->end > B->end) )

Возможно, потребуется настроить в зависимости от того, составляет ли общая граница перекрытие.Кроме того, вы можете упростить это, если знаете, отсортированы ли A и B каким-либо образом (либо по началу, либо по концу)

0 голосов
/ 07 января 2011

(Нарушается реальная жизнь - извинения, я напишу объяснение - и возьму эти пустые массивы, хотя это довольно тривиально - позже!)

#! /usr/bin/perl
use strict;
use warnings;
use 5.010;
use List::MoreUtils qw(any);
use Data::Dumper;

my $locations = {
    loc_1 => {
        start => 1,
        end   => 193,
    },
    loc_2 => {
        start => 180,
        end   => 407,
    },
    loc_3 => {
        start => 329,
        end   => 684,
    },
    loc_4 => {
        start => 651,
        end   => 720,
    },
};

my @keys = keys %$locations;

my %final;

for my $key (@keys) {
    push @{ $final{$key} }, map {
        if (   $locations->{$key}->{start} >= $locations->{$_}->{start}
            && $locations->{$key}->{start} <= $locations->{$_}->{end}
            or $locations->{$key}->{end} >= $locations->{$_}->{start}
            && $locations->{$key}->{end} <= $locations->{$_}->{end} )
        {
            ();
        }
        else {
            my $return = [ sort $key, $_ ];
            if ( any { $return ~~ $_ } @{ $final{$_} }, @{ $final{$key} } ) {
                ();
            }
            else { $return; }
        }
    } grep { $_ ne $key } keys %$locations;
}

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