Interleaving schaars gesorteerd arrays

stemmen
7

Ik heb een set van de lijsten van evenementen kregen. De gebeurtenissen altijd gebeuren in een bepaalde volgorde, maar niet elke gebeurtenis gebeurt altijd. Hier is een voorbeeld ingang:

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

De invoerwaarden hebben geen inherente volgorde. Ze zijn eigenlijk berichten als het creëren van symbolische links en indexeren zoeken. Ze zijn gesorteerd in de afzonderlijke lijst, maar er is geen manier om te kijken naar alleen 'fa' in de eerste lijst en 'mi' in de tweede en bepalen welke komt voor de andere.

Ik zou graag in staat zijn om die ingang te nemen en het genereren van een gesorteerde lijst van alle evenementen:

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

of beter nog, wat informatie over elke gebeurtenis, zoals een telling:

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

Is er een naam voor wat ik doe? Zijn er geaccepteerd algoritmen? Ik schrijf dit in Perl, als die kwesties, maar pseudocode zal doen.

Ik weet dat gezien mijn voorbeeld input, ik waarschijnlijk niet kan worden gegarandeerd van de juiste volgorde. Maar mijn echte ingang heeft tonnen meer datapunten, en ik ben ervan overtuigd dat met enige slimheid het zal 95% gelijk te hebben (dat is echt alles wat ik nodig heb). Ik wil niet opnieuw het wiel uitvinden als ik niet hoeft te doen.

De vraag is gesteld op 09/07/2010 om 19:32
bron van user
In andere talen...                            


10 antwoorden

stemmen
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 snelkoppeling die ik kan bedenken. Hoe dan ook, moet je minstens een keer doorlopen dingen ...

antwoordde op 09/07/2010 om 19:42
bron van user

stemmen
0

Dit is een perfecte kandidaat voor een Merge Sort . Ga hier naar de wikipedia pagina voor een vrij goede weergave van het algoritme http://en.wikipedia.org/wiki/Merge_sort

Wat je hebt beschreven, is eigenlijk een subset / kleine tweak van de merge soort. In plaats van te beginnen met een ongesorteerde array, heb je een set van gesorteerde arrays die u wilt samenvoegen. Gewoon bellen met de functie "merge", zoals beschreven in de wikipedia pagina over paren van uw arrays en de resultaten van de merge functie totdat u één array (die zal worden opgelost).

Om de uitvoer naar de manier waarop u wilt aanpassen, moet u een vergelijking functie die kan terugkeren als één gebeurtenis minder dan, gelijk aan, of groter is dan een ander evenement is te definiëren. Dan, wanneer je merge-functie twee gebeurtenissen die gelijk zijn vindt, kunt u ze samen in een enkele gebeurtenis en houdt een telling voor dat evenement.

antwoordde op 09/07/2010 om 19:45
bron van user

stemmen
3

Theoretisch gesproken, laat me raden de volgende algoritme:

  1. Bouw een gerichte graaf.
  2. Voor elke ingang [X, Y, Z], maken de randen X-> Y en Y> Z als ze niet actief is.
  3. Voer een topologische sortering van de grafiek.
  4. Voila!

PS
Dit is alleen de veronderstelling dat alle gebeurtenissen plaatsvinden in een bepaalde volgorde (altijd!). Als dat niet het geval is, wordt het probleem NP-Complete.

PPS
En gewoon zo dat je iets nuttigs te hebben: Sorteer :: topologische (weet niet of het echt werkt, maar het lijkt rechts)

antwoordde op 09/07/2010 om 19:48
bron van user

stemmen
0

Ruwweg, de naam die ik zou geven is "hashing". U bent om dingen in naam waarde paren. Als u wilt wat schijn van orde te houden, moet je de hash aan te vullen met een reeks die orde houdt. Deze beschikking is "ontmoeting order" voor mij.

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

Er zijn andere antwoorden zoals deze, maar mijn bevat deze keurige autovivification truc.

antwoordde op 09/07/2010 om 20:31
bron van user

stemmen
2

Als je niet in het schrijven van te veel code, kunt u gebruik maken van de Unix command-line utility tsort:

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

Dat is een lijst van alle paren in je steekproef input. Dit levert als output:

do
la
re
so
mi
fa
ti
za

dat is eigenlijk wat je wilt.

antwoordde op 09/07/2010 om 21:06
bron van user

stemmen
3

U kunt gebruik maken tsortvan een redelijke, zij het niet noodzakelijk uniek-sorteervolgorde (bekend als een afleiden topologische volgorde ) van de ordening die je hebt waargenomen. U bent wellicht geïnteresseerd in het lezen van zijn tsort's oorspronkelijke gebruik , dat is vergelijkbaar met die van uw probleem.

Merk op dat tsortvereist een acyclische grafiek. In termen van uw voorbeeld, betekent dit dat u kon niet zien doen, gevolgd door opnieuw in een volgorde en opnieuw gevolgd door do in de andere.

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

Omdat u beschreef de gegevens schaars, de code voorziet tsortmet zoveel mogelijk informatie over gebeurtenissen adjacentiematrix.

Het hebben van die informatie, het berekenen van een histogram en sorteren van de onderdelen ervan is eenvoudig:

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

Voor de invoer in uw vraag die u heeft opgegeven, is de uitvoer

[Do, 3]
[La, 1]
[Re, 3]
[Zodat, 1]
[Mi, 2]
[Fa, 2]
[Ti, 2]
[ZA, 2]

Dit ziet er grappig uit omdat we de volgorde van notenleer weten, maar her en la zijn onvergelijkbaar in de gedeeltelijke volgorde bepaald door $events: we alleen dat ze moeten beide komen na ken.

antwoordde op 09/07/2010 om 21:22
bron van user

stemmen
0

Ik ben niet echt zeker wat dit ofwel genoemd zou worden, maar ik bedacht een manier om de volgorde, gezien de reeks van arrays als een ingang vinden. In wezen de pseudo-code is:

10 Zoek vroegste werk in alle arrays
20 Push die op een lijst met
30 Verwijder het item uit alle arrays
40 Goto 10 als er geen items achtergelaten

Hier is een werkend prototype:

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

Output:

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

antwoordde op 09/07/2010 om 21:42
bron van user

stemmen
0

Oplossing:

Dit lost het oorspronkelijke vraag voordat het werd gewijzigd door de vraagsteller.


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

Hoe kan ophalen 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   

Kortom, we creëren een unieke lijst van elementen in de hash. Voor elk bestanddeel hebben we een "eigendom" hash, dat een scalair bevat counten een matrix voor position. Het aantal elementen in de array dient te variëren, op basis van het aantal voorkomens van de elementen waren in het origineel.

De scalaire pand is niet echt nodig, omdat je de scalaire van het altijd zou kunnen nemen positionarray naar hetzelfde nummer op te halen. Let op: als je ooit toevoegen / verwijderen van elementen uit de array counten positionwordt niet correleren in hun betekenis.

  • Bijvoorbeeld: print scalar @{$new{za}{position}};zal je hetzelfde als gevenprint $new{za}{count};
antwoordde op 09/07/2010 om 22:20
bron van user

stemmen
0

Net realiseerde uw vraag gezegd hun is geen vooraf bepaalde volgorde, dus dit kan niet worden bestaande reglementen.

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

output:

[do 3]
[re 3]
[mi 2]
[fa 2]
[so 1]
[la 1]
[ti 2]
[za 2]
antwoordde op 10/07/2010 om 16:32
bron van user

stemmen
1

Gebruik een hash te aggregeren.

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;

opbrengsten

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

De% out hash wordt gemakkelijk omgezet in een lijst als dat is wat je wilt.

my @newout;
push @newout,[$_,$out{$_}] foreach sort keys %out;
antwoordde op 21/04/2011 om 16:54
bron van user

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