2016-05-24 15 views
5

Sto cercando un modo per interrompere una funzione di livello superiore dopo aver valutato parte della sua sequenza di input.È possibile interrompere una valutazione breve di una funzione di livello superiore?

Considerare una situazione quando si cerca il primo indice in una sequenza che soddisfa una determinata condizione. Ad esempio, supponiamo che stiamo cercando la prima posizione in una matrice a di Int s dove la somma dei due valori consecutivi è superiore a 100.

Si può fare con un ciclo, come questo:

func firstAbove100(a:[Int]) -> Int? { 
    if a.count < 2 { 
     return nil 
    } 
    for i in 0..<a.count-1 { 
     if a[i]+a[i+1] > 100 { 
      return i 
     } 
    } 
    return nil 
} 

Il ciclo si interrompe non appena viene scoperta la posizione di interesse.

possiamo riscrivere questo codice usando reduce come segue:

func firstAbove100(a:[Int]) -> Int? { 
    if a.count < 2 { 
     return nil 
    } 
    return (0..<a.count-1).reduce(nil) { prev, i in 
     prev ?? (a[i]+a[i+1] > 100 ? i : nil) 
    } 
} 

Tuttavia, lo svantaggio di questo approccio è che reduce va tutta la strada fino a a.count-2 anche se si trova una corrispondenza al primo indice. Il risultato sarà lo stesso, ma sarebbe bello tagliare il lavoro non necessario.

C'è un modo per impedire a reduce di provare ulteriori corrispondenze o forse una funzione diversa che consente di fermarsi dopo aver trovato la prima corrispondenza?

+0

@paulvs Perché è copia-incasinato andato male :-) Grazie! – dasblinkenlight

+2

Non penso che ci sia un modo integrato per farlo con 'reduce'. L'intero punto di 'reduce' è che combina e valuta la * intera * sequenza. Per questo specifico caso d'uso, probabilmente dovresti scrivere la tua funzione, come hai fatto nel tuo primo blocco di codice. –

+1

'reduce' è solo un' forEach' che sta passando un valore alla successiva iterazione. Se si desidera cortocircuitare, è necessario utilizzare una funzione di ordine elevato che la supporta. O scrivi la tua versione di 'reduce'. In questo caso penso che l'uso di 'reduce' sia una scelta molto brutta in quanto non si sta effettivamente utilizzando l'accumulatore. – Sulthan

risposta

4

come già detto, reduce è specificamente progettato per valutare un'intera sequenza e quindi non progettati per corto circuito. Usandolo in questo modo per trovare l'indice di un elemento che soddisfa un dato predicato è meglio fare con indexOf come @Casey says.

A partire da Swift 3, ora è disponibile la funzione first(where:) su Sequence che consente di trovare il primo elemento che soddisfa un determinato predicato. Questa potrebbe essere un'alternativa ancora più adatta rispetto a indexOf, in quanto restituisce l'elemento anziché l'indice (sebbene nel tuo esempio particolare siano uguali).

Si potrebbe scrivere il vostro esempio come questo:

func firstAbove100(_ a:[Int]) -> Int? { 
    guard a.count > 1 else {return nil} 

    return (0..<a.count-1).first { i in 
     a[i]+a[i+1] > 100 
    } 
} 

Tuttavia, se si desidera una funzione di alto livello più generale che scorrere una sequenza e uscire se trova un risultato non nullo di un dato predicato - si può sempre scrivere il proprio find funzione:

extension SequenceType { 

    func find<T>(@noescape predicate: (Self.Generator.Element) throws -> T?) rethrows -> T? { 
     for element in self { 
      if let c = try predicate(element) {return c} 
     } 
     return nil 
    } 
} 

si può ora scrivere la funzione firstAbove100 in questo modo:

func firstAbove100(a:[Int]) -> Int? { 
    if a.count < 2 { 
     return nil 
    } 
    return (0..<a.count-1).find { i in 
     a[i]+a[i+1] > 100 ? i : nil 
    } 
} 

e sarà ora cortocircuito quando trova una coppia di elementi che aggiungono sopra 100.

Oppure diciamo invece di restituire l'indice della prima coppia di elementi nell'array che si aggiungono alla maggiore 100, ora si desidera restituire la somma degli elementi.Si potrebbe ora scrivere in questo modo:

func sumOfFirstAbove100(a:[Int]) -> Int? { 
    guard a.count > 1 else {return nil} 
    return (0..<a.count-1).find { i in 
     let sum = a[i]+a[i+1] 
     return sum > 100 ? sum : nil 
    } 
} 

let a = [10, 20, 30, 40, 50, 60, 70, 80, 90] 
print(sumOfFirstAbove100(a)) // prints: Optional(110) 

La funzione find sarà scorrere la matrice, applicando il predicato ad ogni elemento (in questo caso gli indici dell'array). Se il predicato restituisce nil, proseguirà con iterazione. Se il predicato restituisce non nulla, restituirà quel risultato e interromperà l'iterazione.

4

indexOf si fermerà dopo che trova la prima partita così si potrebbe riscrivere firstAbove100 a qualcosa di simile:

func firstAbove100(a:[Int]) -> Int? { 
    return a.count > 1 ? (a.startIndex..<a.endIndex-1).indexOf({ a[$0] + a[$0 + 1] > 100 }) : nil 
} 
Problemi correlati