Сложные операции многомерного списка в Scala - PullRequest
0 голосов
/ 21 января 2011

Учитывая список, такой как следующий:

val dane = List(
    ("2011-01-04", -137.76),
    ("2011-01-04", 2376.45),
    ("2011-01-04", -1.70),
    ("2011-01-04", -1.70),
    ("2011-01-04", -1.00),
    // ... skip a few ...
    ("2011-12-22", -178.02),
    ("2011-12-29", 1800.82),
    ("2011-12-23", -83.97),
    ("2011-12-24", -200.00),
    ("2011-12-24", -30.55),
    ("2011-12-30", 728.00)
)

Я хотел бы суммировать значения (т.е. второй элемент внутренних списков) определенного месяца (например, январь или 01), используя следующие операции в указанном порядке:

  1. groupBy
  2. slice
  3. collect
  4. sum

Ответы [ 8 ]

11 голосов
/ 21 января 2011

Я чувствую обратное, так что вот ответ, который использует НИ ОДИН из предписанных методов: groupBy, slice, collect или sum

Избегать collect было самой трудной частью, condOpt / flatten просто намного страшнее ...

val YMD = """(\d\d\d\d)-(\d\d)-(\d\d)""".r

import PartialFunction._

(dane map {
  condOpt(_:(String,Double)){ case (YMD(_,"01",_), v) => v }  
}).flatten reduceLeft {_+_}
8 голосов
/ 21 января 2011
(for((YearMonthDay(_, 1, _), value)<-dane) yield value).sum

object YearMonthDay{
   def unapply(dateString:String):Option((Int, Int, Int)) ={ 
       //yes, there should really be some error checking in this extractor 
       //to return None for a bad date string
       val components = dateString.split("-")
       Some((components(0).toInt, components(1).toInt, components(2).toInt)) 
  }  

}
5 голосов
/ 21 января 2011

Теперь, когда Кевин начал тенденцию противоположных ответов, вот тот, который вы никогда не должны использовать, но, черт возьми, это работает! (И избегает каждого запрошенного метода, и будет работать в любой месяц, если вы измените строку, но он требует, чтобы список был отсортирован по дате.)

dane.scanLeft(("2011-01",0.0))((l,r) =>
  ( l._1,
    if ((l._1 zip r._1).forall(x => x._1==x._2)) l._2+r._2 else 0.0
  )
).dropWhile(_._2==0).takeWhile(_._2 != 0.0).reverse.head._2
4 голосов
/ 21 января 2011

Разбейте проблему на более мелкие этапы. Начните с попытки разбить список на один список за каждый месяц. Вы можете использовать groupBy для этого. Ваша первая проблема, вероятно, будет в том, как разобрать строку даты. Общее решение было бы использовать пользовательский класс даты и регулярное выражение; однако более простое специальное решение использования индексированной подстроки (или slice) может быть уместным в этом контексте.

Общий совет - загрузить данные в Scala REPL и поиграть с ними. Удачи.

3 голосов
/ 22 января 2011

Итак, вот идея:

  • groupBy, потому что вам нужно сгруппировать данные за каждый месяц вместе
  • slice, потому что вам нужно увидеть, какоймесяц даты
  • collect, потому что вам нужно filter по месяцам и map для значения
  • sum, мммм ... Я не уверен, где этоприходит. Есть идеи?
3 голосов
/ 21 января 2011
import scala.collection.mutable.HashMap
val totals = new HashMap[Int, Double]
for (e <- dane) {
    val (date, value) = e
    val month = date.drop(5).take(2).toInt
    totals(month) = totals.getOrElse(month,0.0) + value
}

Другая реализация, не использующая ни одну из предложенных функций, изменяемые коллекции и некоторое ублюдочное сочетание процедурного и функционального стиля, избегая некоторых полезных функций:)

totals заканчивается как карта от номера месяца до итога.

2 голосов
/ 21 января 2011

я отказываюсь запутывать sum.

import org.joda.time.DateMidnight
for (month <- 1 to 12) yield {
  dane map { case (d,v) => new DateMidnight(d).getMonthOfYear -> v }
  filter { case (m, v) => m == month }
  map (_._2)
  sum
}
0 голосов
/ 02 июня 2011
dane.groupBy (_._1.matches (".*-01-.*")).slice (0, 1).map (x => x._2).flatten .map (y => y._2).sum

Мне действительно нужно поискать «собирать», что каким-то образом должно заменить мою карту / сглаживать / карту.

Мой результат: Double = 2234.29

...