Как найти минимум с бинарным поиском на интервале в Scala? - PullRequest
0 голосов
/ 02 июля 2018

Хорошо, я пытаюсь найти минимум заданного интервала с помощью бинарного поиска в Scala, но этот минимум должен соответствовать и данной функции. Вот мой код:

def binarySearch: (Int => Boolean) => (Int, Int) => Int = f => (l, h) => {
  def bs: ((Int => Boolean) => (Int, Int, Int) => Int) = f => (l, h, minimum) => {

    val mid = l + ((h-l) / 2)

    mid match{
      case mid if(f(mid) == false) => bs(f)(mid+1, h, mid)
      case mid if(f(mid) == true && mid > minimum) => bs(f)(l, mid-1, minimum)
      case mid if(f(mid) == true && mid < minimum) => bs(f)(mid+1, h, mid)
      case mid => mid
    }
  }
  bs(f)(l, h, 0)
}

Я думаю, что моя проблема в том, что я неправильно сохраняю минимум.

Тестовый пример может выглядеть так:

val v = Vector(0, 1, 2, 3, 7, 9)
binarySearch(v(_) < 5)(0, v.length) == 4

Есть идеи?

Ответы [ 2 ]

0 голосов
/ 08 июля 2018

Мой код был слишком сложен с помощью вспомогательной функции. Это возможное правильное решение:

def binarySearch: (Int => Boolean) => (Int, Int) => Int = f => (l, h) => {
val mid = l + ((h-l) / 2)
mid match {
  case _ if(l >= h) => h
  case mid if(f(mid)) => binarySearch(f)(l,mid)
  case mid => binarySearch(f)(mid+1, h)
}

}

К сожалению, мы должны использовать этот стиль объявления метода

0 голосов
/ 02 июля 2018

Этот стиль объявления методов очень необычен и делает ваш код трудным для чтения и понимания. Итак, мой первый шаг - воспроизвести ваш код в более идиоматической, обычной форме.

Я предпринял следующие шаги:

  1. ИМХО, предпочтительно, чтобы функции принимали аргументы, а не возвращали анонимные функции, принимающие аргументы. Это намного проще и чище, а ваши намерения проясняются.
  2. Обычно называют предикатом функции - те, которые обычно принимают один аргумент и возвращают Boolean & mdash; p.
  3. В bs переопределение аргумента функции предиката является избыточным; он может повторно использовать предикат, предоставленный для binarySearch, и поэтому не нуждается в его повторном объявлении.
  4. При проверке значений Boolean принято оставлять значение обычным. То есть, если boolExpr является выражением Boolean, имеющим значение true или false, вы должны предпочесть if(boolExpr) if(boolExpr == true) и if(!boolExpr) if(boolExpr == false).
  5. Случай по умолчанию, case _ может использоваться, если ни один из предыдущих случаев не совпадает. Это немного понятнее, чем case mid в вашем коде.

Ваш оригинальный код становится:

def binarySearch(p: Int => Boolean)(l: Int, h: Int): Int = {

  def bs(l: Int, h: Int, minimum: Int): Int = {

    val mid = l + ((h - l) / 2)

    mid match {
      case mid if(!p(mid)) => bs(mid+1, h, mid)
      case mid if(p(mid) && mid > minimum) => bs(l, mid-1, minimum)
      case mid if(p(mid) && mid < minimum) => bs(mid+1, h, mid)
      case _ => mid
    }
  }
  bs(l, h, 0)
}

ОК, теперь мы подошли к отладке вашей функции. Вы утверждаете, что он должен вычислять минимум данного интервала и что минимум должен соответствовать также данной функции . Или, другими словами, он должен найти минимальное значение в диапазоне, который удовлетворяет предикату.

Однако в вашем тестовом случае ваш предикат состоит в том, что значение должно быть меньше 5, и вы ожидаете значение 4 в качестве ответа.

Некоторые проблемы понятны:

  1. Ваш код предполагает, но не проверяет, что данные упорядочены от низкого до высокого. Я просто упоминаю об этом, потому что, очевидно, код потерпит неудачу, если данные не будут упорядочены. Если вы можете гарантировать, что предположение верно, то это нормально.
  2. l, h, mid и minimum - это все индексы значений, принадлежащих Vector, которые недоступны из binarySearch - но сами они не являются значениями. Это противоречит вашему утверждению, что вы ищете минимальное значение; скорее, похоже, вы ищете index минимального значения.
  3. В вашем тестовом условии вы ожидаете значение 4, которое является индексом значения 7. Однако 7 не соответствует предикату, так как он не меньше 5. Это наводит меня на мысль, что ваша функция предиката в вашем тесте должна быть binarySearch(v(_) >= 5)(0, v.length).
  4. Вы не проверяете, что l меньше h в binarySearch, указывая, что у вас нет диапазона для поиска. Если это происходит в bs, то вы должны рассматривать его как условие завершения (диапазон был полностью найден) и возвращать найденный индекс минимального значения. (Похоже, у вас нет такого условия завершения в существующем коде, поэтому при определенных обстоятельствах он может зацикливаться бесконечно, что в итоге приводит к StackOverflowError.)
  5. Следует отметить, что бинарный поиск с функцией предиката является принципиально некорректным, если только предикат не разделяет диапазон так, что индексы всех значений, которые не соответствуют предикату, являются последовательными и появляются в начале диапазона, и что индексы все значения, которые передают предикат, являются последовательными в конце диапазона. Чтобы проиллюстрировать это, рассмотрим, что произойдет, если предикат принимает только четные значения: binarySearch(v(_) % 2 == 0)(0, v.length)? (Подсказка: ваша функция должна была бы посещать каждый элемент в диапазоне, чтобы гарантировать поиск минимума, а это не то, что делает бинарный поиск.)
  6. Если ваш поиск не может найти минимальное значение, которое удовлетворяет предикату, то он не может сигнализировать об этом. По этой причине я думаю, что ваша функция должна вернуть Option[Int] со значением None, возвращаемым, если значение не может быть найдено, и Some(minimum) в противном случае.
  7. Передача v.length для h вызовет выброс IndexOutOfBoundsException, если будет проверено значение элемента с этим индексом. Вместо этого вы должны передать v.length - 1 или трактовать h как одну позицию за пределами диапазона.

UPDATE

Чтобы рассмотреть вашу реализацию, я немного реструктурировал проблему. Теперь функция binarySearch использует двоичный поиск, чтобы найти самое низкое значение, которое больше указанного минимума. Для этого вместо функции предиката с низким и высоким индексом она принимает IndexedSeq с именем seq и значение minimum, которое может быть принято.

// Return None if no value found, or Some(min index) otherwise.
def binarySearch(seq: IndexedSeq[Int], minimum: Int): Option[Int] = {

  // Helper function. Check middle value in range. Note: h is one position beyond end of
  // current range.
  def bs(l: Int, h: Int, min: Option[Int]): Option[Int] = {

    // If the l index isn't less than the h index, then we have nothing more to search for.
    // Return whatever our current minimum is.
    if(l >= h) min

    // Otherwise, find the middle index value of this range.
    else {
      val mid = l + ((h - l) / 2)
      assert(mid >= l && mid < h) // Sanity check.

      // Determine if the middle value is large enough.
      if(seq(mid) >= minimum) {

        // OK. So this value qualifies. Update our minimum. Any potentially lower values are
        // going to be in the range below mid, so look there next.
        val nextMin = min.filter(_ < mid).orElse(Some(mid))
        bs(l, mid, nextMin)
      }

      // No luck. Search the range above mid with the same minimum.
      else bs(mid + 1, h, min)
    }
  }

  // Start the search. Our initial minimum value is None.
  bs(0, seq.length, None)
}

Вот несколько примеров в Scala REPL :

scala> val v = Vector(0, 1, 2, 3, 7, 9)
v: scala.collection.immutable.Vector[Int] = Vector(0, 1, 2, 3, 7, 9)

scala> binarySearch(v, 5)
res0: Option[Int] = Some(4)

scala> binarySearch(v, 9)
res1: Option[Int] = Some(5)

scala> binarySearch(v, 50)
res2: Option[Int] = None

scala> binarySearch(v, -1)
res3: Option[Int] = Some(0)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...