convert string to list in prolog - prolog

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?

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.

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.

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

Related

Turning 1's and 0's to a list of characters in Prolog

I have a homework assignment using prolog and I'm translating a set of 1's and 0's to a different set of characters. For example, here are my facts
analog([1], .).
analog([1, 1, 1], -).
analog([0], "").
analog([0, 0, 0], ^).
analog([0, 0, 0, 0, 0, 0, 0], #).
An example input would be a list like
[1,1,1,0,1,1,1,
0,0,0,1,1,1,0,1,1,1,0,1,1,1,0,0,0,1,0,1,1,1,
0,1,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,
1,1,1,0,1,0,1,1,1,0,1,0,0,0,1,1,1,0,1,1,1,
0,1,1,1]
I have the following code :
signal_morse([], []). %base case
signal_morse([A, B, C | Rest, R) :-
analog([A, B, C], H),
signal_morse(Rest, T),
append([H], T, R).
It only currently checks the first three elements in a list and I want to be able to check for 1 element if the first three don't match with any of the facts.
For example, say I have [1, 0, 0], since that doesn't match with any of my rules I want the program to check [1] instead and keep looking through the rest of the list.
So I wanted to know if there is any sort of pattern matching I can do so if the code analog([A, B, C], H) fails to find a match, the code would then try to match just the first character like analog([A], H).
I realize this probably doesn't help you with your homework, but this is such a perfect problem for DCGs I can't help but show you what it would look like.
analog(.) --> [1].
analog(-) --> [1,1,1].
analog("") --> [0].
analog(^) --> [0,0,0].
analog(#) --> [0,0,0,0,0,0,0].
analogs([]) --> [].
analogs([A|As]) --> analog(A), analogs(As).
Usage:
?- phrase(analogs(X), [1,1,1,0,1,1,1, 0,0,0,1,1,1,0,1,1,1,0,1,1,1,0,0,0,1,0,1,1,1, 0,1,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0, 1,1,1,0,1,0,1,1,1,0,1,0,0,0,1,1,1,0,1,1,1, 0,1,1,1]).
X = ['.', '.', '.', "", '.', '.', '.', "", ""|...] ;
Anyway, to answer your actual question, Prolog can figure out the lengths on its own with something like this:
signal_morse([], []).
signal_morse(Signals, [Code|RemainingMorseCodes]) :-
append(Signal, RemainingSignals, Signals),
analog(Signal, Code),
signal_morse(RemainingSignals, RemainingMorseCodes).

Using a functor

Following code accepts a index , a list and it deletes every nth occurance of that index in the list and returns a new list.
deleteNTerm(N,L1,L2) :- deleteNTerm(L1,N,L2,N).
deleteNTerm([],_,[],_).
deleteNTerm([_|Xs],N,Ys,1) :- deleteNTerm(Xs,N,Ys,N).
deleteNTerm([X|Xs],N,[X|Ys],K) :- K > 1, K1 is K - 1, deleteNTerm(Xs,N,Ys,K1).
So for the following query
?- deleteNTerm(2,[1,2,3,4,5,6,7],Result).
Result = [1,3,5,7].
However I want my code to accept a functor instead so i get
?-deleteNterm(2,f(1,2,3,4,5,6,7),Result).
Result f(1,3,5,7)
How to achieve this?.
You can break down a term into its functor and arguments using the (appropriately named) predicate functor. This being prolog, it is also used to build a term from those components. For example: functor(A,f,3) will yield A = f(_G2130, _G2131, _G2132).
Actually, what would be more helpful would be =..:
3 ?- f(1,3,5,7) =.. X.
X = [f, 1, 3, 5, 7].
4 ?- X =.. [f,1,3,5,7].
X = f(1, 3, 5, 7).

Check if string is substring in Prolog

Is there a way to check if a string is a substring of another string in Prolog? I tried converting the string to a list of chars and subsequently checking if the first set is a subset of the second that that doesn't seem to be restrictive enough. This is my current code:
isSubstring(X,Y):-
stringToLower(X,XLower),
stringToLower(Y,YLower),
isSubset(XLower,YLower).
isSubset([],_).
isSubset([H|T],Y):-
member(H,Y),
select(H,Y,Z),
isSubset(T,Z).
stringToLower([],[]).
stringToLower([Char1|Rest1],[Char2|Rest2]):-
char_type(Char2,to_lower(Char1)),
stringToLower(Rest1,Rest2).
If I test this with
isSubstring("test","tesZting").
it returns yes, but should return no.
It is not clear what you mean by a string. But since you say you are converting it to a list, you could mean atoms. ISO Prolog offers atom_concat/3 and sub_atom/5 for this purpose.
?- atom_concat(X,Y,'abc').
X = '', Y = abc
; X = a, Y = bc
; X = ab, Y = c
; X = abc, Y = ''.
?- sub_atom('abcbcbe',Before,Length,After,'bcb').
Before = 1, Length = 3, After = 3
; Before = 3, Length = 3, After = 1.
Otherwise, use DCGs! Here's how
seq([]) --> [].
seq([E|Es]) --> [E], seq(Es).
... --> [] | [_], ... .
subseq([]) --> [].
subseq(Es) --> [_], subseq(Es).
subseq([E|Es]) --> [E], subseq(Es).
seq_substring(S, Sub) :-
phrase((...,seq(Sub),...),S).
seq_subseq(S, Sub) :-
phrase(subseq(Sub),S).
Acknowledgements
The first appearance of above definition of ... is on p. 205, Note 1 of
David B. Searls, Investigating the Linguistics of DNA with Definite Clause Grammars. NACLP 1989, Volume 1.
Prolog strings are lists, where each element of the list is the integer value representing the codepoint of the character in question. The string "abc" is exactly equivalent to the list [97,98,99] (assuming your prolog implementation is using Unicode or ASCII, otherwise the values might differ). That leads to this (probably suboptimal from a Big-O perspective) solution, which basically says that X is a substring of S if
S has a suffix T such that, and
X is a prefix of T
Here's the code:
substring(X,S) :-
append(_,T,S) ,
append(X,_,T) ,
X \= []
.
We restrict X to being something other than the empty list (aka the nil string ""), since one could conceptually find an awful lot of zero-length substrings in any string: a string of length n has 2+(n-1) nil substrings, one between each character in the string, one preceding the first character and one following the last character.
The problem is with your isSubset/2.
There are two distinct situations that you've tried to capture in one predicate. Either you're looking for the first position to try to match your substring, or you've already found that point and are checking whether the strings 'line up'.
isSubset([], _).
isSubSet(Substring, String) :-
findStart(Substring, String, RestString),
line_up(Substring, RestString).
findStart([], String, String).
findStart([H|T], [H|T1], [H|T1]).
findStart(Substring, [_|T], RestString) :-
findStart(Substring, T, RestString).
line_up([], _).
line_up([H|T], [H|T1]) :-
line_up(T, T1).
You can combine these into one predicate, as follows:
isSublist([], L, L).
isSublist([H|T], [H|T1], [H|T1]) :-
isSublist(T, T1, T1).
isSublist(L, [_|T], Rest) :-
isSublist(L, T, Rest).
Using DCG's you can do the following: (SWI)
% anything substring anything
substr(String) --> ([_|_];[]), String, ([_|_];[]).
% is X a substring of Y ?
substring(X,Y) :- phrase(substr(X),Y).

How to count how many times a character appears in a string list in Prolog?

I want to check if a character exists in a string. So Atom is the string and Ch the character. name is a predicate that converts the string in a list of numbers according to the ASCII code.
find_element is a predicate that is supposed to be true only if element X is part of a list. C is a counter that tells us where exactly element X was found.
This is the result I am getting:
?- exists(prolog,g). [103][112,114,111,108,111,103] false.
-------> 103 is the ASCII code of letter "g" and the list [112,114,111,108,111,103] is the list that represents the string "prolog". The question exists(prolog,g) should have provided a true response.
However the find_element predicate is working correctly. I don't understand why this is happening because when I type for example
?- find_element(5,[3,4,5,6,5,2],X).
I am getting X= 3 ; X = 5 ; false.
---->
which is absolutely fine because it tells me that 5 is the 3rd and the 5th element of the list.
So the problem is that find_element is working when I type something like ?- find_element(5,[3,4,5,6,5,2],X) but it is not when I try to call the predicate exists (which calls find_element).
This is the code:
find_element(X,[X|T],1).
find_element(X,[H|T],C):- find_element(X,T,TEMPC), C is TEMPC +1.
exists(Atom,Ch):- name(Atom,[X|T]), name(Ch,Z), write(Z), write([X|T]), find_element(Z,[X|T],Count).
Thanks in advance
I've cleaned a bit your code, and fixed a bug:
find_element(X,[X|_], 1).
find_element(X,[_|T], C) :-
find_element(X,T,TEMPC),
C is TEMPC +1.
exists(Atom, Ch):-
name(Atom, L),
name(Ch, [Z]),
find_element(Z, L, _Count).
note name(Ch, [Z]) to extract the single character. Now
?- exists(pippo,o).
true
It's worth to note that
?- find_element(3, [1,2,3,4,1,2,3,4],P).
P = 3 ;
P = 7 ;
false.
?- nth1(P, [1,2,3,4,1,2,3,4], 3).
P = 3 ;
P = 7 ;
false.
your find_element/3 behaves as nth1/3, with arguments 1 and 3 swapped.
Of course there are simpler and more general ways to perform such test. Using ISO builtins
like sub_atom/5 (a really powerful primitive for atom inspection)
?- sub_atom(pippo, _,_,_, o).
true ;
or memberchk/2, after the conversion to character lists that you already know (but using ISO builtin atom_codes/2)
exists(Atom, Ch):-
atom_codes(Atom, L),
atom_codes(Ch, [Z]),
memberchk(Z, L).
To count occurrences of a sub_atom, library(aggregate) can be used
occurences(Atom, Ch, N) :-
aggregate_all(count, sub_atom(Atom, _,_,_, Ch), N).
?- occurences(pippo, p, X).
X = 3.

Prolog programs - how to make it work?

I have these two programs and they're not working as they should. The first without_doubles_2(Xs, Ys)is supposed to show that it is true if Ys is the list of the elements appearing in Xs without duplication. The elements in Ys are in the reversed order of Xs with the first duplicate values being kept. Such as, without_doubles_2([1,2,3,4,5,6,4,4],X) prints X=[6,5,4,3,2,1] yet, it prints false.
without_doubles_2([],[]).
without_doubles_2([H|T],[H|Y]):- member(H,T),!,
delete(H,T,T1),
without_doubles_2(T1,Y).
without_doubles_2([H|T],[H|Y]):- without_doubles_2(T,Y).
reverse([],[]).
reverse([H|T],Y):- reverse(T,T1), addtoend(H,T1,Y).
addtoend(H,[],[H]).
addtoend(X,[H|T],[H|T1]):-addtoend(X,T,T1).
without_doubles_21(X,Z):- without_doubles_2(X,Y),
reverse(Y,Z).
The second one is how do I make this program use a string? It's supposed to delete the vowels from a string and print only the consonants.
deleteV([H|T],R):-member(H,[a,e,i,o,u]),deleteV(T,R),!.
deleteV([H|T],[H|R]):-deleteV(T,R),!.
deleteV([],[]).
Your call to delete always fails because you have the order of arguments wrong:
delete(+List1, #Elem, -List2)
So instead of
delete(H, T, T1)
You want
delete(T, H, T1)
Finding an error like this is simple using the trace functionality of the swi-prolog interpreter - just enter trace. to begin trace mode, enter the predicate, and see what the interpreter is doing. In this case you would have seen that the fail comes from the delete statement. The documentation related to tracing can be found here.
Also note that you can rewrite the predicate omitting the member check and thus the third clause, because delete([1,2,3],9001,[1,2,3]) evaluates to true - if the element is not in the list the result is the same as the input. So your predicate could look like this (name shortened due to lazyness):
nodubs([], []).
nodubs([H|T], [H|Y]) :- delete(T, H, T1), nodubs(T1, Y).
For your second question, you can turn a string into a list of characters (represented as ascii codes) using the string_to_list predicate.
As for the predicate deleting vovels from the string, I would implement it like this (there's probably better solutions for this problem or some built-ins you could use but my prolog is somewhat rusty):
%deleteall(+L, +Elems, -R)
%a helper predicate for deleting all items in Elems from L
deleteall(L, [], L).
deleteall(L, [H|T], R) :- delete(L, H, L1), deleteall(L1, T, R).
deleteV(S, R) :-
string_to_list(S, L), %create list L from input string
string_to_list("aeiou", A), %create a list of all vovels
deleteall(L, A, RL), %use deleteall to delete all vovels from L
string_to_list(R, RL). %turn the result back into a string
deleteV/2 could make use of library(lists):
?- subtract("carlo","aeiou",L), format('~s',[L]).
crl
L = [99, 114, 108].
while to remove duplicates we could take advantage from sort/2 and select/3:
nodup(L, N) :-
sort(L, S),
nodup(L, S, N).
nodup([], _S, []).
nodup([X|Xs], S, N) :-
( select(X, S, R) -> N = [X|Ys] ; N = Ys, R = S ),
nodup(Xs, R, Ys).
test:
?- nodup([1,2,3,4,4,4,5,2,7],L).
L = [1, 2, 3, 4, 5, 7].
edit much better, from ssBarBee
?- setof(X,member(X,[1,2,2,5,3,2]),L).
L = [1, 2, 3, 5].

Resources