Почему тот же алгоритм работает в Scala намного медленнее, чем в C #?И как сделать это быстрее? - PullRequest
3 голосов
/ 16 апреля 2011

Алгоритм создает все возможные варианты последовательности из вариантов для каждого члена последовательности.

Код C #:

static void Main(string[] args)
{
  var arg = new List<List<int>>();
  int i = 0;
  for (int j = 0; j < 5; j++)
  {
    arg.Add(new List<int>());
    for (int j1 = i; j1 < i + 3; j1++)
    {
      //if (j1 != 5)
      arg[j].Add(j1);
    }
    i += 3;
  }
  List<Utils<int>.Variant<int>> b2 = new List<Utils<int>.Variant<int>>();
  //int[][] bN;

  var s = System.Diagnostics.Stopwatch.StartNew();
  //for(int j = 0; j < 10;j++)
    b2 = Utils<int>.Produce2(arg);
  s.Stop();
  Console.WriteLine(s.ElapsedMilliseconds);

}


public class Variant<T>
{
  public T element;
  public Variant<T> previous;
}


public static List<Variant<T>> Produce2(List<List<T>> input)
{
  var ret = new List<Variant<T>>();
  foreach (var form in input)
  {
    var newRet = new List<Variant<T>>(ret.Count * form.Count);
    foreach (var el in form)
    {
      if (ret.Count == 0)
      {
        newRet.Add(new Variant<T>{ element = el, previous = null });
      }
      else
      {
        foreach (var variant in ret)
        {
          var buf = new Variant<T> { previous = variant, element = el };
          newRet.Add(buf);
        }
      }
    }
    ret = newRet;
  }
  return ret;
}

Код Scala:

object test {
def main() {
 var arg = new Array[Array[Int]](5)
 var i = 0
 var init = 0
 while (i<5)
 {
  var buf = new Array[Int](3)
  var j = 0
  while (j<3)
  {
   buf(j) = init
   init = init+1
   j = j + 1
  }
  arg(i)=buf
  i = i + 1
 }
 println("Hello, world!")
 val start = System.currentTimeMillis
 var res = Produce(arg)
 val stop = System.currentTimeMillis
 println(stop-start)
 /*for(list <- res)
  {
   for(el <- list)
    print(el+" ")
   println
  }*/
 println(res.length)
}

 def Produce[T](input:Array[Array[T]]):Array[Variant[T]]=
  {
   var ret = new Array[Variant[T]](1)
   for(val forms <- input)
   {
    if(forms!=null)
    {
     var newRet = new Array[Variant[T]](forms.length*ret.length)
     if(ret.length>0)
     {
      for(val prev <-ret)
       if(prev!=null)
       for(val el <-forms)
       {
        newRet = newRet:+new Variant[T](el,prev)
       }
     }
     else
     {
      for(val el <- forms)
        {
         newRet = newRet:+new Variant[T](el,null)
        }
     }
     ret = newRet
    }
   }
   return ret
  }


}

class Variant[T](var element:T, previous:Variant[T])
{
}

Ответы [ 3 ]

20 голосов
/ 16 апреля 2011

Как уже говорили другие, разница в том, как вы используете коллекции. Array в Scala - это то же самое, что примитивный массив Java, [], который совпадает с примитивным массивом C # []. Scala достаточно умен, чтобы делать то, что вы просите (а именно, скопировать весь массив с новым элементом в конце), но не настолько умен, чтобы сказать вам, что вам лучше использовать другую коллекцию. Например, если вы просто измените Array на ArrayBuffer, это должно быть намного быстрее (сравнимо с C #).

На самом деле, вам бы лучше вообще не использовать циклы for. Одна из сильных сторон библиотеки коллекций Scala заключается в том, что в вашем распоряжении широкий спектр мощных операций. В этом случае вы хотите взять каждый элемент из forms и преобразовать его в Variant. Вот что делает map.

Кроме того, ваш код Scala, похоже, на самом деле не работает.

Если вам нужны все возможные варианты от каждого участника, вы действительно хотите использовать рекурсию. Эта реализация делает то, что вы говорите, что вы хотите:

object test {
  def produce[T](input: Array[Array[T]], index: Int = 0): Array[List[T]] = {
    if (index >= input.length) Array()
    else if (index == input.length-1) input(index).map(elem => List(elem))
    else {
      produce(input, index+1).flatMap(variant => {
        input(index).map(elem => elem :: variant)
      })
    }
  }

  def main() {
    val arg = Array.tabulate(5,3)((i,j) => i*3+j)
    println("Hello, world!")
    val start = System.nanoTime
    var res = produce(arg)
    val stop = System.nanoTime
    println("Time elapsed (ms): " + (stop-start)/1000000L)
    println("Result length: " + res.length)
    println(res.deep)
  }
}

Давайте немного распакуем это. Во-первых, мы заменили всю вашу конструкцию начальных вариантов одной инструкцией tabulate. tabulate принимает целевой размер (здесь 5x3), а затем функцию, которая отображается из индексов в этот прямоугольник в конечное значение.

Мы также сделали produce рекурсивной функцией. (Обычно мы делаем это хвостовой рекурсией, но давайте пока все упростим.) Как вы генерируете все варианты? Ну, все варианты ясно (каждая возможность в этой позиции) + (все варианты из более поздних позиций). Поэтому мы записываем это рекурсивно.

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

Теперь, как мы на самом деле делаем рекурсию? Ну, если вообще нет данных, нам лучше вернуть пустой массив (т.е. если index находится за концом массива). Если мы находимся в последнем элементе массива вариантов, мы в основном хотим, чтобы каждый элемент превратился в список длины 1, поэтому мы используем map, чтобы сделать именно это (elem => List(elem)). Наконец, если мы не в конце, мы получаем результаты от остальных (то есть produce(input, index+1)) и создаем варианты для каждого элемента.

Давайте сначала возьмем внутренний цикл: input(index).map(elem => elem :: variant). Это берет каждый элемент из вариантов в позиции index и прикрепляет их к существующему variant. Так что это даст нам новую партию вариантов. Справедливо, но откуда мы берем новый вариант? Мы производим его из остальной части списка: produce(input, index+1), и тогда единственная хитрость в том, что нам нужно использовать flatMap - это берет каждый элемент, создает из него коллекцию и склеивает все эти коллекции вместе.

Я призываю вас бросать printlns в разные места, чтобы посмотреть, что происходит.

Наконец, обратите внимание, что с вашим размером теста, это фактически незначительный объем работы; Вы не можете точно измерить это, даже если вы переключитесь на использование более точного System.nanoTime, как я. Вам понадобится что-то вроде tabulate(12,3), прежде чем оно станет значительным (500 000 вариантов).

4 голосов
/ 16 апреля 2011

Метод: + массива (точнее, ArrayOps) всегда создает копию массива. Таким образом, вместо операции с постоянным временем у вас есть та, которая больше или меньше O (n). Вы делаете это в течение вложенных циклов => весь ваш материал будет на порядок медленнее.

Таким образом, вы более или менее эмулируете неизменную структуру данных с изменяемой (которая не была предназначена для нее).

Чтобы исправить это, вы можете использовать массив в качестве изменяемой структуры данных (но затем попытайтесь избежать бесконечного копирования) или переключиться на неизменяемое. Я не очень тщательно проверял ваш код, но первой ставкой, как правило, является «Список», проверьте скалярный код различных методов, чтобы увидеть их поведение.

0 голосов
/ 16 апреля 2011

ret.length не всегда равен 0, перед возвратом он равен 243. Размер массива не должен изменяться, а List в .net является абстракцией над массивом.НО спасибо за точку - проблема была в том, что я использовал: + оператор с массивом, который, как я понимаю, вызвал неявное использование типа LinkedList

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...