0

I have an original C# LINQ Query:

var rez = planes
    .Where(b => (b.FlDate >= dateFrom) && (b.FlDate <= dateTo))
    .GroupBy(i => i.Destination)
    .Select(g => new { Destination = g.Key, Count = g.Count() })
    .ToList();

and re-written Scala-code with issue:

var rez = planes
  .filter( _.FlDate.getMillis >= dateFrom.getMillis)
  .filter(_.FlDate.getMillis <= dateTo.getMillis)
  .groupBy(_.Destination)
  .flatMap( new { Destination:String = _.Key, Count = _.Count() })//issue is here

So main task is to create anonymous objects array with Destination and Count properties


Scala PlanesLogRow class source:

import com.github.nscala_time.time.Imports._

class PlanesLogRow {
  var FlDate:DateTime = new DateTime
  var Origin = ""
  var Destination = ""
}
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
  • Please post a [MCVE] of your problem. Copying and pasting this locally won't compile. Also, be explicit about the problem. "issue is here" isn't enough, *What* is the compilation error? (although it's pretty trivial, you're using C# syntax in Scala). – Yuval Itzchakov Apr 03 '17 at 11:45
  • @YuvalItzchakov That's what I'm trying to find -- correct syntax for creation anonymous objects in .flatMap() – Andrew_STOP_RU_WAR_IN_UA Apr 03 '17 at 11:51

2 Answers2

4

There are no C# comaprable anonmyous classes in Scala (although they do exist, they are far less useful IMO). Alternatively, you can use a Tuple2[String, Int] ((String, Int)) which has nice syntax sugar.

For convience, we can create a case class of the PlanesLogRow, which gives us a nice compact way for creating a class (note all values are immutable):

case class PlanesLogRow(flightDate: ZonedDateTime, origin: String, destination: String)

Now let's create a sequence of them:

val flights = Seq(
  PlanesLogRow(ZonedDateTime.now(), "US", "Kiev"),
  PlanesLogRow(ZonedDateTime.now().plusHours(2L), "US", "Prague"),
  PlanesLogRow(ZonedDateTime.now().plusHours(4L), "Canada", "Prague")
)

From and to dates (I used java.time for convenience):

val dateFrom = ZonedDateTime.now
val dateTo = ZonedDateTime.now().plusHours(5L)

Now, we can filter once (no need for two passes over the collection), group, and then output the result to a Map[String, Int]:

val result: Map[String, Int] =
  flights
    .filter(logRow => logRow.flightDate.isAfter(dateFrom) && logRow.flightDate.isBefore(dateTo))
    .groupBy(_.destination)
    .map { case (location, logRows) => (location, logRows.length) }

Yields:

Map(Prague -> 2)

In Scala, unlike LINQ in C#, combinators over collections are strict, not lazy. This means that for every combinator you invoke (map, filter etc) there will be an allocation of a new collection. To get around this, you can use views which produce lazy collections:

val result =
  flights
    .view
    .filter(logRow => logRow.flightDate.isAfter(dateFrom) && logRow.flightDate.isBefore(dateTo))
    .groupBy(_.destination)
    .map { case (location, logRows) => (location, logRows.length) }

Other then that, the naming convention for scala fields are camelCase, not PascalCase like C#.

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • I have a problem with this code... flights count is 0 in my case. But must be 2. in case of remove ".map{...}" string flights count is correct. So looks like there must be some mistake, but cannot find where – Andrew_STOP_RU_WAR_IN_UA Apr 04 '17 at 12:38
  • @Andrew I'm not even sure what that comment means? – Yuval Itzchakov Apr 04 '17 at 12:40
  • http://prntscr.com/esb6vh so looks like there is some mistake in ".map{...}" code. – Andrew_STOP_RU_WAR_IN_UA Apr 04 '17 at 12:48
  • @Andrew I just copy pasted this code, ran it and I got the expected result. Not sure what you're doing there. http://prnt.sc/esba89 – Yuval Itzchakov Apr 04 '17 at 12:53
  • Was my mistake. Sorry. Thanks a lot :) But, can you explain one more thing: Where did you get from "location" ? Why it's know what location is? – Andrew_STOP_RU_WAR_IN_UA Apr 04 '17 at 12:58
  • 1
    @Andrew It's called a partial function, and it allows us to write the `case (..)` syntax. What it does is decompose the tuple into values, `location` and `logRows` respectively (of course there is more to it, just not that important as for your question). If you look at the object inside the `map` function, you'll see its a `Tuple2[String, Seq[PlanesLogRow]]`. – Yuval Itzchakov Apr 04 '17 at 13:05
1

As an addition to @Yuval Itzchakov's excellent answer, if you really want, for some reason, to have AnyRef{val destination: String; val count: Int} instead of Tuple2[String, Int], you could replace its last map with:

.map {
  case (location, logRows) => new {
    val destination = location;
    val count = logRows.length
  }
}

Note: As @pedrofurla correctly pointed out, this solution force the runtime to use reflection.

Federico Pellegatta
  • 3,977
  • 1
  • 17
  • 29