Verschachtelung spärlich sortiert Arrays

stimmen
7

Ich habe eine Reihe von Listen von Ereignissen bekommt. Die Ereignisse geschehen immer in einer bestimmten Reihenfolge, aber nicht jedes Ereignis passiert immer. Hier ist ein Beispiel-Eingang:

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

Die Eingangswerte haben keine inhärente Reihenfolge. Sie sind eigentlich Meldungen wie „Erstellen von symbolischen Links“ und „reindexing Suche“. Sie sind in den einzelnen Liste sortiert, aber es gibt keine Möglichkeit, nur zu schauen ‚fa‘ in der ersten Liste und ‚mi‘ in der zweiten und bestimmen, welche vor dem anderen kommt.

Ich möchte in der Lage sein , dass die Eingabe zu nehmen und eine sortierte Liste generiert alle Ereignisse:

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

oder besser noch, einige Informationen über jedes Ereignis, wie eine Zählung:

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

Gibt es einen Namen für das, was ich tue? Gibt es akzeptiert Algorithmen? Ich schreibe dies in Perl, wenn es ankommt, aber Pseudo-Code tun wird.

Ich weiß , dass mein Beispiel Eingabe gegeben, wahrscheinlich kann ich nicht von der „richtigen“ Reihenfolge garantiert werden. Aber mein wirklicher Eingang hat Tonnen mehr Datenpunkte, und ich bin zuversichtlich , dass es mit etwas Geschicklichkeit 95% richtig sein wird (was wirklich alles , was ich brauche). Ich will einfach nicht , das Rad neu erfinden , wenn ich nicht zu tun haben.

Veröffentlicht am 09/07/2010 um 19:32
quelle vom benutzer
In anderen Sprachen...                            


10 antworten

stimmen
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 Abkürzung ich mir vorstellen kann. So oder so, müssen Sie durch die Dinge durchlaufen mindestens einmal ...

Beantwortet am 09/07/2010 um 19:42
quelle vom benutzer

stimmen
0

Dies ist ein perfekter Kandidat für einen Merge Sort . Gehen Sie auf die Wikipedia - Seite hier für eine ziemlich gute Darstellung des Algorithmus http://en.wikipedia.org/wiki/Merge_sort

Was Sie beschrieben haben, ist eigentlich eine Untergruppe / kleine zwicken des Mergesort. Statt mit einer unsortierten Array von Start, haben Sie eine Reihe von sortierten Arrays, die Sie zusammenführen möchten. Rufen Sie einfach die „Merge“ Funktion wie in der Wikipedia-Seite auf Paare Ihre Arrays und die Ergebnisse der Merge-Funktion beschrieben, bis Sie ein einzelnes Array (die sortiert werden wird).

Um die Ausgabe auf die Art und Weise zu optimieren Sie wollen, müssen Sie eine Vergleichsfunktion definieren, die zurückkehren kann, wenn ein Ereignis kleiner als, gleich oder größer ist als ein anderes Ereignis. Dann, wenn Ihre Merge-Funktion zwei Ereignisse findet, die gleich sind, können Sie sie zu einem einzigen Ereignis zusammenbrechen und eine Zählung halten für dieses Ereignis.

Beantwortet am 09/07/2010 um 19:45
quelle vom benutzer

stimmen
3

Theoretisch gesprochen, lassen Sie mich den folgenden Algorithmus vorschlagen:

  1. Erstellen Sie einen gerichteten Graphen.
  2. Für jeden Eingang [X, Y, Z], erstellen Sie die Kanten X-> Y und Y> Z, wenn sie nicht bereits vorhanden sind.
  3. Führen Sie eine topologische Sortierung des Graphen.
  4. Voila!

PS
Das setzt voraus , nur , dass alle Ereignisse in einer bestimmten Reihenfolge auftreten (immer!). Wenn das nicht der Fall ist, wird das Problem NP-vollständig.

PPS
Und nur so , dass Sie etwas Nützliches: Sortieren :: Topologische (weiß nicht , ob es tatsächlich funktioniert , aber es scheint rechts)

Beantwortet am 09/07/2010 um 19:48
quelle vom benutzer

stimmen
0

Grob gesagt, wäre der Name Ich gebe es „Hashing“. Sie setzen die Dinge in Name-Wert-Paare. Wenn Sie einen Anschein von Ordnung halten wollen, müssen Sie den Hash mit einem Array ergänzen, die Ordnung hält. Diese Ordnung ist „Begegnung Ordnung“ für mich.

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

Es gibt noch andere Antworten wie diese, aber ich enthält diesen ordentlich autovivification Trick.

Beantwortet am 09/07/2010 um 20:31
quelle vom benutzer

stimmen
2

Wenn Sie zu viel Code nicht in das Schreiben sind, können Sie die Befehlszeilenprogramm Unix verwenden tsort:

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

Das ist eine Liste aller Paare in Ihrem Probe-Eingang. Dies erzeugt als Ausgabe:

do
la
re
so
mi
fa
ti
za

das ist im Grunde, was Sie wollen.

Beantwortet am 09/07/2010 um 21:06
quelle vom benutzer

stimmen
3

Sie können mit tsorteiner angemessenen-wenn auch nicht unbedingt einzigartig-Sortierreihenfolge ( auch bekannt als abzuleiten topologische Ordnung ) von der Bestellung Sie beobachtet hat. Sie können bei der Lektüre interessiert sein tsort‚s ursprünglichen Gebrauch , die in ihrer Struktur , um Ihr Problem ähnlich ist.

Beachten Sie, dass tsorteine azyklische Graph erfordert. In Bezug auf Ihrem Beispiel bedeutet dies , könnten Sie nicht wieder in einer Sequenz gefolgt sehen mich und wieder von do in einem anderen gefolgt.

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

Da Sie die Daten als spärlich beschrieben, stellt der Code über tsortso viele Informationen wie möglich über die Adjazenzmatrix Ereignisse.

Nachdem diese Informationen, Berechnen eines Histogramms und Sortieren seiner Komponenten ist einfach:

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";
}

Für die Eingabe in Ihrer Frage, die Sie zur Verfügung gestellt, ist der Ausgang

[Tun, 3]
[La, 1]
[Re, 3]
[So, 1]
[Mi, 2]
[Fa, 2]
[Ti, 2]
[Za, 2]

Das sieht lustig , weil wir den Auftrag von solfège wissen, aber neu und la ist unvergleichlich in der Teilordnung definiert durch $events: Wir wissen nur , dass sie beide nach tun müssen kommen.

Beantwortet am 09/07/2010 um 21:22
quelle vom benutzer

stimmen
0

Ich bin nicht wirklich sicher, was das entweder genannt werden würde, aber ich herausgefunden eine Möglichkeit, die Reihenfolge der Anordnung von Arrays als eine Eingabe gegeben zu finden. Im Wesentlichen des Pseudo-Code ist:

10 Finden früheste Artikel in allen Arrays
20 Drücken Sie, dass auf einer Liste
30 das Element entfernen aus allen Arrays
40 Goto 10 , wenn es irgendwelche Reststücke

Hier ist ein funktionsfähiger Prototyp:

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

Ausgabe:

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

Beantwortet am 09/07/2010 um 21:42
quelle vom benutzer

stimmen
0

Lösung:

Dies löst die ursprüngliche Frage, bevor es durch die Fragesteller geändert wurde.


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

Wie Abrufen 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   

Im Grunde haben wir eine eindeutige Liste von Elementen in der Hash erstellen. Für jedes dieser Elemente haben wir eine „Eigenschaft“ hash, die einen Skalar enthält , countund eine Anordnung für die position. Die Anzahl der Elemente in dem Array soll, variieren basierend auf wie viele Vorkommen des Elements in der ursprünglichen waren.

Die skalare Eigenschaft ist nicht wirklich notwendig , da man immer die skalare des nehmen könnte positionArray die gleiche Nummer abzurufen. Hinweis: Wenn Sie jemals hinzufügen / entfernen Elemente aus dem Array countund positionnicht in ihrer Bedeutung korrelieren.

  • Beispiel: print scalar @{$new{za}{position}};geben Sie das gleiche wieprint $new{za}{count};
Beantwortet am 09/07/2010 um 22:20
quelle vom benutzer

stimmen
0

Gerade realisierte Ihre Frage sagte sie keine vorbestimmten Reihenfolge ist, so dass dies nicht relevent werden kann.

Perl-Code:

$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);

Ausgabe:

[do 3]
[re 3]
[mi 2]
[fa 2]
[so 1]
[la 1]
[ti 2]
[za 2]
Beantwortet am 10/07/2010 um 16:32
quelle vom benutzer

stimmen
1

Verwenden einer Hash zu aggregieren.

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;

Die Ausbeuten

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

Das% aus Hash wird einfach in eine Liste umgewandelt, wenn das ist, was Sie wollen.

my @newout;
push @newout,[$_,$out{$_}] foreach sort keys %out;
Beantwortet am 21/04/2011 um 16:54
quelle vom benutzer

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