2013-08-01 10 views
5

Ho un oggetto IEnumerable e volevo suddividere i dati su 3 colonne utilizzando la seguente logica aziendale. se 3 o meno elementi, 1 oggetto per colonna, qualsiasi altra cosa ho voluto dividere gli elementi totali per 3 dividere gli avanzi (1 o 2 elementi) tra le prime due colonne. Ora questo è abbastanza brutto, ma fa il lavoro. Sto cercando suggerimenti per sfruttare meglio linq o forse eliminare l'istruzione switch. Sono graditi consigli o suggerimenti che migliorano il codice.Rifrattore algoritmo C# che suddivide un array in 3 parti?

var numItems = items.Count; 

      IEnumerable<JToken> col1Items, 
           col2Items, 
           col3Items; 


      if(numItems <=3) 
      { 
       col1Items = items.Take(1); 
       col2Items = items.Skip(1).Take(1); 
       col3Items = items.Skip(2).Take(1); 

      } else { 

       int remainder = numItems % 3, 
        take = numItems/3, 
        col1Take, 
        col2Take, 
        col3Take; 

       switch(remainder) 
       { 
        case 1: 
         col1Take = take + 1; 
         col2Take = take; 
         col3Take = take; 
         break; 
        case 2: 
         col1Take = take + 1; 
         col2Take = take + 1; 
         col3Take = take; 
         break; 
        default: 
         col1Take = take; 
         col2Take = take; 
         col3Take = take; 
         break; 

       } 

       col1Items = items.Take(col1Take); 
       col2Items = items.Skip(col1Take).Take(col2Take); 
       col3Items = items.Skip(col1Take + col2Take).Take(col3Take); 

definitiva Sto usando questi in vista Razor MVC

<div class="widgetColumn"> 
       @Html.DisplayFor(m => col1Items, "MenuColumn")      
      </div> 

      <div class="widgetColumn"> 
       @Html.DisplayFor(m => col2Items, "MenuColumn")      
      </div> 

      <div class="widgetColumn"> 
       @Html.DisplayFor(m => col3Items, "MenuColumn")      
      </div> 

Nel mio primo tentativo voglio sbarazzarsi dei colNItems e variabili colNTake ma io non riesco a capire il corretto algoritmo per fallo funzionare allo stesso modo.

for (int i = 1; i <= 3; i++) 
      { 
       IEnumerable<JToken> widgets = new List<JToken>(); 
       var col = i; 
       switch(col) 
       { 
        case 1: 
         break; 
        case 2: 
         break; 
        case 3: 
         break; 
       } 
      } 
+5

Questa domanda è probabilmente una soluzione migliore per http://codereview.stackexchange.com/ –

+0

Pensa ricorsiva! la regola del rimanente è in linea di principio uguale alla regola "minore di 3" che a sua volta è simile alla regola di divisione – Polity

risposta

1

Si potrebbe generalizzare:

int cols = 3; 
IEnumerable<JToken> colItems[3]; // you can make this dynamic of course 

int rem = numItems % cols; 
int len = numItems/cols; 

for (int col=0; col<cols; col++){ 
    int colTake = len; 
    if (col < rem) colTake++; 
    colItems[col] = items.Skip(col*len).Take(colTake); 
} 

non ho ancora testato, ma questo dovrebbe funzionare per qualsiasi numero di colonne.

Inoltre, ogni volta che avete bisogno di variabili col1, col2, col3, pensate a col [0], col [1], col [2].

+0

Questo non crea 3 liste? – Hogan

+0

Il codice ora crea gli elenchi. Inoltre, credo che sia in linea con l'idea dell'OP di mantenere gli elementi consecutivi nella stessa colonna – rslite

+0

. Ho finito per usare questo approccio. Sembrava pulito e abbastanza semplice. Tutte ottime soluzioni per tutti. Mi piace sicuramente anche l'approccio css puro di Brian Ball. Grazie a tutti! – Hcabnettek

1

Non puoi fare qualcosa di simile?

int len = numItems/3; 
int rem = numItems % 3; 

int col1Take = len + (rem > 0 ? 1 : 0); 
int col2Take = len + (rem > 1 ? 1 : 0); 
int col3Take = len; 

Edit:

Una soluzione più generico che funziona per qualsiasi numero di colonne (COLUMNS) sarebbe:

int len = numItems/COLUMNS; 
int rem = numItems % COLUMNS; 

foreach (var i in Enumerable.Range(0, COLUMNS)) { 
    colTake[i] = len + (rem > i ? 1 : 0); 
} 
+0

Questo non ha senso, stai solo contando gli elementi? Non vuoi mettere il risultato in una lista? – Hogan

+0

Se leggi la totalità del post originale vedrai che la domanda è "c'è un modo per sbarazzarsi dell'istruzione switch?". L'ho fatto con questo snippet di codice. –

+0

Capisco cosa intendi, tuttavia penso che abbia bisogno di una soluzione che renda le liste più veloci. – Hogan

0

Se si vuole riempire le colonne round-robin voi può usare:

int numColumns = 3; 

var result = Enumerable.Range(1,numColumns).Select(c => 
     items.Where((x,ix) => ix % numColumns == c-1).ToArray() 
    ); 
+0

carino, ma fai scorrere l'elenco 3 volte anziché una volta. – Hogan

6

Sono le colonne f ixed larghezza? Se è così, allora non c'è bisogno di fare qualcosa di speciale con la tua collezione. Basta affidarsi al browser per farlo per te. Avere un contenitore esterno che ha la larghezza complessiva delle 3 colonne, quindi basta riempirlo con un div per ogni oggetto (e fluttuare a sinistra). Impostare i contenitori interni per avere una larghezza esattamente 1/3 del contenitore esterno.

Ecco un rapido fiddle

Ecco un rapido accenno alla stile

div#outer{ 
    width:300px;  
} 

div#outer > div{ 
    width:100px; 
    float:left;  
} 
+0

+1, soluzione piacevole, fuori dagli schemi. (pun intended) – Hogan

+0

Per quanto riguarda questa soluzione, volevano che gli oggetti fossero "tagliati verticalmente" se lo si desidera, come negli articoli 1, qualunque cosa apparirebbe nella stessa colonna, non orizzontale come nel violino. Ottima soluzione però !! – Hcabnettek

0

potrebbe aiutare

 IEnumerable<object> items = new Object[]{ "1", "2", "3", "4", "5", "6", "7","8", "9", "10", "11", "12","13", "14" }; 

     IEnumerable<object> col1Items = new List<object>(), 
          col2Items = new List<object>(), 
          col3Items = new List<object>(); 

     Object[] list = new Object[]{col1Items, col2Items, col3Items}; 
     int limit = items.Count()/3; 
     int len = items.Count(); 
     int col;    

     for (int i = 0; i < items.Count(); i++) 
     {     
      if (len == 3) col = i; 
      else col = i/limit; 

      if (col >= 3) col = i%limit ; 

      ((IList<object>)(list[col])).Add(items.ElementAt(i)); 

     } 
0

Questo non è veloce, ma lo farà il trucco:

var col1Items = items.Select((obj, index) => new { Value = obj, Index = index }) 
    .Where(o => o.Index % 3 == 0).Select(o => o.Value); 
var col2Items = items.Select((obj, index) => new { Value = obj, Index = index }) 
    .Where(o => o.Index % 3 == 1).Select(o => o.Value); 
var col3Items = items.Select((obj, index) => new { Value = obj, Index = index }) 
    .Where(o => o.Index % 3 == 2).Select(o => o.Value); 

Usa la versione di Select che include un parametro index. Potresti usare un GroupBy per velocizzare un po 'questo al costo di poche righe di codice.

0

Se si vuole andare oltre, in basso si veda la risposta sotto questa, se si vuole scendere poi attraverso si può fare così (usare questo codice con il test qui sotto per vederlo funzionante al posto della linea var result lì .:.

var curCol = 0; 
var iPer = items.Count()/3; 
var iLeft = items.Count() % 3; 
var result = items.Aggregate(
       // object that will hold items 
       new { 
         cols = new List<ItemElement>[3] { new List<ItemElement>(), 
                 new List<ItemElement>(), 
                 new List<ItemElement>(), }, 
          }, 
       (o, n) => { 
       o.cols[curCol].Add(n); 

       if (o.cols[curCol].Count() > iPer + (iLeft > (curCol+1) ? 1:0)) 
        curCol++; 

       return new { 
        cols = o.cols 
       }; 
      }); 

Si può fare questo con aggregato sarebbe simile a questa:

void Main() 
{ 
    List<ItemElement> items = new List<ItemElement>() { 
      new ItemElement() { aField = 1 }, 
      new ItemElement() { aField = 2 }, 
      new ItemElement() { aField = 3 }, 
      new ItemElement() { aField = 4 }, 
      new ItemElement() { aField = 5 }, 
      new ItemElement() { aField = 6 }, 
      new ItemElement() { aField = 7 }, 
      new ItemElement() { aField = 8 }, 
      new ItemElement() { aField = 9 } 
    }; 

    var result = 
    items.Aggregate(
     // object that will hold items 
     new { 
     cols = new List<ItemElement>[3] { new List<ItemElement>(), 
              new List<ItemElement>(), 
              new List<ItemElement>(), }, 
     next = 0 }, 
    // aggregate 
    (o, n) => { 
     o.cols[o.next].Add(n); 

     return new { 
     cols = o.cols, 
     next = (o.next + 1) % 3 
     }; 
    }); 
    result.Dump(); 
} 

public class ItemElement 
{ 
    public int aField { get; set; } 
} 

si finisce con un oggetto con una serie di 3 liste (uno per ogni colonna).

Questo esempio verrà eseguito come in linqPad. Raccomando linqPad per questo tipo di test POC. (LinqPad.com)

0

LinqLib (NuGet: LinqExtLibrary) ha un sovraccarico di ToArray() che lo fa:

using System.Collections.Generic; 
using System.Linq; 
using LinqLib.Array; 

... 

    public void TakeEm(IEnumerable<int> data) 
    { 
     var dataAry = data as int[] ?? data.ToArray(); 
     var rows = (dataAry.Length/3) + 1; 
     //var columns = Enumerable.Empty<int>().ToArray(3, rows); 
     // vvv These two lines are the ones that re-arrange your array 
     var columns = dataAry.ToArray(3, rows); 
     var menus = columns.Slice(); 
    } 
+0

Ho provato questo e non ha funzionato affatto. – Hogan

1

Così si vuole il primo n/3 elementi nella prima colonna, accanto n/3 elementi nel 2 ° colonna, ecc

var concreteList = items.ToList(); 
var count = concreteList.Count; 
var take1 = count/3 + (count % 3 > 0 ? 1 : 0); 
var take2 = count/3 + (count % 3 > 1 ? 1 : 0); 

var col1 = concreteList.Take(take1); 
var col2 = concreteList.Skip(take1).Take(take2); 
var col3 = concreteList.Skip(take1 + take2); 

faccio una lista concreta al fine di evitare l'iterazione i Enumerable più volte. Ad esempio, se si dispone di:

items = File.ReadLines("foo.txt"); 

Quindi non si sarà in grado di ripetere più volte.

Problemi correlati