2

I am a Prolog newbie and am stuck at parsing a string to a list. I have a string of the form

1..2...3..4

I wish to convert it into a list which looks like

[1, _, _, 2, _, _, _, 3, _, _, 4]

How can I achieve this functionality?

false
  • 10,264
  • 13
  • 101
  • 209
Dhruv Chandna
  • 571
  • 5
  • 17

3 Answers3

2

Another solution is to use DCG's. The code is straightforward:

digit(N) -->
    [ D ], { member(D, "0123456789"), number_codes(N, [D]) }.

dot(_) --> ".".

token(T) --> digit(T).
token(T) --> dot(T).

tokens([T|Ts]) --> token(T), tokens(Ts).
tokens([]) --> "".

parse_codes(In, Out):-
    phrase(tokens(Out), In, "").

parse_atom(In, Out):-
    atom_codes(In, Codes),
    parse_codes(Codes, Out).

Testing on SWI-Prolog with "string" (which is actually just a list of codes):

?- parse_codes("1..24.4", Out).
Out = [1, _G992, _G995, 2, 4, _G1070, 4] .

And with an atom (which is just converted to codes before using the same predicate):

?- parse_atom('1..22.4', Out).
Out = [1, _G971, _G974, 2, 2, _G1049, 4] .

SWI-Prolog prints anonymous variables (_) in a bit fancier notation but otherwise it should be the same result you need.

Raivo Laanemets
  • 1,119
  • 10
  • 7
1

Yet another way.. take advantage of the fact that ascii numbers for 0..9 are known/fixed, then no type conversions or checks are needed, just subtractions.

% case 1: char is in decimal range 0-9, ie ascii 48,49,50,51,52,53,54,55,56,57
% so eg. char 48 returns integer 0
onechar(Char, Out) :-
    between(48, 57, Char),
    Out is Char -48.

% case 2: case 1 failed, dot '.' is ascii 46, use anonymous variable
onechar(46, _).

% execution
go(InString, OutList) :-
    maplist(onechar, InString, OutList).

Execution:

?- go("1..2...3..4", X).
X = [1, _G5638, _G5641, 2, _G5650, _G5653, _G5656, 3, _G5665, _G5668, 4]

Edit: forgot to say that this works because strings are represented as a list of ascii numbers, so string "0123456789" is represented internally as [48,49,50,51,52,53,54,55,56,57].

onechar does the calc for 1 of those list items, then maplist calls the same predicate on all list items.

Edit 2: the 2nd rule was originally:

% case 2: case 1 failed, output is an anon variable
onechar(_, _).

This is too generous - presumably if the input does not contain 0.9 or a dot, then the predicate should fail.

magus
  • 1,347
  • 7
  • 13
  • You should really use the `0'` notation instead of direct ascii codes. It is the same, just easier to read (no magic numbers). –  Aug 15 '13 at 05:11
0

A predicate that describes the relationship between a character in your string and an element of the list could be:

char_to_el(DigitChar, Digit) :- % a character between '0' and '9'
    DigitChar >= 0'0, DigitChar =< 0'9,
    number_codes(Digit, [DigitChar]).
char_to_el(0'., _). % the element is the '.' characther

The first clause checks whether the character is indeed a digit and converts it to an integer. You could also simply subtract 0'0 from the integer value of the character, so instead of using number_codes/2 you could write Digit is DigitChar - 0'0.

You should be able to use maplist/3 then, according to the gnu prolog manual:

| ?- maplist(char_to_el, "1..2...3..4", L).

L = [1,_,_,2,_,_,_,3,_,_,4]

yes

but it didn't work on my system (old gnu prolog version maybe?), so instead:

str_to_list([], []).
str_to_list([C|Cs], [E|Es]) :-
    char_to_el(C, E),
    str_to_list(Cs, Es).

| ?- str_to_list("1..2...3..4", L).

L = [1,_,_,2,_,_,_,3,_,_,4]

yes