Детский вопрос по LINQ
От: SergASh  
Дата: 04.10.08 12:10
Оценка:
Привет всем!

Есть две последовательности одинаковой длины. Как для них построить соединение, которое бы соотносило элементы с одинаковыми индексами?

То есть на входе { 1, 2, 3 }, { "ein", "zwei", "drei" }. А на выходе { { 1, "ein" }, { 2, "zwei" }, { 3, "drei" } }.

Интересует человеческий вариант на LINQ-операторах, а не это убожество, приведенное ниже.
var digits = new[] { 1, 2, 3 };
var german = new[] { "ein", "zwei", "drei" };

var q = digits.Select( ( digit, index ) => new { Value = digit, Index = index } )
              .Join( german.Select( ( phrase, index ) => new { Value = phrase, Index = index } ),
                     _ => _.Index, _ => _.Index, // Вот это очень плохо читается; с первого взгляда не очевидно, что именно тут индексы на равенство и сравниваются.
                     ( digit, phrase ) => new { Digit = digit.Value, Phrase = phrase.Value }
                    );
foreach ( var translation in q )
  Console.WriteLine( "'{0}' stands for {1}", translation.Phrase, translation.Digit );

Спасибо.
Re: Детский вопрос по LINQ
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 04.10.08 12:29
Оценка:
Здравствуйте, SergASh, Вы писали:

SAS>Привет всем!


SAS>Есть две последовательности одинаковой длины. Как для них построить соединение, которое бы соотносило элементы с одинаковыми индексами?


SAS>То есть на входе { 1, 2, 3 }, { "ein", "zwei", "drei" }. А на выходе { { 1, "ein" }, { 2, "zwei" }, { 3, "drei" } }.


SAS>Интересует человеческий вариант на LINQ-операторах, а не это убожество, приведенное ниже.

SAS>Спасибо.
Думаю проще будет написать свой Extension-метод Merge. Кстати на форуме где-то такое пробегало.
Re: Детский вопрос по LINQ
От: losev-al  
Дата: 04.10.08 13:52
Оценка:
Здравствуйте, SergASh, Вы писали:

SAS>Привет всем!


SAS>Есть две последовательности одинаковой длины. Как для них построить соединение, которое бы соотносило элементы с одинаковыми индексами?


SAS>То есть на входе { 1, 2, 3 }, { "ein", "zwei", "drei" }. А на выходе { { 1, "ein" }, { 2, "zwei" }, { 3, "drei" } }.


SAS>Интересует человеческий вариант на LINQ-операторах, а не это убожество, приведенное ниже.

...
SAS>Спасибо.

Ну сходу приходит на ум вот это. Но сразу оговорюсь медленно хотя, с другой стороны, понятно и скорость написания высокая

int[] digits = { 1, 2, 3 };
string[] german = new[] { "ein", "zwei", "drei" };
var result = from digit in digits
join phrase in german on digits.ToList<int>().IndexOf(digit) equals german.ToList<string>().IndexOf(phrase)
select new { Digit = digit, Phrase = phrase};
foreach ( var translation in result )
Console.WriteLine( "'{0}' stands for {1}", translation.Phrase, translation.Digit );
Re: Детский вопрос по LINQ
От: desco США http://v2matveev.blogspot.com
Дата: 04.10.08 14:00
Оценка: +3
Здравствуйте, SergASh, Вы писали:

<skipped/>

можно, например, так:

static class Extensions
{
    public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
        this IEnumerable<TFirst> first,
        IEnumerable<TSecond> second,
        Func<TFirst, TSecond, TResult> func)
    {
        if (first == null)
            throw new ArgumentNullException("first");

        if (second == null)
            throw new ArgumentNullException("second");

        if (func == null)
            throw new ArgumentNullException("func");

        using (var e1 = first.GetEnumerator())
        using (var e2 = second.GetEnumerator())
            while (e1.MoveNext() && e2.MoveNext())
                yield return func(e1.Current, e2.Current);
    }
}

class Program
{
    static void Main()
    {
        var digits = new[] { 1, 2, 3 };
        var german = new[] { "ein", "zwei", "drei" };
        var q = digits.Zip(german, (digit, phrase) => new {Digit = digit, Phrase = phrase});
        foreach (var translation in q)
            Console.WriteLine("'{0}' stands for {1}", translation.Phrase, translation.Digit);
    }
}
Re[2]: Детский вопрос по LINQ
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.10.08 15:21
Оценка:
Здравствуйте, desco, Вы писали:

Возник филосовский вопрос...
D>
D>        using (var e1 = first.GetEnumerator())
D>        using (var e2 = second.GetEnumerator())
D>            while (e1.MoveNext() && e2.MoveNext())
D>                yield return func(e1.Current, e2.Current);
D>    }
D>}
D>


Насколько корректно позволять склеивать последовательности разной длинны?
Может быть было бы корректнее допускать склеивании только последовательностей имеющих одинаковую длину?

Вопрос не праздный, так как давно хотел добавить аналогичную функцию в библиотеку Немерла.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Детский вопрос по LINQ
От: desco США http://v2matveev.blogspot.com
Дата: 04.10.08 15:57
Оценка:
Здравствуйте, VladD2, Вы писали:


VD>Насколько корректно позволять склеивать последовательности разной длинны?

VD>Может быть было бы корректнее допускать склеивании только последовательностей имеющих одинаковую длину?

VD>Вопрос не праздный, так как давно хотел добавить аналогичную функцию в библиотеку Немерла.


Например, Seq.zip из стандартной библиотеки F# игнорирует остаток последовательности большей длины.
Zip из Haskell ведет себя также:

If one input list is short, excess elements of the longer list are discarded.

Re[2]: Детский вопрос по LINQ
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.10.08 16:28
Оценка: +1
Здравствуйте, desco, Вы писали:

D>можно, например, так:


D>
D>    public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
D>        this IEnumerable<TFirst> first,
D>        IEnumerable<TSecond> second,
D>        Func<TFirst, TSecond, TResult> func)
D>


Кстати, такая функция должна называться не Zip, а Map/Map2/Select/Select2, так как он позволяет произвольное отображение. А Zip на шарпе можно создать только с использованием типа наподобии System.Collections.Generic.KeyValuePair, так как Zip должен возвращать список кортежей, а оных в C# нет. На роль кортежей могли бы пойти анонимные типы, но их нельзя описать, а стало быть и использовать в качестве возвращаемых значений функций.
    public static IEnumerable<KeyValuePair<TFirst, TSecond>> Zip<TFirst, TSecond>(
        this IEnumerable<TFirst> first,
        IEnumerable<TSecond> second)
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Детский вопрос по LINQ
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.10.08 16:30
Оценка:
Здравствуйте, desco, Вы писали:

D>Например, Seq.zip из стандартной библиотеки F# игнорирует остаток последовательности большей длины.

D>Zip из Haskell ведет себя также:
D>

D>If one input list is short, excess elements of the longer list are discarded.


Вот только хорошо ли это? Насколько часто встречаются случаи когда разница в длине не важна?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Детский вопрос по LINQ
От: Аноним  
Дата: 05.10.08 01:56
Оценка:
Здравствуйте, desco, Вы писали:

D>Например, Seq.zip из стандартной библиотеки F# игнорирует остаток последовательности большей длины.

D>Zip из Haskell

Хаскель оперирует только ленивыми последовательностями, F# — как ленивыми (Seq), так и строгими (List и Array)
Для ленивых, как мне кажется, общим паттерном является "игнорировать несоответствие", для строгих — "кидать исключение".
LINQ — ленив (LINQ-овский Take, например, соответствует "ленивой парадигме" — возвращает "все что есть", а не кидает исключением если запрошено больше элементов чем имеется), так то данная имплементация (которая кстати соответствует хаскельному TakeWith) мне видится абсолютно верной.

To VladD2: Насчет "как часто приходтся зиповать последовательности разной длины" — да постоянно. Чаще как раз приходится зиповать разной длины (особенно если участвуют бесконечные списки):
fib :: Integer -> Integer
fib n = fibs !! n
  where
    fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

Т.е. зачастую до конца какого-либо списка дело просто и не доходит:
var q = new[] { 1, 2, 3, 4 }.ZipWith(i1, i2 => i1 + i2, new[] { 1, 2, 3 }).Take(2);
Re: Удобное получение имени свойства
От: adanov  
Дата: 05.10.08 09:49
Оценка: 25 (1)
Здравствуйте, SergASh, Вы писали:

SAS>Есть две последовательности одинаковой длины. Как для них построить соединение, которое бы соотносило элементы с одинаковыми индексами?


SAS>То есть на входе { 1, 2, 3 }, { "ein", "zwei", "drei" }. А на выходе { { 1, "ein" }, { 2, "zwei" }, { 3, "drei" } }.


SAS>Интересует человеческий вариант на LINQ-операторах...


А вот так в рамках Ваших условий можно?
      int[] digits ={ 1, 2, 3 };
      string[] german = { "ein", "zwei", "drei" };

      // вариант 1
      var c = digits.Select((digit, index) => new { Digit = digit, Phrase  = german[index] });

      // вариант 2, медленный
      var d = digits.Select((digit, index) => new { Digit = digit, Phrase = german.Skip(index).First() });

      foreach (var e in c)
      {
          Console.WriteLine("" + e.Digit + "  " + e.Phrase);
      }
Re[2]: Удобное получение имени свойства
От: SergASh  
Дата: 07.10.08 11:04
Оценка:
Здравствуйте, adanov, Вы писали:

A>А вот так в рамках Ваших условий можно? ...


Не-а. Это был наколенный пример, в реале на входе идут IEnumerable<...>
Re[2]: Детский вопрос по LINQ
От: SergASh  
Дата: 07.10.08 11:25
Оценка:
Здравствуйте, desco, Вы писали:

Согласен, Zip/Select2 вполне решают задачу, но изначально я хотел узнать есть ли способ получить индекс элемента последовательности используя только LINQ-операторы, а не extension-методы. Похоже что нет
Re[3]: Re[2]: Детский вопрос по LINQ
От: adanov  
Дата: 07.10.08 16:55
Оценка:
Здравствуйте, SergASh, Вы писали:

SAS>Здравствуйте, adanov, Вы писали:


A>>А вот так в рамках Ваших условий можно? ...

SAS>Не-а. Это был наколенный пример, в реале на входе идут IEnumerable<...>

Тогда вариант №3:
    static void Main(string[] args)
    {
        var c = from a in digits(10).Select((e, i) => new { e, i })
                join b in german(10).Select((e, i) => new { e, i })
                on a.i equals b.i
                select new { d = a.e, f = b.e };
        foreach (var e in c)
        {
            Console.WriteLine("" + e.d + "  " + e.f);
        }
    }

    static IEnumerable<int> digits(int n)
    {
        for (int i = 1; i <= n; i++)
            yield return i;
    }

    static IEnumerable<string> german(int n)
    {
        for (int i = 1; i <= n; i++)
            yield return string.Format("{0:D2}", i);
    }

Итоговая сложность алгоритма O(N) для n < 10000, затем негативоно сказывается нехватка кэша.

PS:
Для варината №2 сложность O(N^2)
Re[4]: Детский вопрос по LINQ
От: adanov  
Дата: 07.10.08 17:00
Оценка:
Рекомендую пошагово посмотреть ход выполнения операторов.
Re: Детский вопрос по LINQ
От: Юнусов Булат Россия  
Дата: 09.10.08 18:24
Оценка:
Здравствуйте, SergASh, Вы писали:

    var digits = new[] { 1, 2, 3 };
    var german = new[]  { "ein", "zwei", "drei" };
    var q = Enumerable.Range(0, digits.Count()).Select(i => new { Digit = digits[i], Phrase = german[i] });
Re[2]: Детский вопрос по LINQ
От: samius Япония http://sams-tricks.blogspot.com
Дата: 09.10.08 18:27
Оценка:
Здравствуйте, Юнусов Булат, Вы писали:

ЮБ>Здравствуйте, SergASh, Вы писали:


ЮБ>
ЮБ>    var digits = new[] { 1, 2, 3 };
ЮБ>    var german = new[]  { "ein", "zwei", "drei" };
ЮБ>    var q = Enumerable.Range(0, digits.Count()).Select(i => new { Digit = digits[i], Phrase = german[i] });
ЮБ>


По условию на входе последовательности.
Считаем, что
var digits = new[] { 1, 2, 3 }.AsEnumerable();
var german = new[] { "ein", "zwei", "drei" }.AsEnumerable();
Re[3]: Детский вопрос по LINQ
От: Юнусов Булат Россия  
Дата: 09.10.08 18:38
Оценка:
Здравствуйте, samius, Вы писали:

S>По условию на входе последовательности.

S>Считаем, что
S>
S>var digits = new[] { 1, 2, 3 }.AsEnumerable();
S>var german = new[] { "ein", "zwei", "drei" }.AsEnumerable();
S>


Разница небольшая
    var digits = new[] { 1, 2, 3 }.AsEnumerable();
    var german = new[]  { "ein", "zwei", "drei" }.AsEnumerable();
    var z = Enumerable.Range(0, digits.Count()).Select(i => new { Digit = digits.ElementAt(i), Phrase = german.ElementAt(i) });
Re[4]: Детский вопрос по LINQ
От: samius Япония http://sams-tricks.blogspot.com
Дата: 09.10.08 18:43
Оценка:
Здравствуйте, Юнусов Булат, Вы писали:

ЮБ>Разница небольшая

ЮБ>
ЮБ>    var digits = new[] { 1, 2, 3 }.AsEnumerable();
ЮБ>    var german = new[]  { "ein", "zwei", "drei" }.AsEnumerable();
ЮБ>    var z = Enumerable.Range(0, digits.Count()).Select(i => new { Digit = digits.ElementAt(i), Phrase = german.ElementAt(i) });
ЮБ>


Разница во временной сложности. В этом решении она квадратичная.
Re[5]: Детский вопрос по LINQ
От: Юнусов Булат Россия  
Дата: 09.10.08 19:07
Оценка: +1
Здравствуйте, samius, Вы писали:

S>Разница во временной сложности. В этом решении она квадратичная.

Согласен, вариант от desco тут предпочтительнее.
Re[5]: Детский вопрос по LINQ
От: Константин Л.  
Дата: 09.10.08 20:44
Оценка:
Здравствуйте, samius, Вы писали:

S>Здравствуйте, Юнусов Булат, Вы писали:


ЮБ>>Разница небольшая

ЮБ>>
ЮБ>>    var digits = new[] { 1, 2, 3 }.AsEnumerable();
ЮБ>>    var german = new[]  { "ein", "zwei", "drei" }.AsEnumerable();
ЮБ>>    var z = Enumerable.Range(0, digits.Count()).Select(i => new { Digit = digits.ElementAt(i), Phrase = german.ElementAt(i) });
ЮБ>>


S>Разница во временной сложности. В этом решении она квадратичная.


я бы написал правило для fxcop, которое бы на IEnumerable.Count выдавало warning. Можно даже в компайлер добавить
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.