Quando si progetta una struttura dati, dovrei implementare operazioni molto inefficienti per comodità?

3

(Ho aggiunto i tag .NET perché le strutture dati sono per .NET, e questa domanda dovrebbe essere considerata nel contesto delle convenzioni per quella piattaforma.)

Sto scrivendo una libreria di strutture dati immutabili e persistenti per .NET. Una delle mie strutture dati, un tipo di vettore, supporta le seguenti operazioni principali:

  1. Aggiunta e rimozione alla fine
  2. Ricerca e aggiornamento per indice
  3. Avvio della sottosequenza
  4. (Cose non interessanti come la lunghezza, ecc.)

Inoltre, vi è un'implementazione speciale di operazioni di massa (che coinvolgono sequenze, come l'aggiunta di molti elementi) che è estremamente veloce per i grandi input. Questo rende l'aggiunta di molti elementi alla fine molto, molto velocemente.

Il mio problema è un po 'più complicato rispetto al titolo della domanda. Posso implementare le seguenti operazioni aggiuntive:

  1. Aggiunta di molti elementi all'inizio
  2. Inserimento di molti elementi nel mezzo

L'aggiunta di molti elementi alla fine è O(m + logn) , dove m è il numero di elementi da aggiungere. Queste operazioni sarebbero O(n + m) , e se m = n allora sono quasi veloci come aggiunte alla fine. In pratica, sarebbero più veloci di molti ordini di grandezza rispetto a quando l'utente li avesse implementati in modo ingenuo. Tuttavia, per un numero ridotto di elementi, sarebbero comunque molto lenti.

Il mio primo problema è che le normali operazioni sono così veloci che potrebbero confondere gli utenti e indurli a pensare che queste operazioni aggiuntive funzionino in modo simile. A differenza delle raccolte regolari in cui anche List<T>.Insert è veloce, questo può effettivamente rallentare l'applicazione. Come devo affrontare questo?

Il mio secondo problema è che, se implemento queste operazioni aggiuntive, dovrei anche fornire operazioni simili (estremamente inefficienti) che funzionano con singoli elementi? Sembra strano non fornire queste operazioni apparentemente più semplici, e sono sicuro che saranno a portata di mano (a volte le prestazioni non sono affatto importanti), ma quel fattore di sorpresa è ancora peggio qui, dal momento che queste operazioni non possono mai essere efficienti.

Una considerazione simile si applica a un'operazione Skip (terminando la sottosequenza).

    
posta GregRos 25.03.2015 - 03:14
fonte

1 risposta

5

Addition of many elements to the end is O(m + logn), where m is the number of elements to be added. These operations would be O(n + m), and if m = n then they are almost as fast as addition to the end. In practice, they would be many orders of magnitude faster than if the user implemented them naively. However, for small numbers of elements, they would still be very slow.

Per me, il fatto che l'utente non possa semplicemente ottenere prestazioni simili per queste operazioni senza accesso al codice sorgente della struttura dati è una ragione sufficiente. Fare altrimenti punisce gli utenti che effettivamente hanno bisogno delle prestazioni senza dare grandi vantaggi a quelli che non lo fanno.

Posso vedere da dove vieni; c'è sicuramente qualcosa da dire per seguire il principio della meno sorpresa. Tuttavia, non penso ci sia nulla di sorprendente nell'inserimento all'inizio di un vettore più lento dell'inserimento alla fine e gli utenti che non si sono preoccupati di leggere della complessità temporale delle operazioni non sono in grado di lamentarsi di loro . Inoltre, indipendentemente da quanto sia più lento l'inserimento all'inizio, potrebbe non essere il collo di bottiglia delle prestazioni nell'applicazione dell'utente, o l'utente potrebbe non creare mai vettori abbastanza grandi da rendere la differenza importante.

Ciò che sarebbe sorprendente è se l'utente possa implementare un inserimento più veloce di te, nonostante non abbia accesso alle interiora della struttura dati.

    
risposta data 25.03.2015 - 15:39
fonte

Leggi altre domande sui tag