È una cattiva idea usare la variabile flag per cercare l'elemento MAX nella matrice?

6

Durante la mia carriera di programmatore ho preso l'abitudine di introdurre una variabile flag che indica che il primo confronto è avvenuto, proprio come fa Msft nella sua implementazione del metodo di estensione linq Max ()

public static int Max(this IEnumerable<int> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    int num = 0;
    bool flag = false;
    foreach (int num2 in source)
    {
        if (flag)
        {
            if (num2 > num)
            {
                num = num2;
            }
        }
        else
        {
            num = num2;
            flag = true;
        }
    }
    if (!flag)
    {
        throw Error.NoElements();
    }
    return num;
}

Tuttavia ultimamente ho incontrato alcuni eretici, che lo implementano iniziando solo dal primo elemento e assegnandolo al risultato, e oh no - si è scoperto che gli autori di STL e Java hanno preferito quest'ultimo metodo.

Java:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
Iterator<? extends T> i = coll.iterator();
T candidate = i.next();

    while (i.hasNext()) {
    T next = i.next();
    if (next.compareTo(candidate) > 0)
    candidate = next;
}
return candidate;
}

STL:

template<class _FwdIt> inline
_FwdIt _Max_element(_FwdIt _First, _FwdIt _Last)
{   // find largest element, using operator<
_FwdIt _Found = _First;
if (_First != _Last)
    for (; ++_First != _Last; )
        if (_DEBUG_LT(*_Found, *_First))
            _Found = _First;
return (_Found);
}

Ci sono delle preferenze tra un metodo o l'altro? Ci sono ragioni storiche per questo? Un metodo è più pericoloso di un altro?

UPDATE : Si noti che la versione java è in grado di generare eccezioni con la riga T candidate = i.next(); ed è possibile ottenere lo stesso comportamento in C # con la clausola di guardia come mostrato nel secondo parte della risposta di @ratchet freak.

    
posta Boris Treukhov 15.11.2011 - 21:40
fonte

3 risposte

2

essere totalmente radicali che puoi fare:

public static int Max(this IEnumerable<int> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }

    int num = int.MinValue;
    foreach (int num2 in source)
    {
        if (num2 > num)
        {
            num = num2;
        }
    }
    return num;
}

definendo il massimo di un set vuoto da int.Minvalue

ma è possibile eseguire l'approccio java / stl con c #

public static int Max(this IEnumerable<int> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if(!source.MoveNext()){
        throw throw Error.NoElements();
    }
    int num = source.Current;
    foreach (int num2 in source)
    {
        if (num2 > num)
        {
            num = num2;
        }
    }
    return num;
}

sebbene YMMV sia più chiaro o meno

    
risposta data 15.11.2011 - 23:11
fonte
5

Potrebbero esserci preferenze per l'una o per l'altra; ma una distinzione chiave qui è che i metodi non sono equivalenti in semantica. In particolare nella gestione delle liste vuote.

La versione LINQ deve restituire un valore e, pertanto, se l'elenco è vuoto genera un'eccezione.

La versione C ++, d'altra parte, restituisce un iteratore. L'iteratore potrebbe puntare a end () della raccolta, quindi ogni chiamata deve essere controllata come non-end-ness prima di usare il risultato.

Quindi la risposta qui è molto più su come ti aspetti di usare la funzione, piuttosto che sulla sua implementazione. Il pericolo è negli occhi di chi guarda. Le eccezioni rispetto al rendimento e alle aspettative sono probabilmente appropriate alle rispettive filosofie di implementazione.

    
risposta data 15.11.2011 - 21:50
fonte
1

Personalmente non ho mai sviluppato un modello per fare sempre in un modo o per farlo in un altro modo. Per me è sempre un dato di fatto che dato tutto il resto costante, più linee di codice e più variabili locali aumentano la complessità del codice. Non di molto, ma a poco a poco, finiamo con le funzioni che vengono discusse vicino al refrigeratore d'acqua.

Quindi scelgo sempre un approccio che minimizzi l'uso di variabili locali e l'uso di condizionali quando possibile, dato che posso ridurre il codice senza sacrificare alcuna chiarezza. Ma la chiarezza vincerebbe sempre usando una variabile locale extra.

Nell'esempio che hai fornito, probabilmente lo codificherei senza usare la bandiera, ma alla fine si tratta di uno stile personale, quindi non direi che è sbagliato in un modo o nell'altro.

    
risposta data 15.11.2011 - 22:58
fonte

Leggi altre domande sui tag