交错稀疏排序数组
Posted
技术标签:
【中文标题】交错稀疏排序数组【英文标题】:Interleaving sparse sorted arrays 【发布时间】:2011-03-14 00:46:15 【问题描述】:我有一组事件列表。事件总是以给定的顺序发生,但并非每个事件都总是发生。这是一个示例输入:
[[ 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% 正确(这确实是我所需要的)。如果没有必要,我只是不想重新发明***。
【问题讨论】:
在 perl 中应该很容易 - 使用哈希进行计数,然后对键进行排序并准确提取您想要的。 @Jefromi,除非我误解 OP 是说键上没有定义顺序... 正如我所说,这些值本质上不是可排序的。我已将它们从 A-H 更改为“do”、“re”等,以使其更加明显。 我不明白:是什么决定了do
排在re
之前,za
排在最后?
当问题使用 A、B、C 时,我更喜欢它;不,do-re-mi
【参考方案1】:
您可以使用tsort
从您观察到的顺序中推断出合理的(尽管不一定是唯一的)排序顺序(称为topological order)。您可能有兴趣阅读tsort
's original use,它的结构与您的问题相似。
请注意,tsort
需要一个无环图。就您的示例而言,这意味着您看不到 do 后跟 re 在一个序列中,而 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";
对于您提供的问题中的输入,输出是
[做,3] [拉,1] [重新,3] [所以,1] [英里,2] [发,2] [ 钛, 2 ] [ za, 2 ]
这看起来很有趣,因为我们知道 solfège 的顺序,但是 re 和 la 在 $events
定义的 partial order 中是无法比拟的:我们只知道它们必须都在 do 之后。
【讨论】:
这是最好的解决方案,因为它不需要我实现实际的算法。【参考方案2】:从理论上讲,让我建议以下算法:
-
构建有向图。
对于每个输入 [ X, Y, Z ],如果边缘 X->Y 和 Y->Z 不存在,则创建它们。
对图表执行topological sorting。
瞧!
PS 这只是假设所有事件都以特定顺序发生(总是!)。如果不是这样,问题就变成了 NP-Complete。
PPS 只是为了让你有一些有用的东西:Sort::Topological(不知道它是否真的有效,但它似乎是正确的)
【讨论】:
+1 表示实际上适用于无序元素的第一个答案。【参考方案3】:如果您不想编写太多代码,可以使用 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
这基本上就是你想要的。
【讨论】:
【参考方案4】:使用哈希进行聚合。
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;
【讨论】:
【参考方案5】:perl -de 0
DB<1> @a = ( ['a','b','c'], ['c','f'], ['h'] )
DB<2> map @m@$_ = @$_ @a
DB<3> p keys %m
chabf
我能想到的最快的捷径。无论哪种方式,您都必须至少迭代一次...
【讨论】:
这不起作用,因为 order 是最重要的位。您的输出在“a”和“b”之前有“c”,尽管您的第一个输入列表在它们之后都有“c”。 是的,这是一个简单的解决方案。但公平地说,这个问题说他们不关心订购。 :-)【参考方案6】:这是合并排序的完美候选。转到此处的***页面以获得算法http://en.wikipedia.org/wiki/Merge_sort的一个很好的表示@
您所描述的实际上是合并排序的子集/小调整。您不是从未排序的数组开始,而是有一组要合并在一起的排序数组。只需按照***页面中关于数组对和合并函数结果的描述调用“合并”函数,直到你有一个数组(将被排序)。
要将输出调整为您想要的方式,您需要定义一个比较函数,如果一个事件小于、等于或大于另一个事件,则该函数可以返回。然后,当您的合并函数发现两个相等的事件时,您可以将它们合并为一个事件并为该事件保留计数。
【讨论】:
这些值本质上不是可排序的。我已将它们从 A-H 更改为“do”、“re”、“mi”等,以使其更清楚。 啊——我明白了。在您的示例中,数组按事件排序——您是说原始数组中的事件按不同的键排序,虽然该键已“丢失”,但您希望保留每个事件的原始顺序数组的一部分,对吧?有一种方法可以通过重新编写比较函数以根据输入的原始顺序返回小于和大于返回的合并排序相当容易地做到这一点。午饭后我会考虑的:-)【参考方案7】:粗略地说,我给它的名字是“散列”。您正在将事物放入名称值对中。如果你想保持某种秩序,你必须用一个保持秩序的数组来补充散列。那个订单对我来说是“遭遇订单”。
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
还有其他类似的答案,但我的答案包含这个巧妙的自动复活技巧。
【讨论】:
【参考方案8】:我也不确定这会被称为什么,但我想出了一种方法来找到给定数组数组作为输入的顺序。本质上伪代码是:
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);
输出:
do,re,mi,fa,la,ti,so,za
【讨论】:
【参考方案9】:刚刚意识到你的问题说他们没有预定的顺序,所以这可能无关紧要。
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/;
mapmap$k$_++@$_@$list;
push @$result,[$_,$k$_] for sort$sid$a<=>$sid$bkeys%k;
print "[@$_]\n" for(@$result);
输出:
[do 3]
[re 3]
[mi 2]
[fa 2]
[so 1]
[la 1]
[ti 2]
[za 2]
【讨论】:
这只有在您知道并列出所有可能事件的原始顺序时才有效。【参考方案10】:解决方案:
这解决了提问者修改之前的原始问题。
#!/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 $newzacount , "\n"; # 2 - how many there were
print "@$newzaposition[1] \n"; # 4,2 - position of the second occurrence
# remember it starts at 0
基本上,我们在散列中创建一个唯一的元素列表。对于这些元素中的每一个,我们都有一个“属性”哈希,其中包含一个标量count
和一个数组position
。数组中元素的数量应根据元素在原始元素中出现的次数而有所不同。
标量属性并不是真正需要的,因为您总是可以使用position
数组的标量来检索相同的数字。注意:如果您从数组中添加/删除元素 count
和 position
将不会在它们的含义上相关。
print scalar @$newzaposition;
将提供与 print $newzacount;
相同的信息
【讨论】:
如果你只有一个二维数组,而不是不必要的 3 维数组,这看起来会更简洁一些。 感谢您提供有关如何获取数据的示例!我认为散列的散列是他感兴趣的以上是关于交错稀疏排序数组的主要内容,如果未能解决你的问题,请参考以下文章