交错稀疏排序数组

时间:2010-07-09 18:32:12

标签: perl list merge pseudocode

我有一套事件清单。事件总是以给定的顺序发生,但不是每个事件总是发生。这是一个示例输入:

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

输入值没有任何固有顺序。它们实际上是“创建符号链接”和“重新索引搜索”等消息。它们在单个列表中排序,但是没有办法只查看第一个列表中的“fa”和第二个列表中的“mi”,并确定哪个位于另一个列表之前。

我希望能够获取该输入并生成所有事件的排序列表:

[ 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%正确(这是我真正需要的)。如果我不需要,我只是不想重新发明轮子。

10 个答案:

答案 0 :(得分:3)

从理论上讲,让我建议以下算法:

  1. 构建有向图。
  2. 对于每个输入[X,Y,Z],如果它们尚未存在,则创建边X-> Y和Y-> Z.
  3. 执行图表的topological sorting
  4. 瞧!
  5. PS
    这只是假设所有事件都以特定顺序发生(始终!)。如果情况并非如此,则问题变为NP完全。

    PPS
    只是为了让你有一些有用的东西:Sort::Topological(不知道它是否真的有效,但看起来是正确的)

答案 1 :(得分:3)

您可以使用tsort从您观察到的排序中推断出合理的 - 但不一定是唯一排序顺序(称为topological order)。您可能有兴趣阅读tsort's original use,其结构与您的问题类似。

请注意,tsort需要非循环图。就你的例子而言,这意味着你看不到在一个序列中执行re,然后在另一个序列中执行do。

#! /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";
}

对于您提供的问题中的输入,输出为

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

这看起来很有趣,因为我们知道了solfège的顺序,但是在$events定义的partial order中,re和la是无法比拟的:我们只知道它们必须都来自do。

答案 2 :(得分: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

这基本上就是你想要的。

答案 3 :(得分: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

如果你想要的话,%out散列很容易转换成一个列表。

my @newout;
push @newout,[$_,$out{$_}] foreach sort keys %out;

答案 4 :(得分:0)

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

我能想到的最快的捷径。无论哪种方式,你必须至少迭代一次......

答案 5 :(得分:0)

这是合并排序的完美候选者。转到维基百科页面,可以很好地表示算法http://en.wikipedia.org/wiki/Merge_sort

您所描述的实际上是合并排序的子集/小调整。您没有以未排序的数组开头,而是拥有一组要合并在一起的已排序数组。只需调用维基百科页面中描述的“合并”函数,对数组对和合并函数的结果,直到你有一个数组(将被排序)。

要根据需要调整输出,您需要定义一个比较函数,如果一个事件小于,等于或大于另一个事件,则该函数可以返回。然后,当您的合并函数找到两个相等的事件时,您可以将它们折叠为单个事件并保留该事件的计数。

答案 6 :(得分: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

还有其他像这样的答案,但我的答案包含了这个整洁的自动化技巧。

答案 7 :(得分:0)

我不确定这会被称为什么,但我找到了一种方法来找到给定数组数组作为输入的顺序。基本上伪代码是:

10查找所有阵列中最早的项目
20将其推入列表中 30从所有阵列中删除该项目
40如果有任何项目,请转到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);

输出:

做,RE,MI,FA,LA,TI,所以,ZA

答案 8 :(得分: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 
      }      
   } 

如何检索信息:

   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};
  • 相同的内容

答案 9 :(得分: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]
相关问题