
Fun with Prolog: Priceonomics Puzzle - dangoldin
http://dangoldin.com/2013/06/07/fun-with-prolog-priceonomics-puzzle/
======
octernion
I really enjoyed reading this - thanks for posting!

I had totally forgotten about Prolog, and I love how it essentially forces you
to approach problems in an absolutely new manner (conceptually) than more
traditional imperative programming.

~~~
dangoldin
Glad you enjoyed. It was pretty frustrating getting started when you know how
easy some of the things are to do in other languages but once I got into it it
felt great. You definitely start thinking about them at a different level
although parts of it felt pretty functional/mathematical to me.

The other thing I realize now is how much easier it is to get started now with
StackOverflow and Google being available. I remember having to go through
tutorials and demos that came with the software or read books to learn new
languages. Now there are so many resources out there.

------
darksaints
The prolog solution is cool, but I would have solved it with a bellman ford
algorithm.

~~~
omarish
Yup, Bellman Ford is the algorithm we had in mind for this puzzle. It's
similar to Dijkstra's algorithm, but deals with negative costs as well.

(I work at Priceonomics)

~~~
darksaints
I really like what you guys do. It is a great concept, and it sounds like a
really fun domain to work in. I hope I didn't ruin the puzzle by blurting this
out.

------
pessimizer
If it was Erlang, the reason that _Prolog_ would be considered a variable
would be the initial caps. _is_ and _sweet_ would be atoms (which are like
names or labels) because of the lack of quotes and lack of initial caps.
Prolog may convert atoms to strings when they're used in a string function (or
that particular string function may convert atoms to strings.) Do they even
call them functions in Prolog? Doesn't seem appropriate.

edit: also, single quotes in Erlang are a way of creating an initial cap atom,
or an atom with special characters in it. Double quotes are used for strings.
Might be the same.

edit: just checked, it's the same. And _compound term_?

~~~
dangoldin
OP here - someone left a comment on the blog who's clearly more knowledgeable
than I am. I'm copying it here:

    
    
        The quotes are necessary if an atom starts with something
        other than a lower-case character. Any unquoted token
        that starts with an upper-case letter is a variable.
    
        As for atomic_list_concat, it's an optimized library
        function that requires the separator. It's certainly 
        possible to write an equivalent that is completely 
        flexible.

~~~
sltkr
I think the predicate you wanted to demonstrate is called append/3, which
works on list (while concat applies to atoms).

In Prolog, 'foo' constructs an atom, which is equivalent to foo without any
quotes (but the quotes are useful to construct atoms with spaces, with digits,
starting with a capital letter, etc.)

However, if you want to manipulate atoms as text, you typically want to use
strings instead, which are literally lists of character codes in Prolog, and
can be entered as double quotes. For example, "ABC" = [65,66,67]. To convert
between strings and atoms dynamically, use atom_chars/2.

For example:

    
    
      ?- append("Prolog is", " awesome", Z), atom_chars(A,Z).
      Z = [80, 114, 111, 108, 111, 103, 32, 105, 115|...],
      A = 'Prolog is awesome'.
    
      ?- append("Prolog is", Y, "Prolog is awesome"), atom_chars(A,Y).
      Y = [32, 97, 119, 101, 115, 111, 109, 101],
      A = ' awesome'.
    
      ?- append(X, "is awesome", "Prolog is awesome"), atom_chars(A,X).
      X = [80, 114, 111, 108, 111, 103, 32],
      A = 'Prolog '.

~~~
dangoldin
Thanks for the explanation. It's such a neat idea that inputs and outputs can
be interchangeable. Opens up approaches that are very different than the
standard ones. The append predicate is definitely a better example so thanks
for sharing.

------
ruv
You mention doing it for variable length chains, It requires hardly more
effort than hard coding the length, something like

    
    
        find_match(X, P) :-
            append([usd | _], [usd], X),
            profit(X, P),
            P > 1.
    
        profit([A, B], P) :- exchange(A, B, P).
        profit([A, B | T], P) :-
            exchange(A, B, P1),
            profit([B | T], P2),
            P is P1 * P2.
     
        # 1 ?- find_match(X, P).
        # X = [usd, jpy, usd],
        # P = 1.0040982 ;
        # X = [usd, eur, jpy, usd],
        # P = 1.0040882716200001 ;
        # X = [usd, eur, btc, usd],
        # P = 1.01209651875 ;
        # X = [usd, btc, jpy, usd],
        # P = 1.0025512896 ;
        # X = [usd, eur, usd, jpy, usd],
        # P = 1.003776175666278 .

~~~
dangoldin
Thanks for sharing this. The way these are defined reminds me of the way I'd
be defining methods in SML - a base case and then a recursive one to deal with
the tail.

------
curiousdannii
My favourite part of Prolog is that if you ask it if something is in a list it
will decide to add it to the list so that it can return true!

~~~
fusiongyro
You must be referring to this:

    
    
        ?- member(what, X).
        X = [what|_G2122] ;
        X = [_G2121, what|_G2125] ...
    

If what you have isn't a variable, it is totally reasonable:

    
    
        ?- member(what, []).
        false.
    

This is because, lacking other evidence about what X might be, it can't
distinguish between making an assertion about a new quantity ("there is a list
X which 'what' is a member of") and a query ("is 'what' in the list?"). Making
an assertion like that, however, is pretty normal. It's a perfectly reasonable
declarative way to understand this behavior, it's just that this thinking is
unusual, so it's weird.

Maybe this little demonstration will give the flavor of why you might want
this:

    
    
        ?- length(X, 3), member(what, X).
        X = [what, _G942, _G945] ;
        X = [_G939, what, _G945] ;
        X = [_G939, _G942, what].

