Trovare il maggiore di due numeri è banale:

public int Max(int a, int b)
{
    return Math.Max(a, b);
}

oppure:

public int Max(int a, int b)
{
    if(a >= b) return a;
    else return b;
}

Ma riusciresti a farlo senza mai usare un comparatore? Niente >, <, ==, nessun if/then/else e nessuno switch!

Ecco il testo:

Write a function that takes 2 integers and returns the greater
of them, or one of them if they are equal.
You should not use comparators such as >, <, ==, Math.Max(), if, then,
else, switch and the like.

Example:

Max(0, 0) => 0
Max(10, 0) => 10
Max(0, 10) => 10
Max(-1, 0) => 0
Max(-1, 1) => 1
Max(-10, -1) => -1

Vince l'approccio più originale.

Se vuoi pubblicare una soluzione, fai una pull request sul repository https://github.com/TicinoXP/code-games

Le nostre soluzioni

Emanuele

Emanuele ha trovato una soluzione che sfrutta il valore assoluto. Dal momento che:

      | + a if a >= 0
      |
|a|  <
      |
      | - a if a < 0

Math.Abs() può essere sfruttato per realizzare un if/else. La sua soluzione è super concisa, e funziona sia con i numeri positivi che con quelli negativi:

public int Max(int a, int b) => (a + b + Math.Abs(a - b) ) /2;

Stefano

A chi dovesse storcere la bocca all'idea di usare l'if/else nascosto dentro il valore assoluto piacerà l'enhancement proposto da Stefano: usare la radice del quadrato come alternativa al valore assoluto, che porta a questa soluzione:

public int Max(int a, int b) => (a + b + Math.Sqrt(Math.Pow(a - b, 2))) / 2 ;

Raffaele

Anche Raffaele ha trovato il modo di evitare l'uso del valore assoluto, e ha sfruttato il calcolo del segno di un'espressione. La sua soluzione è qui

Era partito da questa formula:

public static void comparator(int a, int b) {
    return (a / 2) * (Math.signum(a - b) + 1) + b * Math.signum(b - a);
}

ma poi, rendendosi conto che non funzionava nel caso in cui a e b fossero uguali, l'ha elaborata così:

Tradotta in Java:

public int comparator(int a, int b) {
  return (int)
    (a * (Math.signum(a - b) + 1) / 2 +
    b * (Math.signum(b - a) + 1) / 2 -
    (Math.signum(a - b) + Math.signum(b - a)) / 2);
}

che, opportunamente semplificata, è diventata:

public int comparator(int a, int b) {
    return (int) (a + b + Math.signum(a - b) * (a - b)) /2;
}

Leonardo

La soluzione di Leonardo sfrutta invece un trucco completamente diverso:

public int Max(int number1, int number2)
{
  try
  {
    var result = number1 - number2;
    Convert.ToUInt32(result);

    return number1;
  }
  catch
  {
    return number2;
  }
}

Un applauso per la fantasia!!!

Giuseppe Lombardi

Un'altra soluzione davvero astuta e fantasiosa la propone Giuseppe: se si accodano in un array a ripetuto a volte e b ripetuto b volte, l'elemento al centro dell'array sarà il maggiore:

a = 7
b = 4

  0    1    2    3    4    5    6    7    8
=============================================
| 7 | 7 | 7 | 7 | 7 | 7 | 7 | 4 | 4 | 4 | 4 |
=============================================
                      ^
                    centro

Non è geniale?

Il codice è:

public short Max(short a, short b)
{
    var list = new List<uint>();

    var uintA = (ushort)(short.MaxValue + a);
    for (var i = 0; i < uintA; i++)
        list.Add(uintA);

    var uintB = (ushort)(short.MaxValue + b);
    for (var i = 0; i < uintB; i++)
        list.Add(uintB);

    return (short)(list[((uintA + uintB) / 2)] - short.MaxValue);
}

L'uso di short.MaxValue è un altro trucco furbo per fare in modo che l'agoritmo funzioni anche con i numeri negativi: sommando ai valori in ingresso short.MaxValue si traslano tutti i casi nel dominio dei numeri positivi.

Arialdo

Esiste una piccola variante della soluzione di Giuseppe: si inseriscono in un array a volte a e b volte b, e poi si estraggono b elementi da sinistra; il successivo contiene il max:

a = 7
b = 4

  0    1    2    3    4    5    6    7    8
=============================================
| 7 | 7 | 7 | 7 | 7 | 7 | 7 | 4 | 4 | 4 | 4 |
=============================================
<----------- a  ------------><------ b ----->
<------ b -----> ^
                 max

Questa versione ha il vantaggio di poter essere implementata con una singola espressione LINQ:

public int Max(int a, int b)
{
    return Enumerable.Repeat(a, a).Concat(Enumerable.Repeat(b, b).Skip(b).ToList()[0];
}

Arialdo ha trovato un secondo approccio, molto più convoluto e prolisso, e molto meno efficiente. L'idea è sfruttare il confronto tra bit e una funzione ricorsiva.

Si prendono i due numeri e li si traducono in binario:

a = 1 1 0 1 0 1 0 1 0 1
b =       1 1 1 0 0 1 1

Si aggiungono all'occorrenza dei trailing 0:

a = 1 1 0 1 0 1 0 1 0 1
b = 0 0 0 1 1 1 0 0 1 1

e si iniziano a confrontare i bit partendo da quelli più significativi: se un numero ha il bit a 1 mentre l'altro lo ha a 0, è facile capire quale dei due sia maggiore:

public bool IsBigger(bool a, bool b) => a && !b;

Se i due bit sono uguali

public bool AreEqual(bool a, bool b) =>  a && b || (!a && !b);

si deve procedere ricorsivamente.

Il problema della ricorsione è che ha bisogno di valutare la condizione di uscita, altrimenti non terminerebbe mai. Mentre è facile calcolare la condizione di uscita (non devono esserci altri numeri da valutare):

public static bool ThereAreOtherItems(List<bool> ab) => ToBoolean(ab.Count());

è un po' più difficile valutarla, perché richiederebbe un if. Il trucco è sfruttare la short-circuit evaluation degli operatori booleani: in pratica, in un or il secondo elemento non viene valutato se il primo è già true. L'algoritmo che ne risulta è:

public bool AIsBigger(List<bool> ab, List<bool> bb)
{
     var headA = ab.First();
     var headB = bb.First();
     var tailA = ab.Skip(1).ToList();
     var tailB = bb.Skip(1).ToList();
     var aIsBigger = IsBigger(headA, headB);
     var areEqual = AreEqual(headA, headB);
     var thereAreOtherItems = ThereAreOtherItems(tailA);
     return
        (
            aIsBigger
        )
        ||
        (
            areEqual
            &&
            (
                (thereAreOtherItems && AIsBigger(tailA, tailB))
                ||
                !thereAreOtherItems
            )
        );
 }

Fa schifo, ma funziona!