2012-07-04 9 views
5

Oto scenariusz moje pytanie: Mam tablicę, powiedzieć:LINQ do zliczenia Kontynuuje powtarzające się elementy (int) w int Array?

{ 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 } 

Wynikiem powinno być coś takiego (elementu tablicy => jego liczą):

4 => 1 
1 => 2 
3 => 2 
2 => 1 
5 => 1 
3 => 1 
2 => 2 

wiem to można osiągnąć przez for loop.

Ale google'd dużo, aby było to możliwe przy użyciu mniejszych linii kodu przy użyciu LINQ bez powodzenia.

+3

Trudno zobaczyć jak długość tablicy może być ustalona na poziomie 6, jeżeli przykładem jest 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 ... –

+0

Teraz sprawdź pytanie: proszę – sandeep

+0

Dlaczego na wyjściach znajdują się zduplikowane klucze dla liczb "2" i "3"? Czy nie powinieneś raczej oczekiwać wyjścia "4 => 1, 1 => 2, 3 => 3, 2 => 3, 5 => 1"? –

Odpowiedz

8

Uważam, że najbardziej optymalnym sposobem na zrobienie tego jest utworzenie "rozszerzenia podobnego do LINQ" za pomocą bloku iteratora. Pozwala to wykonać obliczenia wykonując pojedyncze przejście przez dane. Zauważ, że wydajność nie jest wcale ważna, jeśli chcesz wykonać obliczenia na małej liczbie liczb. Oczywiście to jest naprawdę twoja pętla w przebraniu.

static class Extensions { 

    public static IEnumerable<Tuple<T, Int32>> ToRunLengths<T>(this IEnumerable<T> source) { 
    using (var enumerator = source.GetEnumerator()) { 
     // Empty input leads to empty output. 
     if (!enumerator.MoveNext()) 
     yield break; 

     // Retrieve first item of the sequence. 
     var currentValue = enumerator.Current; 
     var runLength = 1; 

     // Iterate the remaining items in the sequence. 
     while (enumerator.MoveNext()) { 
     var value = enumerator.Current; 
     if (!Equals(value, currentValue)) { 
      // A new run is starting. Return the previous run. 
      yield return Tuple.Create(currentValue, runLength); 
      currentValue = value; 
      runLength = 0; 
     } 
     runLength += 1; 
     } 

     // Return the last run. 
     yield return Tuple.Create(currentValue, runLength); 
    } 
    } 

} 

Należy pamiętać, że metoda rozszerzenia jest ogólna i można jej używać na dowolnym typie. Wartości są porównywane dla równości przy użyciu Object.Equals. Jeśli jednak chcesz, możesz przekazać IEqualityComparer<T>, aby umożliwić dostosowanie sposobu porównywania wartości.

Można użyć metody takie jak to:

var numbers = new[] { 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 }; 
var runLengths = numbers.ToRunLengths(); 

Dla Ciebie danych wejściowych wynik będzie te krotki:

 
4 1 
1 2 
3 2 
2 1 
5 1 
3 1 
2 2 
+0

wydaje się, że byłem trochę powolny +1 – Jodrell

+0

Czy istnieje adavantage w użyciu 'GetEnumerator' lub jest po prostu uniknąć' foreach' zgodnie z żądaniem? – Jodrell

+0

@Jodrell: Ponieważ muszę zrobić specjalną obsługę pierwszego elementu, musi wyjść poza główną pętlę. Używanie modułu wyliczającego pozwala mi to zrobić i wciąż tylko pobierać i badać każdy element dokładnie jeden raz. –

-4

Spróbuj GroupBy przez List<int>

 List<int> list = new List<int>() { 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 }; 
     var res = list.GroupBy(val => val); 
     foreach (var v in res) 
     { 
      MessageBox.Show(v.Key.ToString() + "=>" + v.Count().ToString()); 
     } 
+0

y downvoting bez znajomości rzeczy? –

+0

Mam również zaprojektował tę samą powyższą metodę, więc myślę, że nie potrzeba downvoting –

+4

Myślę, że ludzie cię na dół, bo to nie jest właściwa odpowiedź. Jego ideą jest grupowanie ** kolejnych ** zdarzeń. –

0
var array = new int[]{};//whatever ur array is 
array.select((s)=>{return array.where((s2)=>{s == s2}).count();}); 

jedynym prob ze jest tht jeśli masz 1 - dwa razy dostaniesz wynik na 1-dwa razy

3

(Dodanie kolejnego odpowiedź na uniknięcie dwóch upvotes dla mojego usunięty jeden licząc na to ...)

Miałem trochę o tym pomyśleć (teraz zrozumiałem to pytanie) i to naprawdę nie wyjaśnij, jak dobrze to zrobiłbyś w LINQ. Są na pewno sposoby, aby to zrobić, potencjalnie używając Zip lub Aggregate, ale byłyby one względnie niejasne. Korzystanie foreach jest dość prosta:

// Simplest way of building an empty list of an anonymous type... 
var results = new[] { new { Value = 0, Count = 0 } }.Take(0).ToList(); 

// TODO: Handle empty arrays 
int currentValue = array[0]; 
int currentCount = 1; 

foreach (var value in array.Skip(1)) 
{ 
    if (currentValue != value) 
    { 
     results.Add(new { Value = currentValue, Count = currentCount }); 
     currentCount = 0; 
     currentValue = value; 
    } 
    currentCount++; 
} 
// Handle tail, which we won't have emitted yet 
results.Add(new { Value = currentValue, Count = currentCount }); 
2

To nie tyle krótki?

public static IEnumerable<KeyValuePair<T, int>> Repeats<T>(
     this IEnumerable<T> source) 
{ 
    int count = 0; 
    T lastItem = source.First(); 

    foreach (var item in source) 
    { 
     if (Equals(item, lastItem)) 
     { 
      count++; 
     } 
     else 
     { 
      yield return new KeyValuePair<T, int>(lastItem, count); 
      lastItem = item; 
      count = 1; 
     } 
    } 

    yield return new KeyValuePair<T, int>(lastItem, count); 
} 

Będę zainteresowany, aby zobaczyć sposób linq.

+0

Co z ostatnim przedmiotem? – Gabe

+0

@Gabe dobry punkt – Jodrell

+0

Zrobiłem sobie prawo do naprawienia kilku błędów w kodzie pozwalającym na kompilację. –

3

Oto wyrażenie LINQ, który działa (edit: zaostrzone kod tylko trochę więcej):

var data = new int[] { 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 }; 
var result = data.Select ((item, index) => 
         new 
         { 
          Key = item, 
          Count = (index == 0 || data.ElementAt(index - 1) != item) 
           ? data.Skip(index).TakeWhile (d => d == item).Count() 
           : -1 
         } 
         ) 
        .Where (d => d.Count != -1); 

I here's a proof że pokazuje ona działa.

+0

+1 za zminimalizowanie użycia ';' jako OP zażądał – Jodrell

+0

W pewnym sensie jest to poprawna odpowiedź, ponieważ używa LINQ, jak podano w pytaniu. Zastanawiając się nad efektywnością tego podejścia, zliczyłem liczbę utworzonych enumeratorów i liczbę dostępnych elementów, biorąc pod uwagę tablicę wejściową 10 elementów. Utworzono 17 enumeratorów i uzyskano dostęp do 101 elementów w porównaniu do najbardziej optymalnego podejścia, w którym utworzono 1 moduł wyliczający i udostępniono 10 elementów. –

1

Już napisałem metodę, której potrzebujesz ponad there. Oto jak go nazwać.

foreach(var g in numbers.GroupContiguous(i => i)) 
{ 
    Console.WriteLine("{0} => {1}", g.Key, g.Count); 
} 
1

Oto (można uruchomić to bezpośrednio w LINQPad - rle gdzie dzieje się magia):

var xs = new[] { 4, 1, 1, 3, 3, 2, 5, 3, 2, 2 }; 

var rle = Enumerable.Range(0, xs.Length) 
        .Where(i => i == 0 || xs[i - 1] != xs[i]) 
        .Select(i => new { Key = xs[i], Count = xs.Skip(i).TakeWhile(x => x == xs[i]).Count() }); 

Console.WriteLine(rle); 

Oczywiście, to jest O (n^2), ale nie Żądaj wydajności liniowej w specyfikacji.

0
var array = new int[] {1,1,2,3,5,6,6 }; 
var arrayd = array.Distinct(); 
var arrayl= arrayd.Select(s => { return array.Where(s2 => s2 == s).Count(); }).ToArray(); 

wyjściowy

arrayl=[0]2 [1]1 [2]1 [3]1 [4]2 
+0

Witamy w Stack Overflow! Poświęć chwilę na przeczytanie [How to Answer] (http://stackoverflow.com/questions/how-to-answer) - wygląda to na przydatne, ale przydałoby się wyjaśnienie, co robi kod, rozważ [edytuj] (http://stackoverflow.com/posts/41385887/edit)-ing that in? –

0
var array = new int[] {1,1,2,3,5,6,6 }; 
foreach (var g in array.GroupBy(i => i)) 
{ 
    Console.WriteLine("{0} => {1}", g.Key, g.Count()); 
} 
+1

Lepiej uwzględnić kontekst/wyjaśnienie za pomocą kodu, ponieważ dzięki temu odpowiedź jest bardziej przydatna dla OP i przyszłych czytelników. – EJoshuaS