2012-05-14 4 views
3

Допустим, я хочу, чтобы сгладить вложенные списки одного и того же типа ... НапримерСведение вложенные списки одного и того же типа

ListA(Element(A), Element(B), ListA(Element(C), Element(D)), ListB(Element(E),Element(F))) 

ListA содержит вложенный список того же типа (ListA(Element(C), Element(D))), поэтому я хочу, чтобы заменить она со значениями, которые он содержит, поэтому результат верхнего примера должен выглядеть следующим образом:

ListA(Element(A), Element(B), Element(C), Element(D), ListB(Element(E),Element(F))) 

иерархия Текущий класс:

abstract class SpecialList() extends Exp { 
    val elements: List[Exp] 
} 

case class Element(name: String) extends Exp 

case class ListA(elements: List[Exp]) extends SpecialList { 
     override def toString(): String = "ListA("+elements.mkString(",")+")" 
} 

case class ListB(elements: List[Exp]) extends SpecialList { 
     override def toString(): String = "ListB("+elements.mkString(",")+")" 
} 

object ListA{def apply(elements: Exp*):ListA = ListA(elements.toList)} 
object ListB{def apply(elements: Exp*):ListB = ListB(elements.toList)} 

Я сделал три решения, работает, но я думаю, что должен быть лучший способ для достижения этой цели:

Первое решение:

def flatten[T <: SpecialList](parentList: T): List[Exp] = { 
     val buf = new ListBuffer[Exp] 

     for (feature <- parentList.elements) feature match { 
      case listA:ListA if parentList.isInstanceOf[ListA] => buf ++= listA.elements 
      case listB:ListB if parentList.isInstanceOf[ListB] => buf ++= listB.elements 
      case _ => buf += feature 
     } 
     buf.toList 
    } 

Второе решение:

def flatten[T <: SpecialList](parentList: T): List[Exp] = { 
    val buf = new ListBuffer[Exp] 

    parentList match { 
     case listA:ListA => for (elem <- listA.elements) elem match { 
           case listOfTypeA:ListA => buf ++= listOfTypeA.elements 
           case _ => buf += elem 
          } 

     case listB:ListB => for (elem <- listB.elements) elem match { 
           case listOfTypeB:ListB => buf ++= listOfTypeB.elements 
           case _ => buf += elem 
          } 
    } 

    buf.toList 
} 

Третье решение

def flatten[T <: SpecialList](parentList: T): List[Exp] = parentList.elements flatMap { 
    case listA:ListA if parentList.isInstanceOf[ListA] => listA.elements 
    case listB:ListB if parentList.isInstanceOf[ListB] => listB.elements 
    case other => List(other) 
} 

Мой вопрос в том, есть ли какой-либо лучший, более общий способ достижения такой же функциональности, как и во всех трех верхних решениях, происходит повторение кода?

+1

Это дерево, нет? – ziggystar

+0

В некотором роде это .. Если есть что-то еще, что я должен объяснить, пожалуйста, дайте мне знать ... Третье решение ближе всего, я могу получить, но мне не нравятся два почти одинаковых оператора case (для ListA и ListB) ... Должен быть более общий способ сделать это, возможно, с параметрами типа? – PrimosK

+0

Ответ зависит от того, что вы имеете в виду * лучше *. Третье решение - это краткое функциональное решение. Используя 'scalaz', вы можете представить свои данные как' Tree', а затем просто называть '.flatten'. Вы уверены, что ваше дерево имеет только глубину 2? – ziggystar

ответ

13

Истинный функциональный путь. Без использования переменной.

def flatten[A](list: List[A]): List[A] = list match { 
    case Nil => Nil 
    case (ls: List[A]) :: tail => flatten(ls) ::: flatten(tail) 
    case h :: tail => h :: flatten(tail) 
} 
+2

что-то, с чем я боролся, пытаясь реализовать это сам - я не понимал, что вы можете использовать case (x: Type) :: xs =>, чтобы предоставить тип для первого элемента, я получал ошибку от компилятора (что он ожидал a =>) – jasonk

+0

В вашем третьем случае вы не можете просто добавить элемент с помощью 'h :: flatten (tail)', а не создавать «Список (h)», а затем объединить его с помощью :: ::: '? – DNA

+0

Да, вы можете и, вероятно, более эффективны. – cainj

0

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

def flattenList[A](l: List[Any]): List[A] = { 
    var acc = List[A]() 
    l foreach (entry => entry match { 
    case a: List[Any] => acc = acc ::: flattenList(a) 
    case b: A => acc = acc :+ b 
    }) 
    acc 
} 

Это расплющить вам List(Element(A), Element(B), List(Element(C), Element(D), List(Element(E), Element(F)))) к List(Element(A), Element(B), Element(C), Element(D), Element(E), Element(F))

+0

Обратите внимание, что даже Список [Список [Список [Список [A]]]] будут сплющены к списку [A]. –

0

В идеальном мире, где жалкий тип стиранием не существовало бы, вы могли бы сделать что-то вроде этого:

// WON'T WORK 
def flatten[T <: SpecialList](parentList: T): List[Exp] = parentList.elements flatMap { 
    case x:T => x.elements 
    case somethingElse => List(somethingElse) 
} 

Но самое лучшее решение при сложившихся обстоятельствах, на мой взгляд, это один :

def flatten[T <: SpecialList](parentList: T): List[Exp] = parentList.elements flatMap { 
    case x:SpecialList if x.getClass == parentList.getClass => x.elements 
    case somethingElse => List(somethingElse) 
} 

это немного более общий характер, чем тот, предлагаемый в этом вопросе, так как вам не нужно беспокоиться ли аргумент является lišta или LISTB и он также будет работать, если в будущем йо Вы добавите ListC.

Однако это не решит вашу более общую проблему для сплющивания на произвольной глубине, так как flatten (ListA (...)) также должен возвращать ListA (...) в конце - в случае выше него возвращает список, который теряет его первоначальное значение. Решение этой проблемы может быть:

abstract class SpecialList { 
    val elements: List[Exp] 

    def flatten: SpecialList = createAnother(elements flatMap { 
     case x: SpecialList => { 
      val flattenX = x.flatten 
      if (flattenX.getClass == this.getClass) flattenX.elements else List(flattenX) 
     } 
     case somethingElse => List(somethingElse) 
    }) 

    // Creates another special list of the same type 
    def createAnother(elements: List[Exp]): SpecialList 

} 


case class ListA(elements: List[Exp]) extends SpecialList { 
    override def toString: String = "ListA("+elements.mkString(",")+")" 

    def createAnother(elements: List[Exp]) = ListA(elements) 
} 

case class ListB(elements: List[Exp]) extends SpecialList { 
    override def toString: String = "ListB("+elements.mkString(",")+")" 

    def createAnother(elements: List[Exp]) = ListB(elements) 
} 

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

Третье предложение, которое может включать в себя реорганизацию вашего кода немного больше, заключается в том, чтобы вообще отказаться от типов ListA и ListB, поскольку мне кажется, что их цель - предоставить тег в список Exp. Так считают это решение:

case class SpecialList(tag: Tag, elements: List[Exp]) extends Exp { 
    def flatten: SpecialList = { 
     val newElems = elements flatMap { 
      case x: SpecialList => { 
       val flattenX = x.flatten 
       if (flattenX.tag == this.tag) flattenX.elements else List(flattenX) 
      } 
      case somethingElse => List(somethingElse) 
     } 
     SpecialList(tag, newElems) 
    } 

    override def toString = tag.toString ++ "(" + elements.mkString(",") + ")" 

} 


sealed abstract class Tag { 

    // Syntactic sugar to maintain the notation used in the question 
    def apply(elements: Exp*): SpecialList = SpecialList(this, elements.toList) 

} 

object ListA extends Tag { override val toString = "ListA" } 
object ListB extends Tag { override val toString = "ListB" } 

От синтаксической точки зрения, это в значительной степени то же самое, так как у вас есть

val x = ListA(Element(A), Element(B), ListA(Element(C), Element(D)), ListB(Element(E),Element(F), ListA(Element(C), ListA(Element(D))))) 
x.flatten => ListA(Element(A),Element(B),Element(C),Element(D),ListB(Element(E),Element(F),ListA(Element(C),Element(D)))) 

Это не может соответствовать вашей проблемы, тем не менее, очень жаль, если я ушел рельсы немного там.

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