Jak podzielić sekwencję na dwie części za pomocą predykatu?

120

Jak podzielić sekwencję na dwie listy według predykatu?

Alternatywa: mogę użyć filteri filterNotlub napisać własną metodę, ale czy nie ma lepszej, bardziej ogólnej (wbudowanej) metody?

John Threepwood
źródło

Odpowiedzi:

194

Przy użyciu partitionmetody:

scala> List(1,2,3,4).partition(x => x % 2 == 0)
res0: (List[Int], List[Int]) = (List(2, 4),List(1, 3))
mniam mniam mniam
źródło
1
val (even, odd) = List(1,2,3,4).partition(x => x % 2 == 0)jest sposobem na zniszczenie powstałej krotki partitionw czytelny sposób.
k0pernikus
2
Funkcję wewnątrz partycji można skrócić do _ % 2 == 0.
k0pernikus
138

Dobrze, że partitionbyło rzeczą, którą chciał - nie ma innej metody, które również wykorzystuje predykatu podzielić listę na dwie części: span.

Pierwsza z nich, partycja , umieści wszystkie „prawdziwe” elementy na jednej liście, a pozostałe na drugiej liście.

span umieści wszystkie elementy na jednej liście, dopóki element nie będzie „fałszywy” (w sensie predykatu). Od tego momentu elementy zostaną umieszczone na drugiej liście.

scala> Seq(1,2,3,4).span(x => x % 2 == 0)
res0: (Seq[Int], Seq[Int]) = (List(),List(1, 2, 3, 4))
Daniel C. Sobral
źródło
2
Dokładnie to, czego szukałem. Kiedy lista jest uporządkowana według powiązanego kryterium, ma to dużo większy sens.
erich2k8
16

Możesz rzucić okiem na scalex.org - umożliwia ona przeszukiwanie standardowej biblioteki scala w poszukiwaniu funkcji według ich podpisu. Na przykład wpisz:

List[A] => (A => Boolean) => (List[A], List[A])

Zobaczysz partycję .

oxbow_lakes
źródło
10
Domena scalex.org jest obecnie martwa. Ale jest alternatywa - scala-search.org ;-).
poniedziałek
1
Nauka łapania ryb!
TEN UŻYTKOWNIK POTRZEBUJE POMOCY
1
@monnef Jakaś alternatywa dla Twojej alternatywy na rok 2020? :)
tehCivilian
14

Możesz także użyć foldLeft, jeśli potrzebujesz czegoś dodatkowego. Właśnie napisałem taki kod, gdy partycja go nie przecięła:

val list:List[Person] = /* get your list */
val (students,teachers) = 
  list.foldLeft(List.empty[Student],List.empty[Teacher]) {
    case ((acc1, acc2), p) => p match {
      case s:Student => (s :: acc1, acc2)
      case t:Teacher  => (acc1, t :: acc2)
    }
  }
nairbv
źródło
1
Bardzo fajny sposób na użycie krotki i foldLeft. Skończyło się na tym, że użyłem ListBuffer, aby skutecznie utrzymać dwie listy w tej samej kolejności, ale poza tym było to na miejscu, czego potrzebowałem.
Matt Hagopian
1

Wiem, że mogę się spóźnić na przyjęcie i są bardziej szczegółowe odpowiedzi, ale możesz zrobić dobry użytek z tego groupBy

val ret = List(1,2,3,4).groupBy(x => x % 2 == 0)

ret: scala.collection.immutable.Map[Boolean,List[Int]] = Map(false -> List(1, 3), true -> List(2, 4))

ret(true)
res3: List[Int] = List(2, 4)

ret(false)
res4: List[Int] = List(1, 3)

To sprawia, że ​​twój kod jest nieco bardziej przyszłościowy, jeśli chcesz zmienić warunek na coś innego niż logiczne.

Gabber
źródło
0

Jeśli chcesz podzielić listę na więcej niż 2 części i zignorować granice, możesz użyć czegoś takiego (zmodyfikuj, jeśli chcesz szukać int)

def split(list_in: List[String], search: String): List[List[String]] = {
  def split_helper(accum: List[List[String]], list_in2: List[String], search: String): List[List[String]] = {
    val (h1, h2) = list_in2.span({x: String => x!= search})
    val new_accum = accum :+ h1
    if (h2.contains(search)) {
      return split_helper(new_accum, h2.drop(1), search) 
    }
    else {
    return accum
    }
  }
  return split_helper(List(), list_in, search)
}

// TEST

// split(List("a", "b", "c", "d", "c", "a"), {x: String => x != "x"})
Matt
źródło