Чередование редких упорядоченных массивов

голоса
7

У меня есть набор списков событий. События всегда происходят в определенном порядке, но не каждое событие происходит всегда. Вот пример ввода:

[[ do, re, fa, ti ],
 [ do, re, mi ],
 [ do, la, ti, za ],
 [ mi, fa ],
 [ re, so, za ]]

Входные значения не имеют какой-либо определенный порядок. Они на самом деле сообщения, как «создание символических ссылок» и «переиндексации поиска». Они сортируются в индивидуальном списке, но нет никакого способа, чтобы смотреть только на «фа» в первом списке и «ми» во втором и определить, предшествует другой.

Я хотел бы быть в состоянии принять , что ввод и генерировать отсортированный список всех событий:

[ do, re, mi, fa, so, la, ti, za ]

или еще лучше, некоторая информация о каждом событии, как кол:

[ [do, 3], [re, 3], [mi, 2],
  [fa, 2], [so, 1], [la, 1],
  [ti, 1], [za, 2] ]

Есть ли название для того, что я делаю? Существуют ли приняты алгоритмы? Я пишу это в Perl, если это имеет значение, но псевдокод будет делать.

Я знаю , что , учитывая мой пример ввода, я , вероятно , не может быть гарантировано «правильный» порядок. Но мой реальный вход имеет тн больше точек данных, и я уверен , что с некоторой сообразительностью это будет 95% вправо (что на самом деле все , что мне нужно). Я просто не хочу , чтобы заново изобретать колесо , если я не должен.

Задан 09/07/2010 в 19:32
источник пользователем
На других языках...                            


10 ответов

голоса
0
perl -de 0
  DB<1> @a = ( ['a','b','c'], ['c','f'], ['h'] ) 
  DB<2> map { @m{@{$_}} = @$_ } @a
  DB<3> p keys %m
chabf

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

Ответил 09/07/2010 в 19:42
источник пользователем

голоса
0

Это идеальный кандидат для сортировки слияния . Перейти на страницу википедии здесь довольно хорошее представление алгоритма http://en.wikipedia.org/wiki/Merge_sort

То, что вы описали фактически подмножество / маленький твик из сортировки слияния. Вместо того чтобы начать с несортированным массивом, у вас есть набор отсортированных массивов, которые вы хотите объединить вместе. Просто вызовите функцию «Объединить», как описано на странице Википедии на пары ваших массивов и результатах функции слияния до тех пор, пока есть один массив (который будет отсортирован).

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

Ответил 09/07/2010 в 19:45
источник пользователем

голоса
3

Теоретически говоря, позвольте мне предложить следующий алгоритм:

  1. Построить ориентированный граф.
  2. Для каждого входа [X, Y, Z], создайте ребра X-> Y и Y-> Z, если они уже не существуют.
  3. Выполнить топологическую сортировку графа.
  4. Вуаля!

PS
Это только при условии , что все события происходят в определенном порядке (всегда!). Если это не так, то проблема становится NP-полной.

PPS
И просто так , что у вас есть что - то полезное: Сортировка :: Топологическое (не знаю , если это на самом деле работает , но это кажется правильным)

Ответил 09/07/2010 в 19:48
источник пользователем

голоса
0

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

use strict;
use warnings;

my $all 
    = [[ 'do', 're', 'fa', 'ti' ],
       [ 'do', 're', 'mi' ],
       [ 'do', 'la', 'ti', 'za' ],
       [ 'mi', 'fa' ],
       [ 're', 'so', 'za' ]
     ];

my ( @order, %counts );

foreach my $list ( @$all ) { 
    foreach my $item ( @$list ) { 
        my $ref = \$counts{$item}; # autovivs to an *assignable* scalar.
        push @order, $item unless $$ref;
        $$ref++;
    }
}

foreach my $key ( @order ) { 
    print "$key: $counts{$key}\n";
}

# do: 3
# re: 3
# fa: 2
# ti: 2
# mi: 2
# la: 1
# za: 2
# so: 1

Есть другие ответы, как этот, но мой содержит этот аккуратный autovivification трюк.

Ответил 09/07/2010 в 20:31
источник пользователем

голоса
2

Если вы не в письменной форме много кода, вы можете использовать утилиту Unix командной строки tsort:

$ tsort -
do re
re fa
fa ti
do re
re mi
do la
la ti
ti za
mi fa
re so
so za

Что представляет собой список всех пар в вашем входе образца. Это производит в качестве выходного сигнала:

do
la
re
so
mi
fa
ti
za

который является в основном то, что вы хотите.

Ответил 09/07/2010 в 21:06
источник пользователем

голоса
3

Вы можете использовать , tsortчтобы вывести разумную, хотя не обязательно заказ уникальную-сортировку (известную как топологический порядок ) от заказа вы наблюдаемый. Вы можете быть заинтересованы в чтении tsort«s первоначального использования , который сходен по структуре с вашей проблемой.

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

#! /usr/bin/perl

use warnings;
use strict;

use IPC::Open2;

sub tsort {
  my($events) = @_;

  my $pid = open2 my $out, my $in, "tsort";

  foreach my $group (@$events) {
    foreach my $i (0 .. $#$group - 1) {
      print $in map "@$group[$i,$_]\n", $i+1 .. $#$group;
    }
  }

  close $in or warn "$0: close: $!";

  chomp(my @order = <$out>);
  my %order = map +(shift @order => $_), 0 .. $#order;
  wantarray ? %order : \%order;
}

Потому что вы описали данные как разреженные, приведенный выше код дает tsortстолько информации , насколько это возможно о матрице смежности событий , .

Имея эту информацию, вычисление гистограммы и сортировки ее компоненты просто:

my $events = [ ... ];

my %order = tsort $events;

my %seen;
do { ++$seen{$_} for @$_ } for @$events;

my @counts;
foreach my $event (sort { $order{$a} <=> $order{$b} } keys %seen) {
  push @counts => [ $event, $seen{$event} ];
  print "[ $counts[-1][0], $counts[-1][1] ]\n";
}

Для входа в вашем вопросе вы предусмотрели, выход

[Делать, 3]
[Ла, 1]
[Повторно, 3]
[ Таким образом, 1 ]
[Мили, 2]
[Фа, 2]
[Ти, 2]
[ZA, 2]

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

Ответил 09/07/2010 в 21:22
источник пользователем

голоса
0

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

10 Найти ранний элемент во всех массивах
20 Нажмите , что на список
30 Удалить этот элемент из всех массивов
40 Goto 10 , если есть какие - либо предметы , оставленные

Вот рабочий прототип:

#!/usr/bin/perl

use strict;

sub InList {
    my ($x, @list) = @_;
    for (@list) {
        return 1 if $x eq $_;
    }
    return 0;
}

sub Earliest {
    my @lists = @_;
    my $earliest;
    for (@lists) {
        if (@$_) {
            if (!$earliest
                || ($_->[0] ne $earliest && InList($earliest, @$_))) {

                $earliest = $_->[0];
            }
        }
    }
    return $earliest;
}

sub Remove {
    my ($x, @lists) = @_;

    for (@lists) {
        my $n = 0;
        while ($n < @$_) {
            if ($_->[$n] eq $x) {
                splice(@$_,$n,1);
            }
            else {
                $n++
            }
        }
    }
}

my $list = [
    [ 'do', 're', 'fa', 'ti' ],
    [ 'do', 're', 'mi' ],
    [ 'do', 'la', 'ti', 'za' ],
    [ 'mi', 'fa' ],
    [ 're', 'so', 'za' ]
];

my @items;

while (my $earliest = Earliest(@$list)) {
    push @items, $earliest;
    Remove($earliest, @$list);
}

print join(',', @items);

Вывод:

, ре, ми, фа, ля, ти, так, З.А.

Ответил 09/07/2010 в 21:42
источник пользователем

голоса
0

Решение:

Это решает оригинальный вопрос, прежде чем он был изменен спрашивающего.


#!/usr/local/bin/perl -w
use strict; 

   main();

   sub main{
      # Changed your 3-dimensional array to a 2-dimensional array
      my @old = (
                   [ 'do', 're', 'fa', 'ti' ],
                   [ 'do', 're', 'mi' ],
                   [ 'do', 'la', 'ti', 'za' ],
                   [ 'mi', 'fa' ],
                   [ 're', 'so', 'za' ]
                );
      my %new;

      foreach my $row (0.. $#old ){                           # loop through each record (row)
         foreach my $col (0..$#{$old[$row]} ){                # loop through each element (col)                    
            $new{ ${$old[$row]}[$col] }{count}++;
            push @{ $new{${$old[$row]}[$col]}{position} } , [$row,$col];
         }
      }

      foreach my $key (sort keys %new){
         print "$key : $new{$key} " , "\n";                   # notice each value is a hash that we use for properties 
      }      
   } 

Как восстановить Info:

   local $" = ', ';                       # pretty print ($") of array in quotes
   print $new{za}{count} , "\n";          # 2    - how many there were
   print "@{$new{za}{position}[1]} \n";   # 4,2  - position of the second occurrence
                                          #        remember it starts at 0   

В основном, мы создаем уникальный список элементов в хэш. Для каждого из этих элементов у нас есть «свойство» хэш, который содержит скаляр countи массив для position. Количество элементов в массиве должно изменяться в зависимости от того, как много вхождения элемента было в оригинале.

Скалярное свойство не является действительно необходимым , так как вы всегда можете взять скалярный positionмассив , чтобы получить тот же номер. Примечание: если вы когда - нибудь добавить / удалить элементы из массива countи positionне будет коррелировать в их значении.

  • Пример: print scalar @{$new{za}{position}};даст вам то же самое,print $new{za}{count};
Ответил 09/07/2010 в 22:20
источник пользователем

голоса
0

Просто понял ваш вопрос, сказал, что их не предопределен порядок, так что это не может быть Релевент.

код Perl:

$list = [
    ['do', 're', 'fa', 'ti' ],
    ['do', 're', 'mi' ],
    ['do', 'la', 'ti', 'za' ],
    ['mi', 'fa' ],
    ['re', 'so', 'za' ]
];
%sid = map{($_,$n++)}qw/do re mi fa so la ti za/;

map{map{$k{$_}++}@$_}@$list;
push @$result,[$_,$k{$_}] for sort{$sid{$a}<=>$sid{$b}}keys%k;

print "[@$_]\n" for(@$result);

вывод:

[do 3]
[re 3]
[mi 2]
[fa 2]
[so 1]
[la 1]
[ti 2]
[za 2]
Ответил 10/07/2010 в 16:32
источник пользователем

голоса
1

Используйте хэш агрегировать.

my $notes= [[qw(do re fa ti)],
       [qw(do re mi)],
       [qw(do la ti za)],
       [qw(mi fa)],
       [qw(re so za)]];

my %out;
foreach my $list (@$notes)
{
  $out{$_}++ foreach @$list;
}

print "$_: $out{$_}\n" foreach sort keys %out;

Урожайность

do: 3
fa: 2
la: 1
mi: 2
re: 3
so: 1
ti: 2
za: 2

% Из хэша легко преобразуется в список, если это то, что вы хотите.

my @newout;
push @newout,[$_,$out{$_}] foreach sort keys %out;
Ответил 21/04/2011 в 16:54
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more