foldl с ( ++ ) намного медленнее, чем foldr

почему foldl медленнее, чем foldr иногда ? У меня есть список списков " a " like

a = [[1],[2],[3]]

и хотите изменить его на список, используя fold

foldr (++) [] a

он работает нормально(сложность времени O (n)). Но если вместо этого я использую foldl, это очень медленно (сложность времени O(n^2)).

foldl (++) [] a

если foldl просто складывает список ввода с левой стороны,

(([] ++ [1]) ++ [2]) ++ [3]

и фолдр с правой стороны,

[1] ++ ([2] ++ ([3] ++ []))

количество вычисления ( ++ ) должны быть одинаковыми в обоих случаях. Почему фолдр так медлителен? В соответствии со сложностью времени, foldl выглядит так, как будто сканирование входного списка в два раза больше, чем foldr. Я использовал следующее для компьютера time

length $ fold (++) [] $ map (:[]) [1..N]   (fold is either foldl or foldr)

1 ответов


это из-за того, как ++ строительство. Обратите внимание, что is определяется индукцией по ее первому аргументу.

[]     ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)

количество рекурсивных вызовов зависит только от length xs.

из-за этого, в xs ++ ys удобнее иметь небольшой xs и большой ys чем наоборот.

обратите внимание, что складывание справа достигает этого:

[1] ++ ([2] ++ ([3] ++ ...

у нас есть только короткие (O(1)-длина) списки слева от ++, сделать складку стоимостью O (n).

вместо складывания слева плохо:

((([] ++ [1]) ++ [2]) ++ [3]) ++ ...

левые аргументы становятся все больше и больше. Следовательно, мы получаем o(n^2) сложность здесь.

этот аргумент можно сделать более точным, учитывая, как требуется список вывода. Можно заметить, что foldr производит свой результат в "потоковой" моде, где требование, например, первая ячейка списка вывода только заставляет небольшую часть ввода - только первый ++ должен быть выполняется, и на самом деле нужен только первый рекурсивный шаг! Вместо этого требуя даже только первый пункт из foldl результат законную силу все the ++ вызовы, что делает его довольно дорогим(даже если каждый вызов требует только одного рекурсивного шага, есть O (n) вызовов).