我有一套事件清单。事件总是以给定的顺序发生,但不是每个事件总是发生。这是一个示例输入:
[[ 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%正确(这是我真正需要的)。如果我不需要,我只是不想重新发明轮子。
答案 0 :(得分:3)
从理论上讲,让我建议以下算法:
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]