Добавьте элемент (Int, Double ...) в конец списка типов Any. - PullRequest
0 голосов
/ 23 января 2019

Я пытаюсь добавить элемент в конец списка типа «Любой» (List [Any]).Я хочу использовать рекурсивную функцию для ее построения, идея в том, что «если мне понадобится этот элемент, я добавлю его, когда итерация закончится, мой список будет завершен».В следующем коде идея такова: «У меня есть список« l », если элемент« elem »находится во главе« l », я добавлю его позицию, сохраненную в« index », как следующий элемент списка« ret ».'. В противном случае я проверю следующий элемент и ничего не буду делать (я использую' l :: support ... 'только для соответствия возвращаемого вида как List [Any]). Когда' l 'пусто (или Nil)дайте мне список 'ret'. В конце 'ret' это список, который содержит позицию всех элементов 'elem', найденных в списке 'l' ".Это очень важно: список, который я создаю, это 'ret', фактически это возвращение рекурсивной функции!

Я пробовал с "::", "+:", ": +", ноони не работали.Ошибка была одна и та же каждый раз: «error: value :: не является членом параметра типа Any».

object find{
    def support[Any](l:List[Any],elem:Any,index:Int,ret:List[Any]):List[Any]={
        if (l==Nil) ret;
        else if(l.head==elem) (l :: support(l.tail,elem,index+1,ret :: (index.asInstanceOf[Any])));
        else (l :: support(l.tail,elem,index+1,ret));       
    }
    def findIndices[Any](l:List[Any],x:Any):List[Any]={//I want to find all the elements "elem" in the list "l"
        val a:List[Any]=support(l,x,0,List[Any]());//I'll build my list using an auxiliary function "support"
        a;
    }
}
object main extends App{
    val l:List[Int]=List(1,2,2,2,2,3,4,5)
    val x:Int=2;
    val f:List[Int]=find.findIndices(l,x)
    println(f)
}

Я открыт для всех рабочих решений, но сначала попробуйте ответить на мой вопроси объясните, как вы это делаете и почему.Я изучаю этот язык, я пришел из C и Java.

Ответы [ 3 ]

0 голосов
/ 23 января 2019

TL; DR One Line Решение:

val list = List(1,2,2,2,2,3,4,5)
list.zipWithIndex.filter(_._1 == 2).map(_._2) //  List(1, 2, 3, 4)

Или назовите свои переменные с помощью регистра:

list.zipWithIndex.filter { case(value,index) => value == 2 } map { case(value,index) => index }

Или используйте метод сбора для объединения фильтраи карта:

list.zipWithIndex.collect { case (value,index) if value == 2 => index }

Рекурсия

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

Итак, учитывая список

val list = List(1,2,2,2,2,3,4,5) // named list instead of l because l looks like 1.

Нам нужна функция findIndices такая, что findIndices(list,2) возвращает List(1,2,3,4)

Я собираюсь начать с определения findIndices следующим образом:

def findIndices(list: List[Any], element: Any): List[Any] = ???

Теперь есть паравещи, которые я хочу изменить прямо сейчас.Во-первых, ваш пример использует список вещей с одним типом, так что это выглядит как отличная возможность использовать параметризованный тип.

def findIndices[T](list: List[T], element: T): List[Any] = ???

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

def findIndices[T](list: List[T], element: T): List[Int] = ???

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

  1. проверка на завершение регистра.
  2. обработка не завершающих регистра.

Итак, моя функция будет выглядеть примерно так:

def findIndices[T](list: List[T], element: T): List[Int] = {
  // if (terminating case) return termination
  // else if (check for next index of element) return index plus recursive call
  // else return recursive call
}

Заполнение пробелов дает нам что-то вроде этого:

def findIndices[T](list: List[T], element: T): List[Int] = {
  if (list.isEmpty) List.empty[Int]
  else if (list.head == element) 0 :: findIndices(list.tail, element)
  else findIndices(list.tail, element)
}

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

def findIndices[T](list: List[T], element: T, offset: Int = 0): List[Int] = {
  if (list.isEmpty) List.empty[Int]
  else if (list.head == element) offset :: findIndices(list.tail, element, offset + 1)
  else findIndices(list.tail, element, offset + 1)
}

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

def findIndices[T](list: List[T], element: T, offset: Int = 0): List[Int] = ???

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

единственное, что сделает наша внешняя функция, это вызовет внутреннюю функцию с начальными параметрами.

def findIndices[T](list: List[T], element: T, offset: Int = 0): List[Int] = {

  def findIndicesAcc(list: List[T], element: T, acc: List[Int], offset: Int = 0): List[Int] = {
    // do logic here
  }

  findIndicesAcc(list, element, List.empty[Int])
}

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

def findIndices[T](list: List[T], element: T, offset: Int = 0): List[Int] = {

  def findIndicesAcc(list: List[T], element: T, acc: List[Int], offset: Int = 0): List[Int] = {
    if (list.isEmpty) acc
    else if (list.head == element) findIndicesAcc(list.tail, element, offset + 1, offset :: acc)
    else findIndicesAcc(list.tail, element, offset + 1, acc)
  }

  findIndicesAcc(list, element, List.empty[Int])
}

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

import scala.annotation.tailrec

def findIndices[T](list: List[T], element: T): List[Int] = {

  @tailrec
  def findIndicesAcc(list: List[T], element: T, offset: Int, acc: List[Int]): List[Int] = {
    if (list.isEmpty) acc
    else if (list.head == element) findIndicesAcc(list.tail, element, offset + 1, offset :: acc)
    else findIndicesAcc(list.tail, element, offset + 1, acc)
  }

  findIndicesAcc(list, element, 0, List.empty[Int]).reverse
}

Подробнее о рекурсии Tail

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

else if (list.head == element) offset :: findIndices(list.tail, element, offset + 1)

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

else if (list.head == element) {
  val tail = list.tail      
  val newOffset = offset + 1
  val recursiveResult = findIndices(tail, element, newOffset)
  return offset :: recursiveResult
}

Когда код разбит таким образом, мы можем видеть, что послеfindIndices recursive * call return.

С другой стороны, фрагмент кода

else if (list.head == element) findIndicesAcc(list.tail, element, offset + 1, offset :: acc)

является хвостово-рекурсивным.Когда мы разбиваем операции на отдельные строки, мы получаем

else if (list.head == element) {
  val tail = list.tail
  val newOffset = offset + 1
  val combinedList = offset :: acc
  return findIndicesAcc(tail, element, newOffset, combinedList)
}

и ясно видим, что вызов findIndicesAcc - это последнее, что происходит.

В первом случаеПрограмма вынуждена поддерживать весь стек вызовов, потому что она должна помнить, какое значение offset было в каждой предыдущей итерации функции.На машинах Linux у нас обычно есть 8 МБ стека.Для очень длинных списков мы используем все 8 МБ стека, что приводит к исключению переполнения стека.

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

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

0 голосов
/ 23 января 2019

Решение полностью ручной работы

def reverse[T](list: List[T]): List[T] = {
  @annotation.tailrec
  def loop(remaining: List[T], acc: List[T]): List[T] = remaining match {
    case Nil => acc
    case x :: xs => loop(remaining = xs, acc = x :: acc)
  }
  loop(remaining = list, acc = Nil)
}

def findIndices[T](elem: T, list: List[T]): List[Int] = {
  @annotation.tailrec
  def loop(remaining: List[T], acc: List[Int], currentIndex: Int): List[Int] = remaining match {
    case Nil => acc
    case x :: xs =>
      val newAcc = if (x == elem) currentIndex :: acc else acc
      loop(remaining = xs, newAcc, currentIndex + 1)
  }
  reverse(loop(remaining = list, acc = Nil, currentIndex = 0))
}

Использование стандартных функций высшего порядка

def findIndicesStd[T](elem: T, list: List[T]): List[Int] =
  list.zipWithIndex.filter(_._1 == elem).map(_._2)

Результаты

findIndices(elem = 5, list = List(1, 5, 3, 5, 8, 5, 5, 10))
// res0: List[Int] = List(1, 3, 5, 6)

findIndicesStd(elem = 5, list = List(1, 5, 3, 5, 8, 5, 5, 10))
// res1: List[Int] = List(1, 3, 5, 6)

Объяснение

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

Однако я отвечу на несколько вопросов, которые, я полагаю, будут у вас сейчас:

  • Что означает @annotation.tailrec? 1021 *В коде ничего на самом деле.Это аннотация компилятора, которая указывает компилятору проверить, является ли данная функция рекурсивной за хвостом, и если нет, выдать предупреждение - это больше похоже на то, что функция не будет взрывать стек .

  • Почему T вместо Any?: T здесь означает универсальный, это как заполнитель типа, говорящий, чтоработает для "любого" типа.Any с другой стороны, это конкретный тип (супер тип всего).Они выглядят одинаково, но универсальный гарантирует, что вы не потеряете информацию о типе (если у вас есть список Ints и вы reverse, он получит список Int, а не Any, обратно) .

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

  • Что означает _._1 & _._2?: Первое подчеркивание используется для анонимных функций, это означает первый (и только в этом случае) параметр, _1 & _2 - это методы (как вы можете видеть, потому что они вызываются с точкой .) в классе Tuple, они получают доступ к первому и второму элементам кортежа .

0 голосов
/ 23 января 2019

Я думаю, вы должны узнать немного больше о дженериках в Scala.В частности, идея вызова универсального параметра Any, чтобы он скрывал стандартный тип Any, является очень плохой идеей.Также это может помочь узнать о сопоставлении с образцом, который является мощным инструментом для замены некоторых if/else if/else операторов.Я считаю, что код, который вы хотите, выглядит следующим образом:

object find {
  @tailrec
  def support[A](l: List[A], elem: A, index: Int, ret: List[Int]): List[Int] = l match {
    case Nil => ret
    case head :: tail if head == elem => support(tail, elem, index + 1, ret :+ index)
    // _ meas here we ignore head anyway so don't need a variable for that
    case _ :: tail => support(tail, elem, index + 1, ret)
  }

  def findIndices[A](l: List[A], x: A): List[Int] = {
    //I want to find all the elements "elem" in the list "l"
    support(l, x, 0, List.empty[Int]) //I'll build my list using an auxiliary function "support"
  }
}

Вы можете попробовать этот код онлайн здесь

Обратите внимание, что я сделал некоторые улучшения, но есть еще нескольковозможны улучшенияНапример, обычно такой support метод помещается внутри findIndices, поэтому он недоступен извне.Также :+ очень медленный на List с.Как правило, быстрее создать список результатов, добавив его в начало, а затем reverse один раз в самом конце.

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