2011-01-21 5 views
-2

Учитывая список таких как следующее:Сложные многомерные список операций в Scala

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
+5

Разве у этого нет метки «домашняя работа»? Лучшее решение - не использовать точные операции в указанном вами порядке; единственная причина использовать их таким образом, чтобы это было как домашнее задание. –

+2

«используйте groupBy, ломтик, собирайте, суммируйте в этом порядке» ... Кажется немного ограничительным; Итак, кто сейчас делает домашнюю работу в Скале? –

+0

@Rex избили меня до наблюдения примерно на 1 секунду :) –

ответ

11

Я чувствую, наоборот, так вот ответ, который не использует ни один из установленных способов: 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 {_+_} 
+0

Пожалуйста, проверьте ваше представление еще раз. Это неверно. :) – Debilski

+1

Он работал в REPL, если я не пропустил что-то в копии/пасте –

+0

@Debilski Nope, я этого не сделал, кажется, работает отлично для меня. Какая ошибка вы наблюдали, точно? –

4

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

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

+0

+1 за то, что он разумный, кто-то должен быть ... –

+0

И еще один +1 для этого. –

5

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

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 
+0

Теперь, когда это действительно красиво ... Это, безусловно, получает * мой * upvote! –

+1

Ха-ха, ну, я бы проголосовал за «-1, глупо», если он был включен /. (и если у них была глупая метка). –

+0

Почему 'dropWhile' /' takeWhile' вместо просто 'filter'? –

3
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 заканчивается как карта с номера месяца до общей суммы.

+0

Вы попадаете опасно близко к действительному ответу там :) –

+0

Я извиняюсь за ваше прощение: -P –

+0

Считаю, что это удовлетворено :) –

2

Я отказываюсь запутывать 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

+1 для использования jodatime, безусловно, самый лучший способ справиться с датами. –

8
(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)) 
    } 

} 
+0

Я думаю, что это удобно. – ziggystar

+0

Да, это то, что заканчивается в большинстве моих проектов, но просто по-другому, что он не сделает хорошую библиотеку. –

+0

Вы потратили слишком много времени на этот «YearMonthDay». Попробуйте 'val YearMonthDay =" "" (\ d +) - (\ d +) - (\ d +) "" "r" и используйте '' 01 "' вместо '1' на карте. –

3

Так вот идея:

  • groupBy, потому что вы должны группировать данные от каждого месяца вместе
  • slice, потому что вы должны видеть, что месяц из дата
  • collect, поскольку необходимо filter по месяцам и map для оценки
  • sum, мммм ... Я не уверен, где этот человек входит.Есть идеи?
0
dane.groupBy (_._1.matches (".*-01-.*")).slice (0, 1).map (x => x._2).flatten .map (y => y._2).sum 

Я действительно должен смотреть вверх «собирать», который так или иначе должен заменить мою карту/выравниваться/карту.

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