2010-02-16 6 views
4

Lisez l'édition ci-dessous pour plus d'informations.Optimized Generic List Split

J'ai un code ci-dessous que j'utilise pour diviser une liste générique d'objet lorsque l'élément est d'un certain type. Je n'ai pas une question claire autant que je suis curieux si quelqu'un sait de quelle façon je peux optimiser ce code. Je l'appelle de nombreuses fois et il semble être tout à fait la bête aussi loin que les cycles d'horloge vont. Des idées? Je peux aussi en faire un wiki si quelqu'un est intéressé, peut-être que nous pouvons garder une trace des derniers changements.

Mise à jour: J'essaie d'analyser des jetons spécifiques. C'est une liste d'autres classes de classes et de jetons. Le jeton a une propriété (enum) de TokenType. J'ai besoin de trouver toutes les classes Token et de les partager sur chacune d'entre elles.

{a b c T d e T f g h T i j k l T m} 

fendrait comme

{a b c}{d e}{f g h}{i j k l}{m} 

EDIT MISE À JOUR: Il semble que tous mes problèmes de vitesse viennent dans la création constante et l'ajout de listes génériques. Est-ce que quelqu'un sait comment je peux y arriver sans cela? Ceci est le profil de ce qui se passe si cela aide quelqu'un.

alt text http://i49.tinypic.com/1zvpcmq.png

+2

Pardonnez mon ignorance, mais quel problème ce code résout-il? –

+0

Wow ... avez-vous porté ce code à partir de python? Il doit y avoir une meilleure façon de faire cela. – Randolpho

+0

@Randolpho: Pas vraiment. – SLaks

Répondre

1

Ceci est le meilleur que je pouvais faire pour éliminer autant de temps d'allocation que possible pour la fonction (ne devrait allouer que quand il dépasse la capacité, ce qui ne devrait pas être plus que nécessaire pour créer la plus grande liste dans les résultats). J'ai testé cette implémentation et cela fonctionne comme vous l'avez décrit.

Veuillez noter que les résultats de la sous-liste précédente sont détruits lors de l'accès à la liste suivante dans le groupe.

public static IEnumerable<IEnumerable> Split(this IEnumerable tokens, TokenType type) 
{ 
    ArrayList currentT = new ArrayList(); 
    foreach (object list in tokens) 
    { 
     Token token = list as Token; 
     if ((token != null) && token.TokenType == type) 
     { 
      yield return currentT; 
      currentT.Clear(); 
      //currentT = new ArrayList(); <-- Use this instead of 'currentT.Clear();' if you want the returned lists to be a different instance 
     } 
     else if ((list is TokenType) && ((TokenType)list) == type) 
     { 
      yield return currentT; 
      currentT.Clear(); 
      //currentT = new ArrayList(); <-- Use this instead of 'currentT.Clear();' if you want the returned lists to be a different instance 
     } 
     else 
     { 
      currentT.Add(list); 
     } 
    } 
} 

EDIT Voici une autre version qui ne fait pas usage d'une autre liste à tous (ne devrait pas faire des allocations). Je ne sais pas à quel point cela va se comparer, mais cela fonctionne comme demandé (aussi je n'ai aucune idée de comment ça ira si vous essayez de mettre en cache un sous-tableau).

En outre, les deux nécessitent une instruction "using System.Collections" (en plus de l'espace de noms Generic).

private static IEnumerable SplitInnerLoop(IEnumerator iter, TokenType type) 
{ 
    do 
    { 
     Token token = iter.Current as Token; 
     if ((token != null) && token.TokenType == type) 
     { 
      break; 
     } 
     else if ((iter.Current is TokenType) && ((TokenType)iter.Current) == type) 
     { 
      break; 
     } 
     else 
     { 
      yield return iter.Current; 
     } 
    } while (iter.MoveNext()); 
} 

public static IEnumerable<IEnumerable> Split(this IEnumerable tokens, TokenType type) 
{ 
    IEnumerator iter = tokens.GetEnumerator(); 
    while (iter.MoveNext()) 
    { 
     yield return SplitInnerLoop(iter, type); 
    } 
} 
+1

Je suggère également que si cette fonction semble être votre goulot d'étranglement, alors vous obtiendrez probablement une meilleure amélioration des performances en essayant de trouver un meilleur algorithme pour la tâche globale que vous essayez d'effectuer, ou que vous devriez mettre en cache les résultats de chaque opération (mon implémentation ne se prête pas à la mise en cache si vous le souhaitez, utilisez l'implémentation d'origine) –

4

Votre code semble bien.

Ma seule suggestion serait de remplacer IEnumerable<object> avec le IEnumerable non-générique. (En System.Collections)

EDIT:

Sur une inspection plus poussée, vous jeter plus de fois que nécessaire.

Remplacez le if avec le code suivant:

var token = list as Token; 
if (token != null && token.TokenType == type) { 

En outre, vous pouvez vous débarrasser votre variable currentT en écrivant t[t.Count - 1] ou t.Last(). Cela rendra le code plus clair, mais pourrait avoir un effet négatif minime sur les performances.
Vous pouvez également stocker une référence à la liste interne dans une variable et l'utiliser directement. (Cela permettra d'améliorer légèrement les performances)

Enfin, si vous pouvez changer le type de retour à List<List<Object>>, vous pouvez retourner t directement; Cela évitera une copie de tableau et sera nettement plus rapide pour les grandes listes. D'ailleurs, vos noms de variables sont déroutants; vous devriez échanger les noms de t et list.

0

Avez-vous besoin de le convertir en tableau? Vous pourriez potentiellement utiliser LINQ et retarder l'exécution pour retourner les résultats.

EDIT:
Avec la question clarifiée, il serait difficile de se plier à LINQ faire revenir les résultats comme vous le souhaitez. Si vous voulez encore retarder l'exécution de chaque cycle, vous pouvez écrire votre propre enquêteur.

Je recommande le test de performance par rapport aux autres options pour voir s'il y a un gain de performance pour votre scénario si vous essayez cette approche. Cela pourrait causer plus de surcharge en gérant l'itérateur, ce qui serait mauvais pour les cas avec peu de données.

J'espère que cela aide.

// This is the easy way to make your own iterator using the C# syntax 
// It will return sets of separated tokens in a lazy fashion 
// This sample is based on the version provided by @Ants 
public static IEnumerable<IEnumerable<object>> Split(this IEnumerable<object> tokens, 
              TokenType type) {   
    var current = new List<object>(); 

    foreach (var item in tokens) 
    { 
     Token token = item as Token; 
     if (token != null && token.TokenType == type) 
     { 
      if(current.Count > 0) 
      { 
       yield return current; 
       current = new List<object>(); 
      } 
     } 
     else 
     { 
      current.Add(item); 
     } 
    } 

    if(current.Count > 0) 
     yield return current; 
} 

Avertissement: Cette compilation, mais a peut-être encore comporter des bogues cachés. Il se fait tard ici.

// This is doing the same thing but doing it all by hand. 
// You could use this method as well to lazily iterate through the 'current' list as well 
// This is probably overkill and substantially more complex 
public class TokenSplitter : IEnumerable<IEnumerable<object>>, IEnumerator<IEnumerable<object>> 
{ 
    IEnumerator<object> _enumerator; 
    IEnumerable<object> _tokens; 
    TokenType _target; 

    List<object> _current; 
    bool _isDone = false; 

    public TokenSplitter(IEnumerable<object> tokens, TokenType target) 
    { 
     _tokens = tokens; 
     _target = target; 
     Reset(); 
    }   

    // Cruft from the IEnumerable and generic IEnumerator 
    public IEnumerator<IEnumerable<object>> GetEnumerator() { return this; } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 

    public IEnumerable<object> Current { get { return _current; } }     
    public void Dispose() { }   

    #region IEnumerator Members 

    object System.Collections.IEnumerator.Current { get { return Current; } } 

    // See if there is anything left to get 
    public bool MoveNext() 
    { 
     if (_isDone) return false; 

     FillCurrent(); 

     return !_isDone;    
    } 

    // Reset the enumerators so that you could reuse this structure if you wanted 
    public void Reset() 
    { 
     _isDone = false; 
     _enumerator = _tokens.GetEnumerator(); 
     _current = new List<object>(); 
     FillCurrent(); 
    } 

    // Fills the current set of token and then begins the next set 
    private void FillCurrent() 
    { 
     // Try to accumulate as many tokens as possible, this too could be an enumerator to delay the process more 
     bool hasNext = _enumerator.MoveNext(); 
     for(; hasNext; hasNext = _enumerator.MoveNext()) 
     {    
      Token token = _enumerator.Current as Token; 
      if (token == null || token.TokenType != _target) 
      { 
       _current.Add(_enumerator.Current); 
      } 
      else 
      { 
       _current = new List<object>(); 
      } 
     } 

     // Continue removing matching tokens and begin creating the next set 
     for(; hasNext; hasNext = _enumerator.MoveNext()) 
     { 
      Token token = _enumerator.Current as Token; 
      if (token == null || token.TokenType != _target) 
      { 
       _current.Add(_enumerator.Current); 
       break; 
      } 
     } 

     _isDone = !hasNext; 
    } 

    #endregion 
} 
+0

Je peux être en mesure d'utiliser cette méthode, pouvez-vous élaborer sur la façon dont cela serait fait? – Dested

2

Ma première pensée serait au lieu de regarder en t[currentT] tout le temps, conserver seulement un currentList et ajouter directement à cela.

+0

Cela rendrait le code beaucoup plus simple et plus lisible. – SLaks

1

Je pense qu'il ya des cas cassés pour ces scénarios où partons du principe que les éléments de liste sont des lettres minuscules, et l'élément avec correspondance type de jeton est T:

  1. {T a b c ...};
  2. {... x y z T};
  3. {... j ... t T m n o ...};
  4. {T}; et
  5. {}

Ce qui se traduira par:

  1. {{} {a b c ...}};
  2. {{... x y z} {}};
  3. {{... j k l} {} {} {m n o ...}};
  4. {{}}; et
  5. {}

Faire un refactoring droit:

public static IEnumerable<object>[] Split(this IEnumerable<object> tokens, 
              TokenType type) { 
    var outer = new List<List<object>>(); 
    var inner = new List<object>(); 
    foreach (var item in tokens) { 
     Token token = item as token; 
     if (token != null && token.TokenType == type) { 
      outer.Add(inner); 
      inner = new List<object>(); 
      continue; 
     } 
     inner.Add(item); 
    } 
    outer.Add(inner); 
    return outer.ToArray(); 
} 

Pour fixer les cas cassés (en supposant que ceux-ci sont vraiment brisés), je vous recommande:

public static IEnumerable<object>[] Split(this IEnumerable<object> tokens, 
              TokenType type) { 
    var outer = new List<List<object>>(); 
    var inner = new List<object>(); 
    var enumerator = tokens.GetEnumerator(); 
    while (enumerator.MoveNext()) { 
     Token token = enumerator.Current as token; 
     if (token == null || token.TokenType != type) { 
      inner.Add(enumerator.Current); 
     } 
     else if (inner.Count > 0) { 
      outer.Add(inner); 
      inner = new List<object>(); 
     } 
    } 
    return outer.ToArray(); 
} 

qui Résultats:

  1. {{a b c ...}};
  2. {{... x y z}};
  3. {{... j k l} {m n o ...}};
  4. {}; et
  5. {}
3

type d'essai et moulages peuvent être un tueur de performance.Si possible, vos types de jeton doivent implémenter une interface commune ou une classe abstraite. Au lieu de passer et object, vous devez passer dans un IToken qui enveloppe votre objet.

est ici un code de concept que vous pouvez utiliser pour commencer:

using System; 
using System.Collections.Generic; 

namespace Juliet 
{ 
    interface IToken<T> 
    { 
     bool IsDelimeter { get; } 
     T Data { get; } 
    } 

    class DelimeterToken<T> : IToken<T> 
    { 
     public bool IsDelimeter { get { return true; } } 
     public T Data { get { throw new Exception("No data"); } } 
    } 

    class DataToken<T> : IToken<T> 
    { 
     public DataToken(T data) 
     { 
      this.Data = data; 
     } 

     public bool IsDelimeter { get { return false; } } 
     public T Data { get; private set; } 
    } 

    class TokenFactory<T> 
    { 
     public IToken<T> Make() 
     { 
      return new DelimeterToken<T>(); 
     } 

     public IToken<T> Make(T data) 
     { 
      return new DataToken<T>(data); 
     } 
    } 

    class Program 
    { 

     static List<List<T>> SplitTokens<T>(IEnumerable<IToken<T>> tokens) 
     { 
      List<List<T>> res = new List<List<T>>(); 
      foreach (IToken<T> token in tokens) 
      { 
       if (token.IsDelimeter) 
       { 
        res.Add(new List<T>()); 
       } 
       else 
       { 
        if (res.Count == 0) 
        { 
         res.Add(new List<T>()); 
        } 

        res[res.Count - 1].Add(token.Data); 
       } 
      } 

      return res; 
     } 

     static void Main(string[] args) 
     { 
      TokenFactory<string> factory = new TokenFactory<string>(); 
      IToken<string>[] tokens = new IToken<string>[] 
       { 
        factory.Make("a"), factory.Make("b"), factory.Make("c"), factory.Make(), 
        factory.Make("d"), factory.Make("e"), factory.Make(), 
        factory.Make("f"), factory.Make("g"), factory.Make("h"), factory.Make(), 
        factory.Make("i"), factory.Make("j"), factory.Make("k"), factory.Make("l"), factory.Make(), 
        factory.Make("m") 
       }; 

      List<List<string>> splitTokens = SplitTokens(tokens); 
      for (int i = 0; i < splitTokens.Count; i++) 
      { 
       Console.Write("{"); 
       for (int j = 0; j < splitTokens[i].Count; j++) 
       { 
        Console.Write("{0}, ", splitTokens[i][j]); 
       } 
       Console.Write("}"); 
      } 

      Console.ReadKey(true); 
     } 
    } 
} 

En principe, vous pouvez créer des instances de IToken<object> pour avoir généralisé aux jetons de plusieurs classes.

1

LINQ vous pouvez essayer ceci: (je ne l'ai pas le tester ...)

public static IEnumerable<object>[] Split(this IEnumerable<object> tokens, TokenType type) 
    { 
     List<List<object>> l = new List<List<object>>(); 
     l.Add(new List<object>()); 
     return tokens.Aggregate(l, (c, n) => 
     { 
      var t = n as Token; 
      if (t != null && t.TokenType == type) 
      { 
       t.Add(new List<object>()); 
      } 
      else 
      { 
       l.Last().Add(n); 
      } 
      return t; 
     }).ToArray(); 
    } 

Deuxième essai:

public static IEnumerable<object>[] Split(this IEnumerable<object> tokens, TokenType type) 
{ 
    var indexes = tokens.Select((t, index) => new { token = t, index = index }).OfType<Token>().Where(t => t.token.TokenType == type).Select(t => t.index); 
    int prevIndex = 0; 
    foreach (int item in indexes) 
    { 
     yield return tokens.Where((t, index) => (index > prevIndex && index < item)); 
     prevIndex = item; 
    } 
} 
+0

Approche intéressante! Les problèmes de vitesse semblent provenir de la création et de l'énumération des listes génériques. Ce dont j'ai vraiment besoin, c'est d'une solution qui pourrait me renvoyer en quelque sorte les morceaux du que je veux dans un tableau, par opposition à l'ajout constant à une liste. – Dested

+0

@Down: Eh bien, il semble que j'ai raté votre commentaire. Si vous voulez faire des tableaux, remplacez simplement la création de la liste et la section 'list.AddRange (EnumerateArraySegment (items, startIndex, endIndex - 1));' dans mon dernier morceau de code avec 'Array.Copy()'. J'espère que cela aide. –

3

A: Une mise en œuvre tout paresseux suffira si vous itérez juste à travers les résultats dans un foreach imbriqué:

using System; 
using System.Collections.Generic; 

public static class Splitter 
{ 
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, Predicate<T> match) 
    { 
     using (IEnumerator<T> enumerator = source.GetEnumerator()) 
     { 
      while (enumerator.MoveNext()) 
      { 
       yield return Split(enumerator, match); 
      } 
     } 
    } 

    static IEnumerable<T> Split<T>(IEnumerator<T> enumerator, Predicate<T> match) 
    { 
     do 
     { 
      if (match(enumerator.Current)) 
      { 
       yield break; 
      } 
      else 
      { 
       yield return enumerator.Current; 
      } 
     } while (enumerator.MoveNext()); 
    } 
} 

Utilisez-le ike ceci:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace MyTokenizer 
{ 
    class Program 
    { 
     enum TokenTypes { SimpleToken, UberToken } 

     class Token { public TokenTypes TokenType = TokenTypes.SimpleToken; } 

     class MyUberToken : Token { public MyUberToken() { TokenType = TokenTypes.UberToken; } } 

     static void Main(string[] args) 
     { 
      List<object> objects = new List<object>(new object[] { "A", Guid.NewGuid(), "C", new MyUberToken(), "D", new MyUberToken(), "E", new MyUberToken() }); 
      var splitOn = TokenTypes.UberToken; 
      foreach (var list in objects.Split(x => x is Token && ((Token)x).TokenType == splitOn)) 
      { 
       foreach (var item in list) 
       { 
        Console.WriteLine(item); 
       } 
       Console.WriteLine("=============="); 
      } 
      Console.ReadKey(); 
     } 

    } 
} 

B: Si vous avez besoin de traiter les résultats quelque temps plus tard et que vous souhaitez le faire hors de l'ordre, ou vous partition sur un fil, puis expédiez éventuellement les segments plusieurs threads, alors ce serait probablement un bon point de départ:

using System; 
using System.Collections.Generic; 
using System.Linq; 

public static class Splitter2 
{ 
    public static IEnumerable<IEnumerable<T>> SplitToSegments<T>(this IEnumerable<T> source, Predicate<T> match) 
    { 
     T[] items = source.ToArray(); 
     for (int startIndex = 0; startIndex < items.Length; startIndex++) 
     { 
      int endIndex = startIndex; 
      for (; endIndex < items.Length; endIndex++) 
      { 
       if (match(items[endIndex])) break; 
      } 
      yield return EnumerateArraySegment(items, startIndex, endIndex - 1); 
      startIndex = endIndex; 
     } 
    } 

    static IEnumerable<T> EnumerateArraySegment<T>(T[] array, int startIndex, int endIndex) 
    { 
     for (; startIndex <= endIndex; startIndex++) 
     { 
      yield return array[startIndex]; 
     } 
    } 
} 

C: Si vous devez vraiment retourner une collection de la liste <T> -s - ce dont je doute, à moins que vous exp veulent licitement les muter quelque temps plus tard -, puis essayez de les initialiser à une capacité donnée avant la copie:

public static List<List<T>> SplitToLists<T>(this IEnumerable<T> source, Predicate<T> match) 
{ 
    List<List<T>> lists = new List<List<T>>(); 
    T[] items = source.ToArray(); 
    for (int startIndex = 0; startIndex < items.Length; startIndex++) 
    { 
     int endIndex = startIndex; 
     for (; endIndex < items.Length; endIndex++) 
     { 
      if (match(items[endIndex])) break; 
     } 
     List<T> list = new List<T>(endIndex - startIndex); 
     list.AddRange(EnumerateArraySegment(items, startIndex, endIndex - 1)); 
     lists.Add(list); 
     startIndex = endIndex; 
    } 
    return lists; 
} 

D: Si cela ne suffit pas encore, je vous suggère de rouler votre propre liste légère implémentation qui peut copier une plage directement dans son tableau interne à partir d'une autre instance.

+0

J'aime ça! C'est super propre. Bien joué. – smaclell

+0

@Dested: Si vous avez besoin de tableaux, remplacez simplement la partie autour de 'list.Addrange' par la création d'un nouveau tableau et en faisant un' Array.Copy'. –

1

Voici une possibilité

La classe Token (peut-être ce que jamais la classe)

public class Token 
{ 
    public string Name { get; set; } 
    public TokenType TokenType { get; set; } 
} 

Maintenant le type ENUM (cela pourrait être ce que d'autres facteurs de regroupement)

public enum TokenType 
{ 
    Type1, 
    Type2, 
    Type3, 
    Type4, 
    Type5, 
} 

La méthode d'extension (Déclarez cette option de toute façon)

public static class TokenExtension 
{ 
    public static IEnumerable<Token>[] Split(this IEnumerable<Token> tokens) 
    { 
     return tokens.GroupBy(token => ((Token)token).TokenType).ToArray(); 
    } 
} 

Exemple d'utilisation (j'ai utilisé un projet web pour faire tourner cette)

List<Token> tokens = new List<Token>(); 
     tokens.Add(new Token { Name = "a", TokenType = TokenType.Type1 }); 
     tokens.Add(new Token { Name = "b", TokenType = TokenType.Type1 }); 
     tokens.Add(new Token { Name = "c", TokenType = TokenType.Type1 }); 

     tokens.Add(new Token { Name = "d", TokenType = TokenType.Type2 }); 
     tokens.Add(new Token { Name = "e", TokenType = TokenType.Type2 }); 

     tokens.Add(new Token { Name = "f", TokenType = TokenType.Type3 }); 
     tokens.Add(new Token { Name = "g", TokenType = TokenType.Type3 }); 
     tokens.Add(new Token { Name = "h", TokenType = TokenType.Type3 }); 

     tokens.Add(new Token { Name = "i", TokenType = TokenType.Type4 }); 
     tokens.Add(new Token { Name = "j", TokenType = TokenType.Type4 }); 
     tokens.Add(new Token { Name = "k", TokenType = TokenType.Type4 }); 
     tokens.Add(new Token { Name = "l", TokenType = TokenType.Type4 }); 

     tokens.Add(new Token { Name = "m", TokenType = TokenType.Type5 }); 

     StringBuilder stringed = new StringBuilder(); 

     foreach (Token token in tokens) 
     { 
      stringed.Append(token.Name); 
      stringed.Append(", "); 
     } 

     Response.Write(stringed.ToString()); 
     Response.Write("</br>"); 


     var q = tokens.Split(); 
     foreach (var list in tokens.Split()) 
     { 
      stringed = new StringBuilder(); 
      foreach (Token token in list) 
      { 
       stringed.Append(token.Name); 
       stringed.Append(", "); 
      } 
      Response.Write(stringed.ToString()); 
      Response.Write("</br>"); 
     } 

Tout ce que je suis SOING utilise Linq, vous pouvez ajouter ou supprimer, vous pouvez actualy devenir fou sur ce sujet et groupe sur de nombreux différentes propriétés.

+0

Juste en l'air, si vous voulez vous pouvez déclarer une liste d'objets et changer la méthode pour tester avec l'opérateur "as" avant d'essayer de lire la propriété. Ou vous pouvez juste aller écrou et utiliser la réflexion ou même aller à très bas niveau avec MSIL. – Oakcool