Thread Zahlenkombinationen suchen (16 answers)
Opened by hugenyn at 2010-10-22 00:55

topeg
 2010-10-27 02:47
#142201 #142201
User since
2006-07-10
2611 Artikel
BenutzerIn

user image
So wie du es macht hast du mehrere probleme, später und das hat nichts mit der Unhandlichkeit des Codes zu tun. Du weist nachher nur noch aus der Position der Summe im Array welche Kartenkombination die Summe ergeben hat. Das ist weder skalierbar noch übersichtlich. Darum habe ich einen Hash verwendet der als Schlüssel die Summe hat und als wert ein anonymes Array mit den Kartennummern.

Man kann das Problem ganz klassisch rekursiv lösen:

Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
sub combinations
{
  # übernehme die Karten
  my @cards=@_;

  # wenn keine Karten an die Funktion übergeben wurden liefere eine leere Liste zurück
  return [] unless (@cards);

  wenn nur eine Karte in der Liste ist dann liefere nur die Liste zurück
  return \@cards if(@cards==1);

  # erzeuge die liste mit den restlichen Kombinationen:
  my @ret;
  # solange noch karten vorhanden sind:
  while(@cards)
  {
    # nimm die erste Karte heraus
    my $card=shift(@cards);
    
    # füge diese Karte des RückgabeArrays hinzu
    push(@ret,[$card]);

    # rufe die Funktion selber wider auf und gehe das Ergebnis durch (ist ein array)
    # das ist rekursive teil, das die Funktion sich immer wieder selber aufruft,
    # die übergebene liste ist aber immer um ein Element kürzer
    # darum kommt die Rekursion irgendwann zu einem Ende (siehe oben)
    for my $r (combinations(@cards))
    {
      # $r enthält eine Arrayrefenz!
      # füge die herausgenommene Karte wieder ein
      # und füge die das dem RückgabeArray hinzu
      push(@ret,[@$r,$card]) if(@$r);
    }
  }

  # gib das RückgabeArray zurück
  return @ret;
}


rekursive Funktionen sind zu Anfang schwer zu verstehen, aber das gibt sich mit der Zeit. Am einfachsten ist es du spielst das auf dem Papier durch:
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
aufruf: combinations(1 2 3 4)
bekomme: 1 2 3 4
gehe liste durch
noch 1 2 3 4 in der Liste
@zurück1 += [ 1 ]

aufruf combinations(2 3 4)
bekomme: 2 3 4
gehe liste durch
noch 2 3 4 in der Liste
@zurück2 += [ 2 ]

aufruf combinations(3 4)
bekomme: 3 4
gehe liste durch
noch 3 4 in der liste
@zurück3 += [ 3 ]

aufruf combinations(4)
bekomme: 4
gebe ( [ 4 ] ) zurück

@zurück3 += [ 3 4 ]

noch 4 in der liste
@zurück3 += [ 4 ]

aufruf combinations()
bekomme:
gebe ( [ ] ) zurück

gebe zurück ( [ 3 4 ] [ 4 ] )

@zurück2 += [ 2 3 4 ]
@zurück2 += [ 2 4 ]

noch 3 4 in der liste
@zurück2 += [ 3 ]

aufruf combinations(4)
bekomme: 4
gebe ( [ 4 ] ) zurück

@zurück2 += [ 3 4 ]

noch 4 in der liste
@zurück2 += [ 4 ]

aufruf combinations()
bekomme:
gebe ( [] ) zurück

gebe zurück ([ 2 ] [ 2 3 4 ] [ 2 4 ] [ 3 4 ] [ 4 ])

@zurück1 += [ 1 2 ]
@zurück1 += [ 1 2 3 4]
@zurück1 += [ 1 2 4 ]
@zurück1 += [ 1 3 4 ]
@zurück1 += [ 1 4 ]

noch 2 3 4 in der liste
@zurück1 += [ 2 ]

aufruf combinations( 3 4 )
bekomme: 3 4
...


Wie du siehst erzeugt die Rekursion alle nötigen Kombination. (Mein erster Code ist komplexer, weil ich nicht genügend nachgedacht habe. :-) )

Der Code lässt sich auch sehr kompakt schreiben:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
sub combinations
{
  return [] unless (@_);
  my @ret;
  while(@_)
  {
    my $card=shift(@_);
    push(@ret,[@$_,$card]) for(combinations(@_))
  }
  return @ret;
}

Und der Code kommt mit beliebig vielen Karten klar.

In dem und auch bei deinem Code kann es ein paar unnötige Kombinationen geben, wenn mehrere gleiche Karten auf der Hand sind. Um das Problem zu lösen kann man wunderbar eine Besonderheit von Hashes nutzen. Die Schlüssel in einem Hash kommen niemals doppelt vor.
Die Lösung ist also:
sortiere das Array, füge die Werte daraus es zusammen und nutze den so erzeugten String als Schlüssel. Wenn der Schlüssel schon im Hash vorkommt, dann verwerfe das Array. An einem Beispiel:

Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# gegeben sei ein Array mit Arrays:
my @array=(
  [1],
  [1 3 4],
  [1 2 3 4],
  [1 2 3 4],
  [2 3 1 4],
  [4 3 1],
);

# definiere einen Hash,
# der die als Indikator für die doppelten Werte dienen soll:
my %unique;

# das Array für die Werte
# ohne die doppelten
my @unique_array;

# gehe das Array @array durch:
for my $array_ref (@array)
{
  # $array_ref ist eine referenz auf ein Array,
  # das muss etwas anders behandelt werden als ein normales Array.
  # das Array sortieren und gleichzeitig eine Kopie anlegen:
  my @copy=sort(@$array_ref);
  # wenn man ein Array in Doppelte Anführungszeichen setzt,
  # dann fügt Perl dort die elemnte der liste ein.
  # man kann also schreiben
  my $key="@copy";
  # und bekommt einen string den man als Schlüssel verwenden kann.
  # nun wird geprüft ob der String als Schlüssel noch nicht im Hash ist:
  if(!$unique{$key})
  {
    # setze den Schlüssel,
    # damit ein Array mit den selben Werten nicht nochmal eingefügt wird.
    $unique{$key}=1;
    #füge das Array @unique_array hinzu:
    push(@unique_array,\@copy);
    # hier ist es wichtig eine Refenz hinzu zu fügen!
  }
}

# in @unique_array sind nun Arrayrefenzen mit einzigartigen Wertkombinationen

Es gibt verschiedenen Möglichkeiten die Funktionalität zu schreiben, lass dich davon nicht irritieren.
eine davon:
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# gegeben sei ein Array mit Arrays:
my @array=(
  [1],
  [1 3 4],
  [1 2 3 4],
  [1 2 3 4],
  [2 3 1 4],
  [4 3 1],
);

my %unique;
my @unique_array=grep{$unique{"".join " ", sort @$_}++}@array;

# in @unique_array sind nun Arrayrefenzen mit einzigartigen Wertkombinationen

Last edited: 2010-10-27 02:57:32 +0200 (CEST)

View full thread Zahlenkombinationen suchen