Pergunta

I have a list of integers based on letters. For example:

let charlist = map (ord) "ABCDEF"

charlist would then look like the following:

[65,66,67,68,69,70]

I also have a list of three functions: (+), (-) and (*). The list in this example looks like this

let funclist = [(+), (-), (*)]

I want to apply the functions in order between the elements in charlist (if there are more "spaces" in charlist than there are elements in funclist, you start over from the beginning of funclist) and calculate the final value from left to right, like the following:

s = ((((((65) + 66) - 67) * 68) + 69) - 70)

I was thinking about using foldl, but foldl seems to only work with one function. Is there any other way to do this? I would like to summarize this entire process in one function, if possible, though it's not a requirement.

Foi útil?

Solução

Whilst foldl may only apply one function, you can have different functions in your data. The trick it to attach the appropriate +, - or * function to your data. You're off to the right start:

funclist = [(+), (-), (*)]

But lets now make a infinite version of the above list, like [(+), (-), (*), (+), (-), (*)...]

infinite_funclist = cycle funclist

Lets assign the numbers we're going to fold over here. first is in this case 65, rest is [66..70]

(first:rest) = [65..70]

Now we zip together rest and infinite_funclist to get [(66,(+)), (67,(-)), (68,(*)), (69,(+)), (70,(-))]. We start with first, and for every new element, we apply the operation that's in the second part of the current tuple to the current value and the first part like so:

result = foldl' (\acc (v, f) -> acc `f` v) first (zip rest infinite_funclist)

If we want to print the result we can do so like so:

main = print result

(Link to code here)

Outras dicas

In essence, this is a zip rather than a fold. Nicest would be

> zipWith ($) (cycle funclist) charlist
[(65+), (66-), (67*), (68+), (69-), (70+)]

but because of left-associativity and initial element we need to do it a bit more wordy

> let ptApplied = tail . zipWith (($) . flip) (cycle funclist) $ charlist
ptApplied = [(+66), (subtract 67), (*68), (+69), (subtract 70)]

Only then comes the fold

> foldl' (flip ($)) (head charlist) ptApplied

You can always pass more information through the accumulator of the fold, which can be used by your single function to behave as if it were a different function each time. Probably the simplest way to do this is to pass the current list of functions to be applied through the rest of the list in the accumulator.

Prelude Data.List> snd $ foldl' (\(f:fs, acc) x -> (fs, f acc x))
                                ((+):cycle funclist, 0) [65,66,67,68,69,70]
4351

(I inserted an extra (+) on the front of funclist so that the "real" plus comes between 65 and 66, rather than between the initial accumulator 0 and 65)

Who says you can't "pass variables" to foldl?

Prelude> let f = [(+), (-), (*)]

Prelude> let c = [65,66,67,68,69,70]

Prelude> foldl (\(a,i) val -> ((f!!(mod i 3)) a val,i + 1)) (head c,0) (tail c)
(4351,5)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top