Подтвердить что ты не робот

Самый простой способ решить, содержит ли список дубликатов?

Один из способов - это

list.distinct.size != list.size

Есть ли лучший способ? Было бы неплохо иметь метод containsDuplicates

4b9b3361

Ответ 1

Предполагая, что "лучше" означает "быстрее", см. альтернативные подходы, ориентированные на этот вопрос, который, кажется, показывает некоторые более быстрые методы (хотя обратите внимание, что в отдельности используется HashSet и уже O (n)). YMMV, конечно, в зависимости от конкретного тестового примера, версии scala и т.д. Возможно, какое-либо значительное улучшение по сравнению с подходом "distinct.size" будет происходить с самого начала, как только будет найден дубликат, но сколько из скорости, на самом деле, будет сильно зависеть от того, насколько типичны общие дубликаты в вашем случае использования.

Если вы имеете в виду "лучше" в том, что вы хотите написать list.containsDuplicates вместо containsDuplicates(list), используйте неявное:

implicit def enhanceWithContainsDuplicates[T](s:List[T]) = new {
  def containsDuplicates = (s.distinct.size != s.size)
}

assert(List(1,2,2,3).containsDuplicates)
assert(!List("a","b","c").containsDuplicates)

Ответ 2

Вы также можете написать:

list.toSet.size != list.size

Но результат будет тем же, потому что distinct уже реализован с Set. В обоих случаях временная сложность должна быть O (n): вы должны пересечь список, а Set - O (1).

Ответ 3

Я думаю, что это остановится, как только будет найден дубликат, и, вероятно, более эффективен, чем делать distinct.size - так как я предполагаю, что distinct также сохраняет набор:

@annotation.tailrec
def containsDups[A](list: List[A], seen: Set[A] = Set[A]()): Boolean = 
  list match {
    case x :: xs => if (seen.contains(x)) true else containsDups(xs, seen + x)
    case _ => false
}

containsDups(List(1,1,2,3))
// Boolean = true

containsDups(List(1,2,3))
// Boolean = false

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

def containsDups[A](list: List[A]): Boolean =  {
  list.iterator.scanLeft(Set[A]())((set, a) => set + a) // incremental sets
    .zip(list.iterator)
    .exists{ case (set, a) => set contains a }
}

Ответ 4

@annotation.tailrec 
def containsDuplicates [T] (s: Seq[T]) : Boolean = 
  if (s.size < 2) false else 
    s.tail.contains (s.head) || containsDuplicates (s.tail)

Я не измерял это и думал, что он похож на решение huynhjl, но немного проще понять.

Он возвращается раньше, если найден дубликат, поэтому я заглянул в источник Seq.contains, вернется ли он раньше - он делает.

В SeqLike 'contains (e)' определяется как 'exists (_ == e)', и существует в TraversableLike:

def exists (p: A => Boolean): Boolean = {
  var result = false
  breakable {
    for (x <- this)
      if (p (x)) { result = true; break }
  }
  result
}

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

Ответ 5

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

Примечание. Этот ответ является прямой копией ответа по соответствующему вопросу.

Детали: Если вам нужно немного больше информации о самих дубликатах, как и я, я написал более общую функцию, которая повторяется через List (как упорядочение была значительна) ровно один раз и возвращает Tuple2, состоящую из оригинала List debuped (все дубликаты после первого удаляются, то есть то же самое, что и вызов distinct), и второй List, показывающий каждый дубликат и индекс Int, в котором он произошел в исходном List.

Здесь функция:

def filterDupes[A](items: List[A]): (List[A], List[(A, Int)]) = {
  def recursive(remaining: List[A], index: Int, accumulator: (List[A], List[(A, Int)])): (List[A], List[(A, Int)]) =
    if (remaining.isEmpty)
      accumulator
    else
      recursive(
          remaining.tail
        , index + 1
        , if (accumulator._1.contains(remaining.head))
            (accumulator._1, (remaining.head, index) :: accumulator._2)
          else
            (remaining.head :: accumulator._1, accumulator._2)
      )
  val (distinct, dupes) = recursive(items, 0, (Nil, Nil))
  (distinct.reverse, dupes.reverse)
}

Ниже приведен пример, который может сделать его более интуитивным. Учитывая этот список значений String:

val withDupes =
  List("a.b", "a.c", "b.a", "b.b", "a.c", "c.a", "a.c", "d.b", "a.b")

... и затем выполните следующее:

val (deduped, dupeAndIndexs) =
  filterDupes(withDupes)

... результаты:

deduped: List[String] = List(a.b, a.c, b.a, b.b, c.a, d.b)
dupeAndIndexs: List[(String, Int)] = List((a.c,4), (a.c,6), (a.b,8))

И если вы просто хотите дублировать, просто map через dupeAndIndexes и вызовите distinct:

val dupesOnly =
  dupeAndIndexs.map(_._1).distinct

... или все за один вызов:

val dupesOnly =
  filterDupes(withDupes)._2.map(_._1).distinct

... или если a Set является предпочтительным, пропустите distinct и вызовите toSet...

val dupesOnly2 =
  dupeAndIndexs.map(_._1).toSet

... или все за один вызов:

val dupesOnly2 =
  filterDupes(withDupes)._2.map(_._1).toSet

Это прямая копия функции filterDupes из моей библиотеки с открытым исходным кодом Scala, ScalaOlio. Он расположен в org.scalaolio.collection.immutable.List_._.