read line to atomic list in prolog - prolog

I need to read any line (from user_input) into an atomic list, e.g.:
Example line, which contains any ASCII chars.
into:
[Example,'line,',which,contains,any,ASCII,'chars.']
what I've got so far:
read_line_to_codes(user_input, Input),
atom_codes(IA,Input),
atomic_list_concat(AlistI,' ',IA).
but that only works w/ single words, because of atom_codes.
read/2 also complains about spaces, so is there any way to do this?
oh and maybe then splitting at comma into 2d-lists, appending the dot/exclamationmark/questionmark, e.g.:
[[Example,line],[which,contains,any,ASCII,chars],'.']
btw: that's SWI-prolog.
EDIT: found the solution:
read_line_to_codes(user_input, Input),
string_to_atom(Input,IA),
atomic_list_concat(AlistI,' ',IA),
can't answer my own question because i don't have 100 reputation :-/

input_to_atom_list(L) :-
read_line_to_codes(user_input, Input),
string_to_atom(Input,IA),
tail_not_mark(IA, R, T),
atomic_list_concat(XL, ',', R),
maplist(split_atom(' '), XL, S),
append(S, [T], L).
is_period(.).
is_period(?).
is_period(!).
split_atom(S, A, L) :- atomic_list_concat(XL, S, A), delete(XL, '', L).
%if tale is ? or ! or . then remove
%A:Atom, R:Removed, T:special mark
tail_not_mark(A, R, T) :- atom_concat(R, T, A), is_period(T),!.
tail_not_mark(A, R, '') :- A = R.
DEMO
1 ?- input_to_atom_list(L).
|: Example line, which contains any ASCII chars.
L = [['Example', line], [which, contains, any, 'ASCII', chars], '.'].

Related

Pattern matching with lists and strings with Prolog

a prolog newby here.
I found the following code online:
string_to_list_of_characters(String, Characters) :-
name(String, Xs),
maplist( number_to_character,
Xs, Characters ).
number_to_character(Number, Character) :-
name(Character, [Number]).
I want to use it to do some pattern matching.
This is what I have tried so far:
wordH1(H1) :-
word(H1),
string_length(H1,6),
string_to_list_of_characters(H1, X) = a,_,_,_,_,_.
I want to get all strings which are of length 6 and that start with an a.
You seem to be using some very old learning resource. Instead of writing this string_to_list_of_characters predicate yourself you can just use the builtin atom_chars:
?- atom_chars(apple, Chars).
Chars = [a, p, p, l, e].
?- atom_chars(amazon, Chars).
Chars = [a, m, a, z, o, n].
For pattern matching you can write lists similarly to how you tried to do it, but you need square brackets around the elements. You also don't pattern match on something like a "function application expression" as you would in other programming languages. Rather you apply a predicate and then write a separate unification. So it's not something like atom_chars(A, B) = Something but rather:
?- atom_chars(apple, Chars), Chars = [a,_,_,_,_,_].
false.
?- atom_chars(amazon, Chars), Chars = [a,_,_,_,_,_].
Chars = [a, m, a, z, o, n].

prolog - sublist of list - alternative approach

I implemented function to get sublist of list, for example:
sublist([1,2,4], [1,2,3,4,5,1,2,4,6]).
true
sublist([1,2,4], [1,2,3,4,5,1,2,6]).
false
look at my solution:
my_equals([], _).
my_equals([H1|T1], [H1|T2]) :- my_equals(T1, T2).
sublist([], _).
sublist(L1, [H2|T2]) :- my_equals(L1, [H2|T2]); sublist(L1, T2).
Could you give me another solution ? Maybe there is exists some predefined predicate as my_equals ?
You can unify a sublist using append/3, like this:
sublist(SubList, List):-
append(_, Tail, List),
append(SubList, _, Tail).
The first call to append/3 will split List into two parts (i.e. dismiss the some "leading" items from List.
The second call to append/3 will check whether SubList is itself a sublist of Tail.
As #false's suggests it would be better, at least for ground terms, to exchange goals,
sublist(SubList, List):-
append(SubList, _, Tail),
append(_, Tail, List).
There's also a DCG approach to the problem:
substr(Sub) --> seq(_), seq(Sub), seq(_).
seq([]) --> [].
seq([Next|Rest]) --> [Next], seq(Rest).
Which you would call with:
phrase(substr([1,2,4]), [1,2,3,4,5,1,2,4,6]).
You can define:
sublist(Sub, List) :-
phrase(substr(Sub), List).
So you could call it by, sublist([1,2,4], [1,2,3,4,5,1,2,4,6])..
Per #mat's suggestion:
substr(Sub) --> ..., seq(Sub), ... .
... --> [] | [_], ... .
Yes, you can have a predicate named .... :)
Per suggestions from #repeat and #false, I changed the name from subseq (subsequence) to substr (substring) since the meaning of "subsequence" embraces non-contiguous sequences.
This is an alternative solution to Lurkers, which is slightly faster,
assuming S is much shorter than L in length and thus the phrase/3 DCG
translation time is negligible:
sublist(S, L) :-
phrase((..., S), L, _).
If S=[X1,..,Xn] it will DCG translate this into a match I=[X1,..,Xn|O]
before execution, thus delegating my_equals/2 completely to Prolog
unification. Here is an example run:
?- phrase((..., [a,b]), [a,c,a,b,a,c,a,b,a,c], X).
X = [a, c, a, b, a, c] ;
X = [a, c] ;
false.
Bye
P.S.: Works also for other patterns S than only terminals.
Maybe there is exists some predefined predicate
If your Prolog has append/2 from library(lists):
sublist(S, L) :- append([_,S,_], L).
Another fairly compact definition, available in every (I guess) Prolog out there:
sublist(S, L) :- append(S, _, L).
sublist(S, [_|L]) :- sublist(S, L).
Solution in the original question is valid just, as has been said, remark that "my_equals" can be replaced by "append" and "sublist" loop by another append providing slices of the original list.
However, prolog is (or it was) about artificial intelligence. Any person can answer immediately "no" to this example:
sublist([1,1,1,2], [1,1,1,1,1,1,1,1,1,1] ).
because a person, with simple observation of the list, infers some characteristics of it, like that there are no a "2".
Instead, the proposals are really inefficient on this case. By example, in the area of DNA analysis, where long sequences of only four elements are studied, this kind of algorithms are not applicable.
Some easy changes can be done, with the objective of look first for the most strongest condition. By example:
/* common( X, Y, C, QX, QY ) => X=C+QX, Y=C+QY */
common( [H|S2], [H|L2], [H|C2], DS, DL ) :- !,
common( S2, L2, C2, DS, DL ).
common( S, L, [], S, L ).
sublist( S, L ) :-
sublist( [], S, L ).
sublist( P, Q, L ) :- /* S=P+Q */
writeln( Q ),
length( P, N ),
length( PD, N ), /* PD is P with all unbound */
append( PD, T, L ), /* L=PD+T */
common( Q, T, C, Q2, _DL ), /* S=P+C+Q2; L=PD+C+_DL */
analysis( L, P, PD, C, Q2 ).
analysis( _L, P, P, _C, [] ) :- !. /* found sublist */
analysis( [_|L2], P, _PD, C, [] ) :- !,
sublist( P, C, L2 ).
analysis( [_|L2], P, _PD, C, Q2 ) :-
append( P, C, P2 ),
sublist( P2, Q2, L2 ).
Lets us try it:
?- sublist([1,1,1,2], [1,1,1,1,1,1,1,1,1,1]).
[1,1,1,2]
[2]
[2]
[2]
[2]
[2]
[2]
[2]
[2]
false.
see how "analysis" has decided that is better look for the "2".
Obviously, this is a strongly simplified solution, in a real situation better "analysis" can be done and patterns to find must be more flexible (the proposal is restricted to patterns at the tail of the original S pattern).

Replacing white spaces in prolog

Is it possible in prolog to replace all white spaces of a string with some given character?
Example-
If I have a variable How are you today? and I want How_are_you_today?
For atoms
There are may ways in which this can be done. I find the following particularly simple, using atomic_list_concat/3:
?- atomic_list_concat(Words, ' ', 'How are you today?'), atomic_list_concat(Words, '_', Result).
Words = ['How', are, you, 'today?'],
Result = 'How_are_you_today?'.
For SWI strings
The above can also be done with SWI strings. Unfortunately, there is no string_list_concat/3 which would have made the conversion trivial. split_string/4 is very versatile, but it only does half of the job:
?- split_string("How are you today?", " ", "", Words).
Words = ["How", "are", "you", "today?"].
We can either define string_list_concat/3 ourselves (a first attempt at defining this is shown below) or we need a slightly different approach, e.g. repeated string_concat/3.
string_list_concat(Strings, Separator, String):-
var(String), !,
maplist(atom_string, [Separator0|Atoms], [Separator|Strings]),
atomic_list_concat(Atoms, Separator0, Atom),
atom_string(Atom, String).
string_list_concat(Strings, Separator, String):-
maplist(atom_string, [Separator0,Atom], [Separator,String]),
atomic_list_concat(Atoms, Separator0, Atom),
maplist(atom_string, Atoms, Strings).
And then:
?- string_list_concat(Words, " ", "How are you today?"), string_list_concat(Words, "_", Result).
Words = ["How", "are", "you", "today?"],
Result = "How_are_you_today?".
It all depends on what you mean by a string. SWI has several for them, some are generally available in any common Prolog and conforming to the ISO standard ; and some are specific to SWI and not conforming. So, let's start with those that are generally available:
###Strings as list of character codes — integers representing code points
This representation is often the default, prior to SWI 7 it was the default in SWI, too. The biggest downside is that a list of arbitrary integers can now be confused with text.
:- set_prolog_flag(double_quotes, codes).
codes_replaced(Xs, Ys) :-
maplist(space_repl, Xs, Ys).
space_repl(0' ,0'_).
space_repl(C, C) :- dif(C,0' ).
?- codes_replaced("Spaces !", R).
R = [83,112,97,99,101,115,95,95,33]
; false.
###Strings as list of characters — atoms of length 1
This representation is a bit cleaner since it does not confuse integers with characters, see this reply how to get more compact answers.
:- set_prolog_flag(double_quotes, chars).
chars_replaced(Xs, Ys) :-
maplist(space_replc, Xs, Ys).
space_replc(' ','_').
space_replc(C, C) :- dif(C,' ').
?- chars_replaced("Spaces !", R).
R = ['S',p,a,c,e,s,'_','_',!]
; false.
###Strings as atoms
#WouterBeek already showed you how this can be done with an SWI-specific built-in. I will reuse above:
atom_replaced(A, R) :-
atom_chars(A, Chs),
chars_replaced(Chs, Rs),
atom_chars(R, Rs).
?- atom_replaced('Spaces !',R).
R = 'Spaces__!'
; false.
So far everything applies to iso-prolog
###Strings as an SWI-specific, non-conforming data type
This version does not work in any other system. I mention it for completeness.
SWI-Prolog DCGs allows an easy definition, using 'push back' or lookahead argument:
?- phrase(rep_string(` `, `_`), `How are you`, R),atom_codes(A,R).
R = [72, 111, 119, 95, 97, 114, 101, 95, 121|...],
A = 'How_are_you'
The definition is
rep_string(Sought, Replace), Replace --> Sought, rep_string(Sought, Replace).
rep_string(Sought, Replace), [C] --> [C], rep_string(Sought, Replace).
rep_string(_, _) --> [].
edit To avoid multiple 'solutions', a possibility is
rep_string(Sought, Replace), Replace --> Sought, !, rep_string(Sought, Replace).
rep_string(Sought, Replace), [C] --> [C], !, rep_string(Sought, Replace).
rep_string(_, _) --> [].

Replace atom with variable

I have a term which may or may not contain the atom 'this'. The term may also contain variables.
I need to replace 'this' with a variable I. How can I do this?
I tried to do something like this:
term_to_atom((f(a), g(this, b), ...), A),
tokenize_atom(A, L),
replace(this, I, L, L2)
It seemed to work. The problem is, I need to go back to the original term and I can't do it...
SWI-Prolog has atomic_list_concat/2 and atom_to_term/2 which should help you go back to the original term.
main :-
term_to_atom((f(a), g(this, b)), A),
tokenize_atom(A, L),
replace(this, 'I', L, L2),
atomic_list_concat(L2, A2),
atom_to_term(A2, T, []),
writeln(T).
?- main.
f(a),g(_G69,b)
true .
Take a look at this predicate (replace/4):
replace(Term,Term,With,With) :-
!.
replace(Term,Find,Replacement,Result) :-
Term =.. [Functor|Args],
replace_args(Args,Find,Replacement,ReplacedArgs),
Result =.. [Functor|ReplacedArgs].
replace_args([],_,_,[]).
replace_args([Arg|Rest],Find,Replacement,[ReplacedArg|ReplacedRest]) :-
replace(Arg,Find,Replacement,ReplacedArg),
replace_args(Rest,Find,Replacement,ReplacedRest).
An example of what you need:
| ?- replace(f(1,23,h(5,this)),this,Var,Result).
Result = f(1,23,h(5,Var))
yes

Password checking with prolog/2

I've got this Prolog program below that examines if a password fulfills certain rules (the password must contain a letter(a-z), a number(0-9),a double letter (aa,ll,ww etc.), must start with a letter (a, aa, c etc.), must be at least 6 characters long).
How can I expand it so that double letters would be counted as one letter? (For example, aa25b1 wouldn't be a correct password as it's only five characters long).
contains_letter(Password) :- wildcard_match('*[a-zA-Z]*', Password).
contains_number(Password) :- wildcard_match('*[0-9]*', Password).
contains_double_letter(Password) :-
(between(65, 90, Letter) ; between(97, 122, Letter)),
append([_, [Letter, Letter], _], Password),
!.
starts_with_letter(Password) :- wildcard_match('[a-zA-Z]*', Password).
long_enough(Password) :-
length(Password, Length),
Length >= 6.
check_everything(Password) :-
contains_letter(Password),
contains_number(Password),
contains_double_letter(Password),
starts_with_letter(Password),
long_enough(Password).
Preprocess the password with a predicate that squashes together equal characters, e.g.
uniq([], []).
uniq([X], [X]).
uniq([X,X|L], R) :-
!,
uniq([X|L], R).
uniq([X,Y|L], [X|R]) :-
uniq([Y|L], R).
(I named this after the Unix tool uniq; you might want to rename it without_adjacent_repetitions or something more to your taste.)
First and as I precised in your first question, note that you can combine that :
contains_letter(Password) :- wildcard_match('*[a-zA-Z]*', Password).
contains_number(Password) :- wildcard_match('*[0-9]*', Password).
starts_with_letter(Password) :- wildcard_match('[a-zA-Z]*', Password).
Into that :
letter_start_and_number(Password) :-
wildcard_match('[a-zA-Z]*[0-9]*', Password).
Now, you can handle double letters and length as follows :
length_double_letters([], Acc, yes, Acc).
length_double_letters([Char, Char|Password], Acc, _Contains, Length) :-
!,
NewAcc is Acc + 1,
length_double_letters(Password, NewAcc, yes, Length).
length_double_letters([_Char|Password], Acc, Contains, Length) :-
NewAcc is Acc + 1,
length_double_letters(Password, NewAcc, Contains, Length).
Using that predicate into this main predicate :
check_everything(Password) :-
letter_start_and_number(Password),
length_double_letters(Password, 0, no, Length),
Length >= 6.
PS : please take the time to accept the answers you find the most constructive and upvote the ones that helped you.
Write your own lengthWithDoubleLetters/2 rule taking a list of characters and returning its length counting double letters as one:
lengthWithDoubleLetters([],0).
lengthWithDoubleLetters([F,F|T],C) :-
lengthWithDoubleLetters(T,TC),
!,
C is TC + 1.
lengthWithDoubleLetters([H|T], C) :-
lengthWithDoubleLetters(T,TC),
C is TC + 1.

Resources