Это продолжение: Развернуть набор наборов строк в декартово произведение в Scala
Идею вы хотите взять:
val sets = Set(Set("a","b","c"), Set("1","2"), Set("S","T"))
и вернитесь:
Set("a&1&S", "a&1&T", "a&2&S", ..., "c&2&T")
Общее решение:
def combine[A](f:(A, A) => A)(xs:Iterable[Iterable[A]]) =
xs.reduceLeft { (x, y) => x.view.flatMap {a => y.map(f(a, _)) } }
используется следующим образом:
val expanded = combine{(x:String, y:String) => x + "&" + y}(sets).toSet
Теоретически, должен быть способ ввода ввода типа Set[Set[A]]
и возврата a Set[B]
. То есть, чтобы преобразовать тип при объединении элементов.
Пример использования будет заключаться в наборе строк (как указано выше) и выводе длин их конкатенации. Функция f
в combine
будет иметь вид:
(a:Int, b:String) => a + b.length
Я не смог придумать реализацию. Кто-нибудь имеет ответ?
Ответ 1
Если вы действительно хотите, чтобы функция объединителя выполняла сопоставление, вы можете использовать fold
, но как указал Крейг, вам нужно будет предоставить начальное значение
def combine[A, B](f: B => A => B, zero: B)(xs: Iterable[Iterable[A]]) =
xs.foldLeft(Iterable(zero)) {
(x, y) => x.view flatMap { y map f(_) }
}
Тот факт, что вам нужно такое начальное значение, следует из типа функции объединителя/картографа (B, A) => B
(или, как функция с карри, B => A => B
). Ясно, что для сопоставления первого A
, с которым вы сталкиваетесь, вам нужно будет предоставить B
.
Вы можете сделать это несколько проще для абонентов, используя класс Zero
type:
trait Zero[T] {
def zero: T
}
object Zero {
implicit object IntHasZero extends Zero[Int] {
val zero = 0
}
// ... etc ...
}
Затем метод combine
можно определить как:
def combine[A, B : Zero](f: B => A => B)(xs: Iterable[Iterable[A]]) =
xs.foldLeft(Iterable(implicitly[Zero[B]].zero)) {
(x, y) => x.view flatMap { y map f(_) }
}
Использование:
combine((b: Int) => (a: String) => b + a.length)(sets)
Scalaz предоставляет класс типа Zero
, а также множество других полезных свойств для функционального программирования.
Ответ 2
Проблема, с которой вы сталкиваетесь, заключается в том, что сокращение (Left | Right) принимает функцию (A, A) = > A, которая не позволяет вам изменять тип. Вы хотите что-то более похожее на foldLeft, которое принимает (B, A) ⇒ B, что позволяет вам накапливать выходные данные другого типа. folds нужно, однако, значение семени, которое здесь не может быть пустой. Вам нужно будет отделить xs друг от друга на голову и хвост, сопоставить головку с истребимой, чтобы быть итерируемой [B], а затем вызвать foldLeft с отображенной головой, хвостом и некоторой функцией (B, A) = > B. Это кажется, больше неприятностей, чем того стоит, поэтому я просто сделал бы все отображение спереди.
def combine[A, B](f: (B, B) => B)(g: (A) => B)(xs:Iterable[Iterable[A]]) =
xs.map(_.map(g)).reduceLeft { (x, y) => x.view.flatMap {a => y.map(f(a, _)) } }
val sets = Set(Set(1, 2, 3), Set(3, 4), Set(5, 6, 7))
val expanded = combine{(x: String, y: String) => x + "&" + y}{(i: Int) => i.toString}(sets).toSet