2014-02-21 2 views
0

Учитывая массив целых чисел и значение, я пытался выяснить, насколько ближе значение от любого из концов, если оно присутствует в массиве. И тем самым создать карту. Я попытался решить его с использованием неизменяемых карт данных и функциональным способом, но он очень неэффективен из перспективы вычислений по сравнению с тем, что было бы, если бы я писал императивным способом (java way). Я считаю, что это связано с моим неполным пониманием функционального стиля кодирования, а не присущей разнице между стилями.Эффективность с функциональным программированием

val typeSum = 8 
val data = List(2,3,4,5,2,3) 
val dogTimes:scala.collection.mutable.Map[Int,Int] = scala.collection.mutable.Map() withDefaultValue(-1); 
for (x <- 1 to (data.length)/2){ 
    if (dogTimes(data(x-1)) > x || dogTimes(data(x-1)) < 0) dogTimes(data(x-1)) = x; 
} 
for(x <- (data.length/2 + 1) to data.length){ 
    if (dogTimes(data(x-1)) > (data.length - x)|| dogTimes(data(x-1)) < 0) 
     dogTimes(data(x-1)) = data.length - x+1; 
} 
if (typeSum%2 ==0) dogTimes(typeSum/2) = -1 

Это код, который я мог бы написать в функциональном стиле и медленнее, чем приведенный выше код. Как улучшить следующий код для повышения эффективности?

val tempDogTimes = data.zipWithIndex groupBy(_._1) mapValues(w => 
    List(w.head._2+1,data.length - w.last._2).min) withDefaultValue(-1) 
val dogTimes = collection.mutable.Map[Int,Double]() ++= tempDogTimes 
if (typeSum%2 ==0) dogTimes(typeSum/2) = -1 

Примечания: Это часть проблемы я представил на конкурс и императивный код был принят, а следующий один дал время превышена ошибке.

+0

Почему вы создаете карту дважды в функциональной версии? Я имею в виду, что у вас есть карта в 'tempDogTimes', а затем вы создаете _another_ map в' dogTimes' с первого. Зачем? –

+0

@ DanielC.Sobral Мне нужно удалить ключ типаSum/2 с карты, которая невозможна в неизменяемой карте. –

+0

На самом деле, вы можете создать новую неизменяемую карту с переопределенным значением - это использует структурный обмен, который является целым рядом постоянных неизменных структур данных. –

ответ

3

Позвольте мне втирать сон из глаз. Вы хотите пройти список с любого конца, записывая первый раз, когда видите каждый элемент, не так ли?

scala> val data = List(2,3,4,5,2,3) 
data: List[Int] = List(2, 3, 4, 5, 2, 3) 

scala> val is = ((data take (data.size/2)), (data drop (data.size/2)).reverse).zipped 
is: scala.runtime.Tuple2Zipped[Int,List[Int],Int,List[Int]] = [email protected] 

scala> .toList 
res0: List[(Int, Int)] = List((2,3), (3,2), (4,5)) 

scala> ((Map.empty[Int,Int], data.to[Set], 0) /: is) { case ((m, n, i), (x, y)) => 
    | if (n.isEmpty) (m, n, i+1) 
    | else (
    | m ++ List(if (m contains x) None else Some(x -> i), if (m contains y) None else Some(y -> i)).flatten, 
    | n -- Set(x,y), 
    | i + 1 
    |)} 
res1: (scala.collection.immutable.Map[Int,Int], Set[Int], Int) = (Map(2 -> 0, 3 -> 0, 4 -> 2, 5 -> 2),Set(),3) 

scala> ._1 
res2: scala.collection.immutable.Map[Int,Int] = Map(2 -> 0, 3 -> 0, 4 -> 2, 5 -> 2) 

Использование Vector и вид индексировать две половинки будет лучше. И построение набора элементов является посторонним, но было бы удобно, если бы вы уже знали домен.

Другой качели:

scala> val data = List(2,3,4,5,2,3).to[Seq] 
data: Seq[Int] = Vector(2, 3, 4, 5, 2, 3) 

scala> val half = data.size/2 
half: Int = 3 

scala> val vs = (data.view take half, (data.view drop half).reverse).zipped 
vs: scala.runtime.Tuple2Zipped[Int,scala.collection.SeqView[Int,Seq[Int]],Int,scala.collection.SeqView[Int,Seq[Int]]] = [email protected] 

scala> import collection.mutable 
import collection.mutable 

scala> val x = 4 // some key to exclude 
x: Int = 4 

scala> ((mutable.Map.empty[Int,Int].withDefaultValue(Int.MaxValue), 0) /: vs) { 
    | case ((m, i), (x, y)) => m(x) = m(x) min i; m(y) = m(y) min i; (m, i+1) } 
res4: (scala.collection.mutable.Map[Int,Int], Int) = (Map(2 -> 0, 5 -> 2, 4 -> 2, 3 -> 0),3) 

scala> ._1.filter { case (k, v) => k != x }.toMap 
res5: scala.collection.immutable.Map[Int,Int] = Map(2 -> 0, 5 -> 2, 3 -> 0) 

Я еще не уверен, является ли взгляды вынуждены складкой, так что цикл с индексацией может быть лучше. И разве SO не использовалось для прокрутки длинных строк горизонтально, а не для обертывания? Этот код нечитабелен.

+0

Как я могу удалить ключ typeSum перед созданием неизменяемой карты? –

+0

@ user1699696 Мне было слишком лениво использовать mutable.Map. Не уверен в прецеденте, но, альтернативно, не добавляйте этот ключ в первую очередь. –

1

У Scala есть элегантный способ соединить элементы списка со своими индексами: zipWithIndex. Как разделить список на две половинки, вы можете сделать два случая матча, с условием на первом:

val typeSum = 8 
val data = List(2, 3, 4, 5, 2, 3) 
val dogTimes: scala.collection.mutable.Map[Int, Int] = scala.collection.mutable.Map() withDefaultValue (-1) 
data.zipWithIndex foreach { 
    case (value, index) if (index < data.length/2) => { 
    if (dogTimes(value) > index + 1 || dogTimes(value) < 0) { 
     dogTimes(value) = index + 1 
    } 
    } 
    case (value, index) => { 
    if (dogTimes(value) > (data.length - index) || dogTimes(value) < 0) { 
     dogTimes(value) = data.length - index 
    } 
    } 
} 
if (typeSum % 2 == 0) dogTimes(typeSum/2) = -1 
2

Во-первых, позвольте мне сказать, что, как вы используете List - в изменяемом версии - ужасно. List имеет отвратительную производительность для индексированного доступа, который вы используете много. Для индексированного доступа используйте вместо этого Vector. Или Array, так как он изменен в любом случае.

На неизменной версии вы также используете length, что является O (n) для List, на каждой итерации. Просто позвонив length один раз за пределы цикла и сохранив его, чтобы повысить производительность. Вы также можете сделать это:

List(w.head._2+1,data.length - w.last._2).min 

который вроде медленно по сравнению с просто

(w.head._2+1) min (data.length - w.last._2) 

И, конечно, вы должны либо изменить структуру данных в Vector или заменить data.length с чем-то присвоенной только один раз.

Теперь я вижу два способа обойти это. Один из них заключается в том, чтобы пройти по карте двумя способами и получить минимум, как и вы, а другой - пройти его только один раз, как som snytt. Для первого вам действительно нужно изменить тип на Vector.Второй будет отлично работать с List.

Начнем с первого, который ближе к тому, что вы сделали. Я вообще не стремлюсь к никакой изменчивости, как упражнение. На практике я, вероятно, использовал бы var неизменяемого Map вместо рекурсии.

def dogTimes(data: IndexedSeq[Int], typeSum: Int): Map[Int, Int] = { 
    import scala.annotation.tailrec 

    val unwantedKey = typeSum/2 
    val end = data.length 
    val halfway = end/2 

    @tailrec 
    def forward(result: Map[Int, Int], i: Int): Map[Int, Int] = { 
    if (i > halfway) result 
    else if (data(i) == unwantedKey) forward(result, i + 1) 
    else if (result contains data(i)) forward(result, i + 1) 
    else forward(result updated (data(i), i + 1), i + 1) 
    } 

    @tailrec 
    def backward(result: Map[Int, Int], i: Int): Map[Int, Int] = { 
    println(s"$i ${data(i)} $result") 
    if (i < halfway) result 
    else if (data(i) == unwantedKey) backward(result, i - 1) 
    else if (result contains data(i)) backward(result updated (data(i), result(data(i)) min (end - i)), i - 1) 
    else backward(result updated (data(i), end - i), i - 1) 
    } 

    // forward has to be computed first 
    val fwd = forward(Map.empty[Int, Int], 0) 
    val bwd = backward(fwd, end - 1) 

    bwd 
} 

Это в значительной степени функциональной версия вашего изменяемого кода - это многословная и не реально использовать любого из методов сбора, чтобы помочь работе. Его также можно упростить - например, data.length % 2 не требуется, поскольку код внутри него всегда будет работать, будь то data.length четным или нечетным. И тесты contains также можно удалить, используя getOrElse в обновлениях.

Он также возвращает стандартную карту, а не стандартную. После этого вы можете добавить значение по умолчанию.

Другим способом было бы более или менее решение som snytt, но я предпочел бы сделать его немного проще из-за того, что в этом решении не требуется min. Здесь я принимаю Seq, который будет работать для List.

def dogTimes(data: Seq[Int], typeSum: Int): Map[Int, Int] = { 
    import scala.annotation.tailrec 

    val unwantedKey = typeSum/2 
    val half = data.length/2 + 1 
    val vs = (data.view take half zip data.view.reverse).zipWithIndex 

    val result = vs.foldLeft(Map.empty[Int, Int]) { 
    case (map, ((x, y), i)) => 
     val m1 = if (map.contains(x) || x == unwantedKey) map else map.updated(x, i + 1) 
     if (m1.contains(y) || y == unwantedKey) m1 else m1.updated(y, i + 1) 
    } 

    result 
} 

Я держал сома snytt-х view, но я подозреваю, что его выступление на обратном будет очень плохо для List. Это должно быть нормально для Vector, но я думаю, что удаление второго вызова view должно сделать это быстрее для List.

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

Также обратите внимание, что я выбираю half + 1 - это гарантирует, что я обработаю средний элемент в списках нечетного размера. Я не отбрасываю элементы перед их реверсированием, потому что zip всегда выбирает самый маленький размер.

Если мы решим требовать индексированные seqs, следующий, вероятно, быстрее:

def dogTimes(data: IndexedSeq[Int], typeSum: Int): Map[Int, Int] = { 
    import scala.annotation.tailrec 

    val unwantedKey = typeSum/2 
    val end = data.length 
    val halfway = end/2 

    val result = (0 to halfway).foldLeft(Map.empty[Int, Int]) { 
    case (map, i) => 
     val x = data(i) 
     val y = data(end - i - 1) 
     val m1 = if (map.contains(x) || x == unwantedKey) map else map.updated(x, i + 1) 
     if (m1.contains(y) || y == unwantedKey) m1 else m1.updated(y, i + 1) 
    } 

    result 
} 

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

Смежные вопросы