порядок конкатенации строк в функции свертки

#haskell

#haskell

Вопрос:

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

с foldr

 food = ["Pizza", "Apple", "Banana"]
let f = (a b -> take 3 a    b)
foldr f "" food
-- result
"PizAppBan"
  

в то время как с foldl

 food = ["Pizza", "Apple", "Banana"]
    let f = (a b -> take 3 a    b)
    foldr f "" food
    -- result
    "BanAppPiz"
  

Итак, я понял, что порядок меняется, потому что, когда мы выполняем какую-то коммутативную операцию, порядок не важен … для конкатенации строк вместо этого.
итак, вопрос в том, как в fold left оценивается конкатенация строк и как я могу концептуально визуализировать порядок операций? потому что, если я выполняю шаг за шагом следующую операцию, результат процесса всегда будет одинаковым…

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

Комментарии:

1. Параметры «перевернуты». В foldr the «накопитель» является вторым элементом, тогда как в the foldl — первым.

Ответ №1:

Порядок параметров функции свертки «перевернут». Действительно, если мы посмотрим на определения для foldl и foldr мы увидим:

 foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b  

Здесь в обоих определениях «накопитель» имеет тип b . Но, как вы можете видеть, в foldl функции функция fold имеет тип b -> a -> b , поэтому первым параметром является накопитель, вторым — элемент, и он возвращает новое значение для накопителя. Это иллюстрирует, что свертка выполняется слева направо.

Для функции foldr функция fold имеет тип a -> b -> b , поэтому сначала она принимает элемент, затем принимает значение для аккумулятора после сворачивания остальной части списка, и вы должны вернуть новое значение для аккумулятора. Таким образом, он складывается справа налево.

Если вы хотите, чтобы это работало foldl , вы, таким образом, используете b a -> … b аккумулятор. В этом случае вы возвращаетесь b take a 3 , поэтому мы добавляем первые три символа a в правый конец аккумулятора:

 Prelude> foldr (a b -> take 3 a    b) "" ["Pizza", "Apple", "Banana"]
"PizAppBan"
Prelude> foldl (b a -> b    take 3 a) "" ["Pizza", "Apple", "Banana"]
"PizAppBan"
  

Таким образом, переключения параметров недостаточно. Вычисление выполняется слева направо в случае foldl и справа налево в случае foldr , таким образом, это означает, что вам также необходимо поменять способ объединения.

Однако этот foldl вариант здесь будет менее эффективным, поскольку ( ) :: [a] -> [a] -> [a] функция занимает время, линейное по длине первого операнда.

Функция a b -> take 3 a b не является ни коммутативной, ни ассоциативной. Действительно, коммутативное означает, что f x y равно f y x для каждого x и y . Но если мы вызываем f "foobar" "qux" , мы получаем:

 Prelude> f "foobar" "qux"
"fooqux"
Prelude> f "qux" "foobar"
"quxfoobar"
  

Операция concat ( ) также не является коммутативной.

Кроме того, сопоставление также не является ассоциативным из-за take 3 части. Действительно, ассоциативная операция означает, что f x (f y z) равно f (f x y) z для каждого x , y и z . Но мы видим:

 Prelude> f (f "foo" "bar") "qux"
"fooqux"
Prelude> f "foo" (f "bar" "qux")
"foobarqux"
  

Однако, если вы сначала выполните a map , чтобы взять первые три символа каждой строки, тогда действительно порядок не имеет значения:

 Prelude> foldl (  ) "" (map (take 3) food)
"PizAppBan"
Prelude> foldr (  ) "" (map (take 3) food)
"PizAppBan"
  

Таким take 3 образом, часть делает функцию неассоциативной, и поэтому вы не можете использовать ее в обоих foldl и foldr без ее правильной адаптации.

Комментарии:

1. Привет! спасибо, что ответили да, но в моем случае foldl возврат «BanAppPizz» и foldr возврат «PizzAppBan» …. я не понял this….in практикуйте то, что происходит внутри функции? вы можете показать мне процесс? потому что, если я думаю об этом на бумаге, я дохожу до той же точки: foldr->("Ban" ("App" ( "Pizz" "" ))) и foldl-> (((("" "Ban") "App") "Pizz") …. так что оба будут казаться одинаковыми… в чем я не прав?

2. @Markus: вы используете take 3 , то есть не только для значений параметра , но и для промежуточных результатов foldl , так take 3 (take 3 (take 3 "" "Pizza") "Apple") "Banana" что в результате после второй «итерации» вы каждый раз сохраняете только первые три символа, поэтому все остальное снова удаляется.

3. @Markus: обратите внимание, что ваша функция, однако, не является коммутативной ( ( ) также не является коммутативной, поскольку ("foo" "bar") != ("bar" "foo") ), но, что более важно, она также не является ассоциативной .

4. take 3 (take 3 (take 3 "" "Pizza") "Apple") "Banana" …. хорошо, но зачем foldl переворачивать результат? как вы уже написали… порядок тот же, за исключением процесса выполнения заказа… но если бы я написал только ((("" "Ban" ) "App") "Piz") или ("Ban" ("App" ( "Piz" "" ))) … порядок всегда остается неизменным ….. Почему???

5. @Markus: поскольку он идет слева направо, имеет смысл сначала указать накопитель, а затем элемент. Но, несмотря на это, речь идет не только об изменении результата. Поскольку ваша функция не является ассоциативной. Это означает, что порядок выполнения имеет значение. Только для ассоциативных функций, как будто ( ) это не имеет значения.