Code-Challenge: Shut the Box

Es gibt 16 Antworten in diesem Thema. Der letzte Beitrag () ist von SeriTools.

    Code-Challenge: Shut the Box

    Falls jemand etwas Langeweile hat, hier eine kleine Code-Challenge:

    Es geht im Grunde um das Spiel Shut The Box, auch bekannt als Klappenspiel, allerdings in etwas abgewandelter Form:

    Input:
    Das Programm bekommt als Input (in welcher Form ist egal, kann auch direkt im Code stehen) eine Summe s, die Anzahl der Summanden n und eine Liste von möglichen Summanden i[].

    Alle Eingaben sind natürliche Zahlen.
    Die Liste i[] ist nicht sortiert.

    Output:
    Es sollen zwei Ausgaben getätigt werden:
    1.: Das Programm soll eine weitere Liste zurückgeben, in der sich n Summanden aus i[] befinden, deren Summe s beträgt. Jeder Summand aus i[] darf nur einmal benutzt werden. Gibt es mehrere Möglichkeiten, so muss nur eine zurückgegeben werden. Die Ausgabeliste muss nicht sortiert werden.
    2.: Gibt es keine Lösung - ob wegen zu kleiner Summandenliste oder unpassender Summanden -, so soll dies auch zurückgegeben werden. Zwischen den Fehlschlagsfällen muss nicht unterschieden werden. (Kurzum: ein Success-Boolean)
    Außerdem soll die zurückgegebene Liste leer sein/bleiben, wenn es keine Lösung gibt.

    Es muss keine Konsoleneingabe oder -ausgabe erfolgen, es geht allein um die Eingabe/Ausgabe im Code.

    Beispiel:
    Input:
    s=18, n=5, i[]={ 4, 11, 1, 7, 3, 5, 2 }

    Output:
    true, { 2, 5, 3, 7, 1 }


    Der Code muss in VB oder C# geschrieben werden.

    Es geht in dieser Challenge nicht ausschließlich um den kürzesten Code. Es kann jeder beliebige Code eingereicht werden, ob er nun zur Kategorie kürzester/längster, am leichtesten/schwersten lesbarer, schönster/hässlichster oder effizientester/ineffizientester Code oder sonst was passt, steht jedem natürlich frei, auch sind mehrere Codes von einem Ersteller natürlich erlaubt und erwünscht!

    Ich habe auch schon eine Lösung vorbereitet und werde sie später posten.

    Fragen bitte immer stellen!

    Viel Spaß an der Challenge!
    lg SeriTools
    | Keine Fragen per PN oder Skype.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „SeriTools“ ()

    Done!
    Spoiler anzeigen

    C-Quellcode

    1. public static bool ShutTheBox(int sum, int count, int[] input, out int[] output)
    2. {
    3. var combinations = input.Permutate();
    4. foreach (var combination in combinations)
    5. {
    6. int s = 0;
    7. for (int i = 0; i < count; i++)
    8. s += combination[i];
    9. if (s == sum)
    10. {
    11. output = combination.Take(count).ToArray();
    12. return true;
    13. }
    14. }
    15. output = null;
    16. return false;
    17. }
    18. public static IEnumerable<List<T>> Permutate<T>(this IEnumerable<T> value)
    19. {
    20. var inputCount = value.Count();
    21. List<T>[] buffer = null;
    22. var oldBuffer = new List<T>[inputCount];
    23. for (int i = 0; i < inputCount; i++)
    24. oldBuffer[i] = new List<T>(new[] { value.ElementAt(i) });
    25. var bufferCount = inputCount;
    26. for (int count = 1; count < inputCount; count++)
    27. {
    28. buffer = new List<T>[bufferCount * (inputCount - count)];
    29. int index = 0;
    30. for (int i = 0; i < oldBuffer.Length; i++)
    31. foreach (var item in value)
    32. if (!oldBuffer[i].Contains(item))
    33. {
    34. var l = new List<T>(oldBuffer[i]);
    35. l.Add(item);
    36. buffer[index] = l;
    37. index++;
    38. }
    39. oldBuffer = buffer;
    40. bufferCount = oldBuffer.Count();
    41. }
    42. return buffer;
    43. }

    Ineffizientester Algo ever.
    Ich habe so verstanden, dass Fragen erlaubt sind.
    @Artentus
    Nachdem ich 10 Minuten mühsam durch deine "List" durchgestiegen bin :P , verstehe ich deinen Code so, dass du in "Permutate()" alle möglichen Kombinationen erstellst und dann n Elemente davon addierst?
    Warum übergibst du die Anzahl der Elemente in Kombinationen nicht gleich mit und hast du nur die Kombinationen mit der richtigen Anzahl der Elemente?
    @Artentus: Oh ja, in der Ineffizienz-Kategorie wäre das ziemlich weit vorne. :D

    Hier ist meine Lösung:
    Spoiler anzeigen

    C-Quellcode

    1. private static void Main(string[] args)
    2. {
    3. List<int> input = new List<int>(new int[] { 4, 11, 1, 7, 3, 5, 2 });
    4. List<int> result = new List<int>();
    5. ShutTheBox(result, 18, 5, input);
    6. }
    7. private static bool ShutTheBox(List<int> result, int s, int n, List<int> input, int nextindex = 0)
    8. {
    9. if (n == 0) return false;
    10. while (true) {
    11. if (n > input.Count - nextindex) return false;
    12. int i = input[nextindex];
    13. if (s - i < 0) {
    14. nextindex++;
    15. } else if (s - i == 0) {
    16. if (n == 1) {
    17. result.Add(i);
    18. break;
    19. } else {
    20. nextindex++;
    21. }
    22. } else {
    23. if (!ShutTheBox(result, s - i, n - 1, input, nextindex + 1)) {
    24. nextindex++;
    25. } else {
    26. result.Add(i);
    27. break;
    28. }
    29. }
    30. }
    31. return true;
    32. }

    Das Ergebnis gibt es in der result-Liste, obs geklappt hat wird von der Funktion selbst zurückgegeben.

    lg SeriTools
    | Keine Fragen per PN oder Skype.
    Ditto. Valide IEnumerable<T>
    Lang.

    C-Quellcode

    1. static void Main( string[] args ) {
    2. int[] val = new int[] { 4, 11, 1, 7, 3, 5, 2 };
    3. foreach ( var item in ShutTheBox( 18, 5, val ) ) {
    4. Console.WriteLine( "{0}; {1}", item.Sum( sum => (int) sum ), string.Join( ", ", item ) );
    5. }
    6. Console.ReadLine();
    7. }
    8. static IEnumerable<IEnumerable<int>> ShutTheBox( int sum, int count, int[] input ) {
    9. return Populate( input, count, count ).Select(
    10. item => item.Distinct()
    11. ).Where(
    12. item => item.Count() == count && item.Sum() == sum
    13. ).Select(
    14. item => item.OrderBy( order => order )
    15. ).Distinct( new IntArrayComparer() ).OrderBy(
    16. item => item.Sum()
    17. );
    18. }
    19. static IEnumerable<T> PopulateList<T>( T value, int count ) {
    20. for ( int i = 0; i < count; i++ )
    21. yield return value;
    22. }
    23. static IEnumerable<IEnumerable<T>> Populate<T>( T[] value, int min, int max ) {
    24. var digits = PopulateList( 0, min ).ToList();
    25. var rotatingDigit = 0;
    26. while ( rotatingDigit < max ) {
    27. List<T> tmp = new List<T>();
    28. if ( rotatingDigit == digits.Count )
    29. digits.Add( 0 );
    30. for ( int k = digits.Count - 1; k >= 0; k-- )
    31. tmp.Add( value[digits[k]] );
    32. yield return tmp;
    33. for ( rotatingDigit = 0; rotatingDigit < digits.Count; rotatingDigit++ ) {
    34. digits[rotatingDigit]++;
    35. if ( digits[rotatingDigit] < value.Length )
    36. break;
    37. digits[rotatingDigit] = 0;
    38. }
    39. }
    40. }
    41. public class IntArrayComparer : IEqualityComparer<IEnumerable<int>> {
    42. public bool Equals( IEnumerable<int> x, IEnumerable<int> y ) {
    43. if ( x == null || y == null )
    44. return x == y;
    45. return x.OrderBy( item => item ).SequenceEqual( y.OrderBy( item => item ) );
    46. }
    47. public int GetHashCode( IEnumerable<int> obj ) {
    48. if ( obj == null )
    49. throw new ArgumentNullException();
    50. int iHash = 0;
    51. for ( int i = 0; i < obj.Count(); ++i )
    52. iHash ^= ( obj.ElementAt( i ) << ( ( 0x03 & i ) << 3 ) );
    53. return iHash;
    54. }
    55. }

    Sollten Fehler enthalten sein: passiert. Bei deinem Beispiel funktioniert es bestens.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „AliveDevil“ () aus folgendem Grund: Formatierung angepasst.

    @sonne75
    Nein, denn in diesem Fall wird später in der Liste noch einmal eine Liste vorhanden sein, bei der die gesuchte Kombination vorne steht. Das macht den Algo ja auch so ineffizient, weil wirklich alle Möglichen Permutationen berechnet werden und dann erst verglichen wird.

    Edit: Ich werde den Code mal etwas optimieren.
    Hier jetzt noch mal der überarbeitete Code:
    Spoiler anzeigen

    C-Quellcode

    1. public static bool ShutTheBox(int sum, int count, int[] input, out int[] output)
    2. {
    3. List<int>[] buffer = null;
    4. var oldBuffer = new List<int>[input.Length];
    5. for (int i = 0; i < input.Length; i++)
    6. oldBuffer[i] = new List<int>(new[] { input[i] });
    7. var bufferCount = input.Length;
    8. for (int c = 1; c < count; c++)
    9. {
    10. buffer = new List<int>[bufferCount * (input.Length - c)];
    11. int index = 0;
    12. for (int i = 0; i < oldBuffer.Length; i++)
    13. foreach (var item in input)
    14. if (!oldBuffer[i].Contains(item))
    15. {
    16. var l = new List<int>(oldBuffer[i]);
    17. l.Add(item);
    18. if (c + 1 == count && l.Sum() == sum)
    19. {
    20. output = l.ToArray();
    21. return true;
    22. }
    23. buffer[index] = l;
    24. index++;
    25. }
    26. oldBuffer = buffer;
    27. bufferCount = oldBuffer.Count();
    28. }
    29. output = null;
    30. return false;

    Die Funktion wird failen, wenn "count" < 2 ist oder wenn "count" > input.Length ist. Allerdings halte ich das auch für keine Eingabe, die man erhalten sollte.
    Na schön, du Halsabschneider. :P ;)
    Spoiler anzeigen

    C-Quellcode

    1. if (count < 1 || count > input.Length)
    2. {
    3. output = null;
    4. return false;
    5. }
    6. if (count == 1)
    7. {
    8. foreach (var item in input)
    9. if (item == sum)
    10. {
    11. output = new[] { item };
    12. return true;
    13. }
    14. output = null;
    15. return false;
    16. }
    17. List<int>[] buffer = null;
    18. var oldBuffer = new List<int>[input.Length];
    19. for (int i = 0; i < input.Length; i++)
    20. oldBuffer[i] = new List<int>(new[] { input[i] });
    21. var bufferCount = input.Length;
    22. for (int c = 1; c < count; c++)
    23. {
    24. buffer = new List<int>[bufferCount * (input.Length - c)];
    25. int index = 0;
    26. for (int i = 0; i < oldBuffer.Length; i++)
    27. foreach (var item in input)
    28. if (!oldBuffer[i].Contains(item))
    29. {
    30. var l = new List<int>(oldBuffer[i]);
    31. l.Add(item);
    32. if (c + 1 == count && l.Sum() == sum)
    33. {
    34. output = l.ToArray();
    35. return true;
    36. }
    37. buffer[index] = l;
    38. index++;
    39. }
    40. oldBuffer = buffer;
    41. bufferCount = oldBuffer.Count();
    42. }
    43. output = null;
    44. return false;
    45. }
    Ich habe den Code nochmal durch n eigenen Performance-Counter gejagt:

    C-Quellcode

    1. static IEnumerable<TimeSpan> performanceCounter( int iterations, TextWriter writer ) {
    2. int[] val = new int[] { 4, 11, 1, 7, 3, 5, 2 };
    3. DateTime start;
    4. for ( int i = 0; i < iterations; i++ ) {
    5. writer.WriteLine( "Iteration: {0}", i );
    6. var query = ShutTheBox( 18, 5, val );
    7. start = DateTime.Now;
    8. List<IEnumerable<int>> bytes = query.ToList();
    9. yield return DateTime.Now - start;
    10. }
    11. }

    ich glaube, dass Ergebnis kann sich sehen lassen:

    Quellcode

    1. Avg.: 00:00:00.0130000, Max.: 00:00:00.0350379, Min.: 00:00:00.0119579
    2. Time: 00:00:01.3110441, Iterations: 100

    Bei folgendem Code:

    C-Quellcode

    1. int iterations = 100;
    2. List<TimeSpan> times = performanceCounter( iterations, Console.Out ).ToList();
    3. Console.WriteLine( "Avg.: {0}, Max.: {1}, Min.: {2}", TimeSpan.FromMilliseconds( times.Average( item => item.Milliseconds ) ), times.Max( item => item ), times.Min( item => item ) );
    4. Console.WriteLine( "Time: {0}, Iterations: {1}", TimeSpan.FromTicks( times.Sum( item => item.Ticks ) ), iterations );
    Spoiler anzeigen

    C-Quellcode

    1. static void Main(string[] args)
    2. {
    3. var sum = 18;
    4. var num = 5;
    5. var input = new[] { 4, 11, 1, 7, 3, 5, 2 };
    6. int[] output = null;
    7. Console.WriteLine("Sum = " + sum + ", Num = " + num + ", Input = " + string.Join(", ", input));
    8. Console.Write("Result = " + ShutTheBoxFast(sum, num, input, out output));
    9. if (output != null)
    10. {
    11. Console.WriteLine(", " + string.Join(", ", output));
    12. }
    13. else
    14. {
    15. Console.WriteLine();
    16. }
    17. Console.ReadKey();
    18. }
    19. class LolNoException : Exception
    20. {
    21. }
    22. static bool ShutTheBoxFast(int sum, int num, int[] input, out int[] output)
    23. {
    24. if (input.Length < num)
    25. {
    26. throw new LolNoException();
    27. }
    28. var rnd = new Random();
    29. const long max = 4000000000;
    30. for (var k = 0; k < max; ++k)
    31. {
    32. var done = new bool[input.Length];
    33. output = new int[num];
    34. for (var i = 0; i < num; ++i)
    35. {
    36. var temp = rnd.Next(0, input.Length - 1);
    37. while (done[temp])
    38. {
    39. temp = rnd.Next(0, input.Length - 1);
    40. }
    41. output[i] = input[temp];
    42. done[temp] = true;
    43. }
    44. if (output.Sum() == sum)
    45. {
    46. return true;
    47. }
    48. }
    49. output = null;
    50. return false;
    51. }


    Spoiler anzeigen
    Mit n = 2 gehts sehr schnell :-)
    Laufzeit bei n >= 4: lim ∞


    Ich poste morgen mal eine ernsthafte Lösung :-D
    To make foobar2000 a real random music player, I figured out the only way to achieve this is to use Windows Media Player.

    At some point in time, you recognize that knowing more does not necessarily make you more happy.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Chrisber“ ()

    Also ~blaze~ hat mich ja gelehrt (;)), dass man immer eine bestimme Zeit festlegen und dann die Durchläufe, die darin möglich sind, zählen soll, da die Stopwatch sonst zu ungenau wird.
    Mein Code schafft z.B. ungefähr 2400 Durchlaufe in einer Sekunde. (@SeriTools: 8| )
    Ich habs so getestet:

    C-Quellcode

    1. List<int> input = new List<int>(new int[] { 4, 11, 1, 7, 3, 5, 2 });
    2. Stopwatch stop = new Stopwatch();
    3. stop.Start();
    4. for (int i = 0; i < 1000000; i++) {
    5. List<int> result = new List<int>();
    6. bool success = ShutTheBox(result, 18, 5, input);
    7. }
    8. stop.Stop();
    9. Console.Write(stop.ElapsedMilliseconds);


    Habe die Zeit und die Durchläufe übrigens nochmal angepasst, hatte nen Fehler im Aufruf drin.

    EDIT: Grad von Debug auf Release gestellt, jetzt sinds 480ms für 1mio Durchläufe :D
    Wenn man die List<T> nur Cleart und nicht immer neu erstellt bin ich bei 430ms.

    EDIT2: Von List<T> auf Array umgestellt:
    Spoiler anzeigen

    C-Quellcode

    1. private static void Main(string[] args)
    2. {
    3. List<int> input = new List<int>(new int[] { 4, 11, 1, 7, 3, 5, 2 });
    4. int[] input2 = new int[] { 4, 11, 1, 7, 3, 5, 2 };
    5. Stopwatch stop = new Stopwatch();
    6. stop.Start();
    7. List<int> result = new List<int>();
    8. for (int i = 0; i < 1000000; i++) {
    9. result.Clear();
    10. bool success = ShutTheBox(result, 18, 5, input);
    11. }
    12. stop.Stop();
    13. Console.WriteLine(stop.ElapsedMilliseconds);
    14. stop.Reset();
    15. stop.Start();
    16. for (int i = 0; i < 1000000; i++) {
    17. int[] result2 = new int[5];
    18. bool success = ShutTheBox2(result2, 18, 5, input2);
    19. }
    20. stop.Stop();
    21. Console.WriteLine(stop.ElapsedMilliseconds);
    22. Console.ReadKey(true);
    23. }
    24. private static bool ShutTheBox(List<int> result, int s, int n, List<int> input, int nextindex = 0)
    25. {
    26. if (n == 0) return false;
    27. while (true) {
    28. if (n > input.Count - nextindex) return false;
    29. int i = input[nextindex];
    30. if (s - i < 0) {
    31. nextindex++;
    32. } else if (s - i == 0) {
    33. if (n == 1) {
    34. result.Add(i);
    35. return true;
    36. } else {
    37. nextindex++;
    38. }
    39. } else {
    40. if (!ShutTheBox(result, s - i, n - 1, input, nextindex + 1)) {
    41. nextindex++;
    42. } else {
    43. result.Add(i);
    44. return true;
    45. }
    46. }
    47. }
    48. }
    49. private static bool ShutTheBox2(int[] result, int s, int n, int[] input, int nextindex = 0)
    50. {
    51. if (n == 0) return false;
    52. while (true) {
    53. if (n > input.Length - nextindex) return false;
    54. int i = input[nextindex];
    55. if (s - i < 0) {
    56. nextindex++;
    57. } else if (s - i == 0) {
    58. if (n == 1) {
    59. result[n - 1] = i;
    60. return true;
    61. } else {
    62. nextindex++;
    63. }
    64. } else {
    65. if (!ShutTheBox2(result, s - i, n - 1, input, nextindex + 1)) {
    66. nextindex++;
    67. } else {
    68. result[n - 1] = i;
    69. return true;
    70. }
    71. }
    72. }
    73. }


    ShutTheBox() ist List<T>, ShutTheBox2() ist Arraybasiert.

    1Mio. Durchläufe:
    ShutTheBox(): 430ms
    ShutTheBox2(): ~210ms

    EDIT3: Code noch einmal etwas verkürzt (Danke an @nikeee13:)
    EDIT4: Noch n bisschen optimiert.
    Spoiler anzeigen

    C-Quellcode

    1. private static bool ShutTheBox2(int[] result, int s, int n, int[] input, int nextindex = 0)
    2. {
    3. if (n == 0) return false;
    4. while (n <= input.Length - nextindex) {
    5. int i = input[nextindex];
    6. if (s == i) {
    7. if (n == 1) {
    8. result[n - 1] = i;
    9. return true;
    10. }
    11. nextindex++;
    12. } else {
    13. nextindex++;
    14. if (s >= i) {
    15. if (ShutTheBox2(result, s - i, n - 1, input, nextindex)) {
    16. result[n - 1] = i;
    17. return true;
    18. }
    19. }
    20. }
    21. }
    22. return false;
    23. }
    | Keine Fragen per PN oder Skype.

    Dieser Beitrag wurde bereits 10 mal editiert, zuletzt von „SeriTools“ ()