2015-08-05 2 views
0

Я использую scala для написания искрового приложения, которое считывает данные из файлов csv с использованием dataframes (ни одна из этих деталей не имеет значения, на мой вопрос может ответить любой, кто хорошо разбирается в функциональном программировании)Скала/функциональный способ делать вещи

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

Я в принципе хочу читать колонки (а, б) из файла CSV и отслеживать те строки, где б < 0.

Я реализовал это, но его довольно много, как я хотел бы сделать это Java, и я хотел бы использовать возможности Scala вместо:

val ValueDF = fileDataFrame.select("colA", "colB")           
val ValueArr = ValueDF.collect() 

     for (index <- 0 until (ValueArr.length)){ 
      var row = ValueArr(index) 
      var A = row(0).toString() 
      var B = row(1).toString().toDouble 

      if (B < 0){ 
       //write A and B somewhere 
      } 
     } 

Преобразование dataframe в массив поражения цели распределенных вычислений. Итак, как я мог получить те же результаты, но вместо формирования массива и прохождения через него я предпочел бы выполнить некоторые преобразования самого кадра данных (например, map/filter/flatmap и т. Д.).

Я должен скоро собираться, просто нужно несколько примеров, чтобы обвести вокруг себя голову.

+0

Если ValueArr является набором, он должен иметь функцию 'filter()', которая принимает функцию '(a, b) => Boolean' в качестве параметра и возвращает коллекцию только тех элементов, где возвращаемая функция правда. – thwiegan

ответ

4

Вы выполняете операцию фильтрации (игнорируете, если не (B < 0)) и отображаете (из каждой строки, получаете A и B/делаем что-то с A и B).

Вы могли бы написать это следующим образом:

val valueDF = fileDataFrame.select("colA", "colB")           
val valueArr = valueDF.collect() 

val result = valueArr.filter(_(1).toString().toDouble < 0).map{row => (row(0).toString(), row(1).toString().toDouble)} 

// do something with result 

Вы также можете сделать первое отображение, а затем фильтрация:

val result = valueArr.map{row => (row(0).toString(), row(1).toString().toDouble)}.filter(_._2 < 0) 

Scala также предлагает более удобные варианты для такого рода операций (спасибо Саша Кольберг), названный withFilter и collect. withFilter имеет преимущество перед filter в том, что он не создает новую коллекцию, экономя вас за один проход, см. this answer для получения более подробной информации. С collect вы также сопоставляете и фильтруете за один проход, передавая частичную функцию, которая позволяет выполнять сопоставление образцов, см., Например, это answer.

В вашем случае collect будет выглядеть следующим образом:

val valueDF = fileDataFrame.select("colA", "colB")           
val valueArr = valueDF.collect() 

val result = valueArr.collect{ 
    case row if row(1).toString().toDouble < 0) => (row(0).toString(), row(1).toString().toDouble) 
} 
// do something with result 

(я думаю, что есть более элегантный способ выразить это, но что осталось в качестве упражнения;))

Кроме того, есть легкая нотация называемых «последовательными пониманиями». С этим можно было бы написать:

val result = for (row <- valueArr if row(1).toString().toDouble < 0) yield (row(0).toString(), row(1).toString().toDouble) 

Или более гибкий вариант:

val result = for (row <- valueArr) yield { 
    val double = row(1).toString().toDouble 
    if (double < 0) { 
    (row(0).toString(), double) 
    } 
} 

В качестве альтернативы, вы можете использовать foldLeft:

val valueDF = fileDataFrame.select("colA", "colB")           
val valueArr = valueDF.collect() 

val result = valueArr.foldLeft(Seq[(String, Double)]()) {(s, row) => 
    val a = row(0).toString() 
    val b = row(1).toString().toDouble 

    if (b < 0){ 
    s :+ (a, b) // append tuple with A and B to results sequence 
    } else { 
    s // let results sequence unmodified 
    } 
} 

// do something with result 

Все они считаются функционально ... которые вы предпочитаете, по большей части, вопрос вкуса. Первые 2 примера (фильтр/карта, карта/фильтр) имеют недостаток производительности по сравнению с остальными, поскольку они повторяют последовательность в два раза.

Обратите внимание, что в FP очень важно минимизировать побочные эффекты/изолировать их от основной логики. I/O («написать A и B где-то») является побочным эффектом. Таким образом, вы обычно будете писать свои функции таким образом, чтобы у них не было побочных эффектов - только логика ввода -> вывода, не затрагивая или не извлекая данные из окружения. Как только у вас есть конечный результат, вы можете делать побочные эффекты. В этом конкретном случае, когда у вас есть result (это последовательность из A и B кортежей), вы можете пропустить ее и распечатать. Таким образом, вы можете, например, легко изменить способ печати (вы можете распечатать на консоль, отправить в удаленное место и т. Д.), Не касаясь основной логики.

Также вы можете предпочесть неизменные значения (val), где это возможно, что безопаснее. Даже в вашей петле, row, A и B не изменяются, поэтому нет причин использовать var.

(Btw, я скорректировал имена значений, начиная с нижнего регистра, см. conventions).

+3

Просто крошечная вещь о 'filter(). Map()': всякий раз, когда кто-то использует 'filter()', за которым следует 'map()', почти всегда лучше использовать 'withFilter(). Map()' или ' собирать {case => ???} ', что в основном означает ваше первое * for-comprehension * (' val result = for (row <- valueArr, если строка (1) .toString(). toDouble <0) yield (строка (0) .toString(), строка (1) .toString(). toDouble) '). –

+0

@SaschaKolberg Включил его в ответ, спасибо! – Ixx

+1

@ lxx Большое спасибо за подробный ответ! Именно то, что мне нужно. – user3376961