2010-11-21 7 views
1

Dans Haskell, lorsque vous utilisez : de manière répétée pour créer une séquence, l'itération du résultat prend l'heure O (N).La complexité de Concat()

1:2:3:4:[] 

Si j'essaie de faire quelque chose de similaire dans LINQ, je peux utiliser Concat(), mais itérer le résultat prend O (N ²) du temps, parce que quand j'appelle MoveNext() sur le recenseur résultant pour n -le temps, il doit passer par n couches de Concat() s.

new[] { 1 }.Concat(new[] { 2 }).Concat(new[] { 3 }).Concat(new[] { 4 }) 

ou

new[] { 1 }.Concat(new[] { 2 }.Concat(new[] { 3 }.Concat(new[] { 4 }))) 

Comment puis-je faire, de sorte qu'il est rapide (par exemple linéaire) et fonctionnellement « propre » (à savoir sans utiliser List<T>)? Peut-être en écrivant IConcatenable<T> avec sa propre surcharge de Concat()? Ou ai-je tort d'utiliser Concat() de cette façon est quadratique?

Répondre

3

L'opération LINQ Concat() est en aucune façon équivalente à l'opération cons fonctionnelle. Ils sont très différents. Pour chaque opération contre, une "nouvelle" liste est créée. C'est en fait rapide puisque les structures de données en jeu sont conçues spécifiquement pour cet usage. Pour chaque opération Concat, un nouvel itérateur est créé et n'est pas vraiment destiné à ce type d'utilisation. Pour illustrer ce qui se fait à chaque fois, prenons l'exemple suivant plus court:

1:2:3:[] 

L'opération fonctionnelle consiste en quelques étapes pour évaluer. 3:[] résultats dans la liste temporaire [3], puis 2:[3] dans la liste [2,3] et 1:[2,3]. C'est 3 simples étapes juste pour créer la liste. Pour "itérer" à travers elle, il faudra une certaine récursivité et correspondance (ou quel que soit l'équivalent Haskell). Cela prendra quelque part dans le voisinage de 3 ou 4 plus complexes étapes (un pour chaque segment de liste).

new[] { 1 }.Concat(new[] { 2 }).Concat(new[] { 3 }); 

L'opération LINQ consiste à quelques pas trop à évaluer. new[] { 1 }.Concat(new[] { 2 }) donne un itérateur itératif à travers la séquence [[1]+[2]], et le dernier produit un itérateur itératif à travers la séquence [[[1]+[2]]+[3]]. Cela a pris deux étapes simples juste pour créer l'itérateur, mais vous devriez remarquer à quel point la séquence est réellement représentée. 5 itérateurs sont utilisés ici, un pour chaque tableau (3) et un pour chaque paire concaténée (2). Je ne passerai pas par toutes les étapes de l'itération, mais cela fait beaucoup plus d'appels de fonctions et d'accès aux propriétés (comme requis par chaque itérateur).

new[] { 1 }.Concat(new[] { 2 }.Concat(new[] { 3 })) 

L'opération LINQ qui ressemble plus structurellement équivalente reprend la même quantité d'étapes pour créer, ce qui donne [[1]+[[2]+[3]]] avec la même quantité de itérateurs et itérer tout aussi complexe si elle.

Vous pensez peut-être que la version fonctionnelle devrait être plus complexe car nous avons besoin de fonctions récursives.Eh bien, ce n'est pas parce que c'est ainsi que les choses sont faites dans les langages fonctionnels et qu'elles sont optimisées pour cet usage. Ils ont l'avantage d'utiliser des séquences immuables qui pourraient être réutilisées dans d'autres listes composées. Générer des séquences complexes en utilisant LINQ de cette manière n'est pas vraiment ce pour quoi il a été conçu. Il n'y a pas d'optimisations, par le langage (certaines par le JIT mais c'est tout) et ce n'est clairement pas la façon dont vous voulez parcourir une séquence. Vous frappez le droit sur la tête à mon humble avis sur la raison pour laquelle il est complexe.


Je pense que la meilleure approche pour tenter de meilleures performances consiste à créer des listes liées pour représenter la séquence concaténée. Vous pouvez soit utiliser la classe LinkedList<T>, créer vos propres structures de données qui rappellent une liste fonctionnelle ou mieux encore, utilisez le FSharpList<T> de F #. Puis étendez avec des méthodes d'extension et d'autres classes de support.

+0

Je réalise que 'Concat()' n'est pas équivalent à 'cons', mais c'est la chose la plus proche de LINQ. 'FSharpList ' semble exactement ce que je voulais. – svick

1

Vous pouvez essayer .AsParallel mais je doute la parallélisation est la peine à moins que vos séquences sont très grandes, fonctionne aussi uniquement sur les données indexables

+0

O (n * n) est identique à O (n²), c'est-à-dire quadratique. Y a-t-il une faute de frappe dans votre réponse? (Je ne sais rien sur LINQ, donc je ne peux pas commenter quelque chose de technique. :-)) –