2013-08-01 9 views
5

Mam IEnumerable i chciałem podzielić dane na 3 kolumny przy użyciu następującej logiki biznesowej. jeśli 3 lub mniej przedmiotów, 1 pozycja na kolumnę, cokolwiek innego, co chciałem podzielić całkowite przedmioty przez 3, podzielono resztki (1 lub 2 pozycje) między dwie pierwsze kolumny. To jest dość brzydkie, ale spełnia swoją rolę. Szukam wskazówek, które pozwolą nieco lepiej wykorzystać linq lub ewentualnie wyeliminować instrukcję switch. Wszelkie rady i wskazówki, które poprawiają kod, są doceniane.Refaktor algorytmu C# dzielący tablicę na 3 części?

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); 

Ostatecznie używam je w widoku mvc Razor

<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> 

W mojej pierwszej próbie Chcę pozbyć się colNItems i zmiennych colNTake ale nie mogę dowiedzieć się prawidłowy algorytm sprawiają, że działa tak samo.

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

To pytanie prawdopodobnie lepiej pasuje do http://codereview.stackexchange.com/ –

+0

Pomyśl rekursywnie! reszta jest w zasadzie taka sama jak reguła "mniej niż 3", która z kolei jest podobna do reguły dzielenia – Polity

Odpowiedz

1

Można uogólnić:

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); 
} 

nie testowałem, ale to powinno działać na dowolną liczbę kolumn.

Także gdy potrzebujesz zmiennych col1, col2, col3, pomyśl o col [0], col [1], col [2].

+0

To nie tworzy 3 list? – Hogan

+0

Kod tworzy teraz listy. Sądzę też, że jest to zgodne z ideą PO, polegającą na utrzymywaniu kolejnych elementów w tej samej kolumnie: – rslite

+0

Skończyło się na tym zastosowaniu. Wydawał się czysty i wystarczająco prosty. Wszystkie świetne rozwiązania, chociaż każdy. Zdecydowanie podoba mi się również czyste podejście Css z Brian Ball. Dziękuję wszystkim! – Hcabnettek

1

Nie możesz po prostu zrobić czegoś takiego?

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:

Bardziej ogólne rozwiązanie, które działa dla dowolnej liczby kolumn (COLUMNS) byłoby:

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

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

To nie ma sensu, czy liczysz tylko przedmioty? Czy nie chcesz umieścić wyniku na liście? – Hogan

+0

Jeśli przeczytasz cały oryginalny wpis, zobaczysz, że pytanie brzmi: "czy istnieje sposób na pozbycie się instrukcji switch?". Zrobiłem to za pomocą tego fragmentu kodu. –

+0

Rozumiem, co masz na myśli, ale myślę, że potrzebuje rozwiązania, które przyspiesza listę. – Hogan

0

Jeśli chcesz wypełnić kolumny round-robin ty można użyć:

int numColumns = 3; 

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

urocze, ale robisz iterację listy 3 razy zamiast raz. – Hogan

6

Czy kolumny f ixed-width? Jeśli tak, to nie musisz robić nic specjalnego ze swoją kolekcją. Wystarczy skorzystać z przeglądarki, aby zrobić to za Ciebie. Miej zewnętrzny pojemnik, który ma ogólną szerokość 3 kolumn, a następnie po prostu wypełnij go elementem div dla każdego elementu (i ułóż w lewo). Ustaw wewnętrzne pojemniki tak, aby miały szerokość dokładnie 1/3 zewnętrznego pojemnika.

Oto krótki fiddle

Oto krótka wskazówka w stylu

div#outer{ 
    width:300px;  
} 

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

+1, ładne rozwiązanie, poza pudełkiem. (kalambur przeznaczony) – Hogan

+0

Odnośnie tego rozwiązania, chcieli, żeby przedmioty były "pionowo" w plasterkach, jeśli chcesz, tak jak w pozycjach 1 - cokolwiek pojawi się w tej samej kolumnie, a nie w poziomie, jak w skrzypcach. Świetne rozwiązanie !! – Hcabnettek

0

może pomóc

 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

To nie jest szybki, ale będzie to rade:

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); 

Używa wersji Select zawierającej parametr indeksu. Możesz użyć GroupBy, aby przyspieszyć to nieco kosztem kilku linii kodu.

0

Jeśli chcesz przejść dalej, a następnie w dół, zobacz odpowiedź poniżej tego, jeśli chcesz zejść w dół, to możesz to zrobić w ten sposób (użyj tego kodu z poniższym testem, aby zobaczyć, że działa, zamiast linii var result .:.

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 
       }; 
      }); 

można to zrobić za pomocą agregatu to będzie wyglądać następująco:

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; } 
} 

skończyć z obiektu z tablicą 3 list (po jednym dla każdej kolumny).

Ten przykład będzie działał jak w LinqPadzie. Polecam linqPad dla tego rodzaju testów POC. (LinqPad.com)

0

LinqLib (Nuget: LinqExtLibrary) ma przeciążenia ToArray(), która to robi:

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

Próbowałem tego i nie działało w ogóle. – Hogan

1

Więc chcesz pierwsza n/3 pozycji w pierwszej kolumnie, następne N/3 szt w 2 kolumna itd

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); 

zrobię konkretną listę, aby uniknąć iteracji z Enumerable wielokrotnie. Na przykład, jeśli miałeś:

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

Wtedy nie byłbyś w stanie iterować go wiele razy.