Уменьшить / сложить только некоторые элементы - PullRequest
0 голосов
/ 11 января 2019

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

trait Data {
  val identifier: String
}

case class Meta(identifier: String, props: Properties) extends Data
case class Complete(identifier: String, contents: Map[String, Any]) extends Data
case class Partial(identifier: String, name: String, value: Any) extends Data

... 

def parse(file: File): Iterator[Data] = ... // this isn't relevant

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

Имея только набор Partial элементов (Iterator[Partial]), я могу сделать следующее:

partialsOnly.groupBy(_.identifier)
 .map{ 
   case (ident, parts) => 
     Complete(ident, parts.map(p => p.name -> p.value).toMap)
 }

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

Ответы [ 2 ]

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

Рекурсия может быть функциональным способом решения вашей проблемы:

def parse(list: List[Data]): (List[Data], List[Data]) = {
  list match {
     case (x:Partial) :: xs =>
       val (partials, rest) = parse(xs)
       (x :: partials, rest) //instead of creating list, you can join partials here
     case x :: xs =>
       val (partials, rest) = parse(xs)
       (partials, x :: rest)
     case _ => (Nil, Nil)
   }
}

val (partials, rest) = parse(list)

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

Вы можете решить эту проблему, используя Eval из cats :

def parse2(list: List[Data]): Eval[(List[Data], List[Data])] =
    Eval.now(list).flatMap {
      case (x:Partial) :: xs =>
        parse2(xs).map {
          case (partials, rest) => (x :: partials, rest) //instead of creating list, you can join partials here
        }
      case x :: xs =>
        parse2(xs).map {
          case (partials, rest) => (partials, x :: rest)
        }
      case _ => Eval.now((Nil, Nil))
    }

val (partialsResult, restResult) = parse2(longList).value

Это решение будет безопасным для стека, поскольку оно использует Heap, а не Stack.

А вот версия, которая также группирует партиалы:

def parse3(list: List[Data]): Eval[(Map[String, List[Partial]], List[Data])] =
Eval.now(list).flatMap {
  case (x:Partial) :: xs =>
    parse3(xs).map {
      case (partials, rest) =>
        val newPartials = x :: partials.getOrElse(x.identifier, Nil)
        (partials + (x.identifier -> newPartials), rest)
    }
  case x :: xs =>
    parse3(xs).map {
      case (partials, rest) => (partials, x :: rest)
    }
  case _ => Eval.now((Map.empty[String, List[Partial]], Nil))
}
0 голосов
/ 11 января 2019

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

val (partial: List[Data], completeAndMeta: List[Data]) = parse("file").partition(_ match{
  case partial: Partial => true
  case _ => false
})

Оттуда вы хотите убедиться, что можете обработать partial как List[Partial], в идеале без отключения предупреждений компилятора о стирании типов или беспорядочных приведений. Вы можете сделать это с помощью вызова collect, используя функцию, которая принимает только Partial.

val partials: List[Partial] = partial.collect(_.match{case partial: Partial => partial}}

К сожалению, при использовании Iterator, partition может потребоваться буферизовать произвольные объемы данных, поэтому это не обязательно самый эффективный метод памяти. Если управление памятью является огромной проблемой, вам, возможно, придется пожертвовать функциональной чистотой. В качестве альтернативы, если вы добавите какой-либо способ узнать, когда Partial завершен, вы можете накапливать их в Map через foldLeft и выдавать окончательное значение по окончании.

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