Forum: PC-Programmierung Algorithmus gesucht: Zahl in zufällig große Teile zerlegen (mit Nebenbedingungen)


von Mike M. (Gast)


Lesenswert?

Hallo zusammen,

gegeben ist folgende Problemstellung:
1) Eine gegebene Zahl N soll in M Teile (m_1, ..., m_M) zerlegt werden, 
so dass die Summe dieser Teile wieder N ergibt. Sowohl N und M sind 
jeweils vorgegeben.

Für dieses Problem kursieren bereits einige Algorithmen im Internet (s. 
https://stackoverflow.com/questions/1581394/split-value-in-24-randomly-sized-parts-using-c-sharp 
oder 
https://stackoverflow.com/questions/14583566/split-number-in-random-parts)

nun habe ich aber noch folgende weitere Anforderen an die M Teile:
2) jeder Teil darf minimal den Wert 0 annehmen
3) jeder Teil darf maximal einen jeweils für jeden Teil separat 
definierten Wert annehmen (max_1, ..., max_M)

Selbstverständlich wird nun N so gewählt, dass diese Bedingungen auch 
erfüllt werden können.


Folgende triviale Lösung kam mir zuerst in den Sinn:
Für jeden Teil m_i wird nacheinander eine zufällige Zahl gewählt, so 
dass m_i <= max_i, N nicht überschritten wird und mit den restlichen 
Teilen noch N erreicht werden kann.

Diesen Ansatz habe ich einmal beispielhaft in C++ implementiert:
1
#include <iostream>
2
#include <random>
3
#include <stdexcept>
4
#include <vector>
5
6
typedef std::vector<float> vector_t;
7
8
std::mt19937 rng;
9
10
11
vector_t get_random_numbers(const vector_t& max_values, float target_sum)
12
{
13
  float sum_max_values = 0.0f;
14
15
  vector_t min_values;
16
17
  for(auto iter = max_values.rbegin(); iter != max_values.rend(); ++iter)
18
  {
19
    min_values.insert(min_values.begin(), target_sum - sum_max_values);
20
    sum_max_values += *iter;
21
  }
22
23
  if(sum_max_values < target_sum)
24
  {
25
    throw std::range_error("could not reach target value");
26
  }
27
28
  vector_t result;
29
  float sum = 0.0f;
30
31
  for(size_t i = 0; i < max_values.size(); i++)
32
  {
33
    const float min = std::max(0.0f, min_values[i] - sum);
34
    const float max = std::min(max_values[i], target_sum - sum);
35
36
    std::uniform_real_distribution<float> uniform_distribution(min, max);
37
    const float value = uniform_distribution(rng);
38
    
39
    result.push_back(value);
40
    sum += value;
41
  }
42
43
  return result;
44
}
45
46
47
int main()
48
{
49
  std::random_device rd;
50
  rng.seed(rd());
51
    
52
  const vector_t max_values = {1.0f, 1.0f, 0.5f, 1.0f};
53
  const float target_sum    = 3.0f;
54
55
  auto result = get_random_numbers(max_values, target_sum);
56
57
  std::cout << "values:" << std::endl;
58
59
  for(auto value : result)
60
  {
61
    std::cout << value << std::endl;
62
  }
63
  
64
  std::cout << "sum: " << std::accumulate(result.begin(), result.end(), 0.0f);
65
}

Allerdings bin ich nun mit der Verteilung der Werte noch nicht ganz 
zufrieden. Dieses müssen zwar nicht zwingend uniform verteilt sein, 
jedoch wäre eine etwas gleichmäßigere Verteilung wünschenswert.

So konvergieren für die Eingabe mit {1.0, 1.0, 1.0, 1.0, 1.0, 1.0} für 
die Maximalwerte und N = 5.0 die Werte bspw. gegen: {0.5, 0.75, 0.875, 
0.9375, 0.96875}. Dies ergibt sich auch schon prinzipbedingt aus obigen 
Ansatz. Notfalls könnte ich die M Teile bei jedem Aufruf zuerst zufällig 
umsortieren. Vielleich hat allerdings jemanden noch einen besseren 
Ansatz.


Vielen Dank schon einmal,
Mike

von asdfasd (Gast)


Lesenswert?

> 1) Eine gegebene Zahl N soll in M Teile (m_1, ..., m_M) zerlegt werden,
> so dass die Summe dieser Teile wieder N ergibt. Sowohl N und M sind
> jeweils vorgegeben.
> 2) jeder Teil darf minimal den Wert 0 annehmen
> 3) jeder Teil darf maximal einen jeweils für jeden Teil separat
> definierten Wert annehmen (max_1, ..., max_M)

Überseh ich was? Die ersten Element werden jeweils auf max gesetzt, bis 
N erreicht ist (das letzte davon evtl kleiner als max), die restlichen 
auf 0.
1
foo(float arr[], float max[], float n, int m)
2
{
3
    for (int i = 0; i < m; ++i)
4
    {
5
        arr[i] = max[i] < n ? max[i] : n;
6
        n -= arr[i]; // rounding ...
7
    }
8
}

von Mario M. (thelonging)


Lesenswert?

asdfasd schrieb:
> Überseh ich was?

Ja, das "zufällig" in der Überschrift. ;-)

von Peter (Gast)


Lesenswert?

Ich würde erst allgemein alle Lösungen berechnen um eine Zahl in N Teile 
zu zerlegen und dann zufällig eine der Lösungen auswählen.

Das mathematische Problem kommt aus dem Bereich der Zahlkompositionen. 
Einen einfachen Ansatz findest du hier: 
http://www.aconnect.de/friends/editions/computer/combinatoricode_g.html
unter "2.2.3. k-Partitionen mit Berücksichtigung der Reihenfolge, 
kolexikographisch geordnet".
Allerdings ohne Maximalwert und mit 1 beginnend. Habe den Algorithmus 
selbst so wie du ihn möchtest darauf aufbauend auch gebraucht und ihn 
letzlich in LabVIEW implementiert. C/C++ kann ich dir nicht anbieten.

von Georg (Gast)


Lesenswert?

asdfasd schrieb:
> Überseh ich was?

Mario M. schrieb:
> Ja, das "zufällig" in der Überschrift. ;-)

Die Forderung ist, wie sich nach und nach herausstellt, noch schärfer: 
die Zahlen sollen nicht nur zufällig, sondern auch statistisch 
gleichverteilt sein. Natürlich ist das nur begrenzt sinnvoll, wenn man 8 
in 4 Teile teilen will wird man keine Gleichverteilung hinkriegen.

Mike M. schrieb:
> 3) jeder Teil darf maximal einen jeweils für jeden Teil separat
> definierten Wert annehmen (max_1, ..., max_M)

Wozu lauter verschiedene Grenzen? Das macht eine Lösung in fast allen 
denkbaren Fällen unmöglich. Man kann ja sagen, so darf man die Werte 
halt nicht wählen, aber das ist ja kein anwendbarer Algorithmus.

Georg

von Vlad T. (vlad_tepesch)


Lesenswert?

warum nicht für jedes feld einen zufälligen wert zwischen 0 und dem 
jeweiligen Maximum ermitteln?
Anschließend wird die Summe der Einzelteile auf die gesuchte Zielsumme 
skaliert.

Soll die Lösung aus ganzen Zahlen bestehen wird in diesem Schritt 
jeweils abgerundet und der Rest auf eine zufällige Zahl aufaddiert 
(wobei natürlich das maximum beachtet wird)


Edit:

code:
1
#include <iostream>
2
#include <vector>
3
#include <random>
4
#include <iterator>
5
6
7
8
template <typename T>
9
std::ostream& operator<< (std::ostream& out, const std::vector<T>& v) {
10
  if (!v.empty()) {
11
    out << '[';
12
    std::copy(v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
13
    out << "\b\b]";
14
  }
15
  return out;
16
}
17
18
int main()
19
{
20
  // general parameter
21
  const int minNumbers = 3;
22
  const int maxNumbers = 10;
23
  const float minPart = 2;
24
  const float maxPart = 1000;
25
26
  // problem variables
27
  int   numbers;
28
  float targetSum;
29
  std::vector<float> maxValues;
30
31
  std::mt19937 rng;
32
  rng.seed(std::random_device()());
33
34
35
  // init problem
36
  numbers = std::uniform_int_distribution<>(minNumbers, maxNumbers)(rng);
37
  targetSum = 0;
38
  maxValues.resize(numbers);
39
  for (auto& v : maxValues) {
40
    v = std::uniform_real_distribution<float>(minPart, maxPart)(rng);
41
    targetSum += v;
42
  }
43
  targetSum = std::uniform_real_distribution<float>(minPart, targetSum)(rng);
44
45
  std::cout << "tartet sum: " << targetSum << " maxvalues: " << maxValues << std::endl;
46
47
48
49
50
  // solve
51
  std::vector<float> values(maxValues);
52
  float valuesSum = 0;
53
54
  float sumCheck = 0;
55
56
  for (auto& v : values){
57
    v = std::uniform_real_distribution<float>(0, v)(rng);
58
    valuesSum += v;
59
  }
60
61
  std::cout << "random vec: sum: " << valuesSum << " values: " << values << std::endl;
62
63
  for (auto& v: values)
64
  {
65
    v *= targetSum / valuesSum;
66
    sumCheck += v;
67
  }
68
  std::cout << "output vec: sum: " << sumCheck << " values: " << values << std::endl;
69
70
71
72
  return 0;
73
}

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.