L'opérateur :: dans F # ajoute toujours des éléments à la liste. Y at-il un opérateur qui ajoute à la liste? Je suppose que l'utilisation de l'opérateur @
[1; 2; 3] @ [4]
serait moins efficace que d'ajouter un élément.
Comme d'autres l'ont dit, il n'y a pas d'opérateur de ce type, car cela n'aurait pas beaucoup de sens. Je pense en fait que c’est une bonne chose, car il est plus facile de réaliser que l’opération ne sera pas efficace. En pratique, vous ne devriez pas avoir besoin de l'opérateur, il existe généralement un meilleur moyen d'écrire la même chose.
Scénario typique: Je pense que le scénario typique dans lequel vous pourriez penser que vous devez ajouter des éléments à la fin est si courant qu’il peut être utile de le décrire.
L'ajout d'éléments à la fin semble nécessaire lorsque vous écrivez une version d'une fonction récursive à l'aide du paramètre accumulator . Par exemple, une implémentation (inefficace) de la fonction filter
pour les listes ressemblerait à ceci:
let filter f l =
let rec filterUtil acc l =
match l with
| [] -> acc
| x::xs when f x -> filterUtil (acc @ [x]) xs
| x::xs -> filterUtil acc xs
filterUtil [] l
À chaque étape, nous devons ajouter un élément à l'accumulateur (qui stocke les éléments à renvoyer en tant que résultat). Ce code peut être facilement modifié pour utiliser l'opérateur ::
au lieu d'ajouter des éléments à la fin de la liste acc
:
let filter f l =
let rec filterUtil acc l =
match l with
| [] -> List.rev acc // (1)
| x::xs when f x -> filterUtil (x::acc) xs // (2)
| x::xs -> filterUtil acc xs
filterUtil [] l
Dans (2), nous ajoutons maintenant des éléments à l'avant de l'accumulateur et lorsque la fonction est sur le point de renvoyer le résultat, nous inversons la liste (1), ce qui est beaucoup plus efficace que d'ajouter des éléments un par un.
Les listes en fa # sont liées individuellement et immuables. Cela signifie que la première ligne est O(1) (créer un élément et qu’elle pointe vers une liste existante), alors que l’écran à l’arrière est O(N) (étant donné que répliqué; vous ne pouvez pas modifier le pointeur final existant, vous devez créer une nouvelle liste).
Si vous devez "ajouter un élément à l'arrière", par exemple, par exemple.
l @ [42]
est le moyen de le faire, mais c’est une odeur de code.
Le coût de l’ajout de deux listes standard est proportionnel à la longueur de la liste de gauche . En particulier, le coût de
xs @ [x]
est proportionnelle à longueur de xs
name__— c'est pas un coût constant.
Si vous voulez une abstraction semblable à une liste avec un ajout de temps constant, vous pouvez utiliser la représentation de fonction de John Hughes, que j'appellerai hlist
name__. Je vais essayer d'utiliser la syntaxe OCaml, qui, j'espère, est assez proche de F #:
type 'a hlist = 'a list -> 'a list (* a John Hughes list *)
let empty : 'a hlist = let id xs = xs in id
let append xs ys = fun tail -> xs (ys tail)
let singleton x = fun tail -> x :: tail
let cons x xs = append (singleton x) xs
let snoc xs x = append xs (singleton x)
let to_list : 'a hlist -> 'a list = fun xs -> xs []
L'idée est que vous représentez fonctionnellement une liste en tant que fonction du "reste des éléments" à "la liste finale". Cela fonctionne si vous voulez créer toute la liste avant de regarder l'un des éléments. Sinon, vous devrez gérer le coût linéaire de l'ajout ou utiliser entièrement une autre structure de données.
J'imagine que l'utilisation de l'opérateur @ [...] serait moins efficace que l'ajout d'un élément.
Si c'est le cas, la différence sera négligeable. L'ajout d'un seul élément et la concaténation d'une liste à la fin sont des opérations O(n)
. En fait, je ne peux pas penser à une seule chose que @
doit faire, qu'une fonction d'ajout d'élément unique ne ferait pas.
Peut-être que vous souhaitez utiliser une autre structure de données. Nous avons des files d'attente doubles (ou "Deques" courtes) dans fsharpx . Vous pouvez en savoir plus à leur sujet à http://jackfoxy.com/double-ended-queues-for-fsharp
L'efficacité (ou l'absence de) provient de parcourir la liste pour trouver l'élément final. Donc, déclarer une nouvelle liste avec [4]
sera négligeable pour tous les scénarios, sauf les plus triviaux.
Essayez d'utiliser une file d'attente à double extrémité au lieu de liste. J'ai récemment ajouté 4 versions de deques (orthographe d'Okasaki) à FSharpx.Core (disponible via NuGet. Code source sur FSharpx.Core.Datastructures ). Voir mon article sur l'utilisation de dequeus Files d'attente doubles pour F #
J'ai suggéré à l'équipe F # l'opérateur contre, ::, et que le discriminateur de modèle actif soit mis à disposition pour d'autres structures de données avec une signature tête/queue. 3