1

There are a few libraries such as Spark and other Scala extensions that have the "groupWith" function available. This function allows you to compare an element to the rest of the collection and then group it using one or more predicates. There doesn't seem to be any native functionality in Scala for this but they do have the sortWith function that behaves similarly but only sorts the items instead of grouping them. If the explanation isn't sufficient here's a small code sample that should display what I'm trying to do:

val list = List(1,2,3,4,5,5)
val groupedList = list.groupWith{ (e,c) =>
    e == c
}

This is a very simple example and I want to do more complicated comparisons such as

e + 1 == c

So again the question is are there any native Scala functions that do this? Any suggestions or workarounds?

Update: From the simple examples given it seems it's not exactly clear what I'm trying to do, here's a better example: Say I have a case class and a list of these objects:

case class Item(num: Int, color: String)
val list = List(new Item(13, "red"), new Item(14,"red"), new Item(15, "blue"), new Item(16, "red"))

list.groupWith{ (e,c) =>
    (e.num -1 == c.num || e.num + 1 == c.num ) && e.color == c.color        
}

And this should return something like this:

res8: List[List[Item]] = List(List(Item(13,red), Item(14,red)), List(Item(15,blue)), List(Item(16,red)))

2 Answers2

2

Here's an implementation:

// Takes the list as a parameter, can use pimp-my-library if you want
def groupWith[A](xs: List[A], f: (A, A) => Boolean) = {
  // helper function to add "e" to any list with a member that matches the predicate
  // otherwise add it to a list of its own
  def addtoGroup(gs: List[List[A]], e: A): List[List[A]] = {
    val (before, after) = gs.span(_.exists(!f(_, e)))
    if (after.isEmpty)
      List(e) :: gs
    else
      before ::: (e :: after.head) :: after.tail
  }
  // now a simple foldLeft adding each element to the appropriate list
  xs.foldLeft(Nil: List[List[A]])(addtoGroup)
} 

groupWith(list, { (e: Item, c: Item) =>
                    (e.num - 1 == c.num || e.num + 1 == c.num) && e.color == c.color})

//| res0: List[List[groups.groups.Item]] =
//         List(List(Item(16,red)),
//              List(Item(15 ,blue)), 
//              List(Item(14,red), Item(13,red)))
The Archetypal Paul
  • 41,321
  • 20
  • 104
  • 134
  • This approach definitely fits the description of what the function needs to do but I was looking for something that is native to Scala(i.e. something that I don't need to write myself). – goodOldFashioned Mar 14 '16 at 21:26
  • 1
    Well, you don't need to write it yourself. I wrote it for you :) As far as I know, there's nothing in the standard library that does exactly what you want. – The Archetypal Paul Mar 14 '16 at 22:31
  • True, I guess that in itself answers my question and this is as good an answer as I'm going to find, thank you! – goodOldFashioned Mar 16 '16 at 19:03
1

Not sure if this what you want (check my comments to your question), but there is method groupBy defined in GenTraversableLike which List inherits (not only List). You will get:

scala> val list = List(1,2,3,4,5,5)
list: List[Int] = List(1, 2, 3, 4, 5, 5)

scala> list.groupBy( el => el )
res0: scala.collection.immutable.Map[Int,List[Int]] = Map(5 -> List(5, 5), 1 -> List(1), 2 -> List(2), 3 -> List(3), 4 -> List(4))

scala> list.groupBy( el => el + 1 )
res1: scala.collection.immutable.Map[Int,List[Int]] = Map(5 -> List(4), 6 -> List(5, 5), 2 -> List(1), 3 -> List(2), 4 -> List(3))

Basically you need to provide discriminator function from value to key and you will get Map[Key, List[Value].

Is this what you want?

Teliatko
  • 1,521
  • 1
  • 14
  • 15
  • Not exactly, I do know of the groupBy function but it only allows you to compare to the element itself so grouping operations based on, say, number proximity are impossible with this (I think). See my update for a clearer picture of what I'm looking for. – goodOldFashioned Mar 10 '16 at 13:17