Password checking with prolog/2 - prolog

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.

Related

How to use the "-" constructor in Prolog?

So I need to create a Prolog predicate that takes an input that looks like this [true-X, false-Y, false-X, true-Z] and only return the variables that occur once. So for this example, it would return [true-Z] since Z only occurs once. I have been able to do this with just normal lists.
singles([],[]).
singles([H | T], L) :-
member(H, T),
delete(T, H, Y),
singles( Y, L).
singles([H | T], [H|T1]) :-
\+member(H, T),
singles(T, T1).
If I run this then it returns
?- singles([1,1,2,3,4,3,3,2], R).
R = [4]
since it only returns the values that appear once in the list. The problem with what I'm trying to do is that I can't use the member or delete predicates with the "-" constructor. Basically, I have to start by splitting each item into it's two parts and then just compare the variable singles([Pol-Var | T], L). To compare the two variables, I created an occurs predicate that compares the variable at the head of the list.
occurs(X, [Pol-Var|T]) :- X == Var.
Here's what I have so far.
singles([],[]).
singles([Pol-Var | T], L) :-
occurs(Var, T),
singles(T, L).
singles([Pol-Var | T], [Pol-Var|T1]) :-
\+occurs(Var, T),
singles(T, T1).
occurs(X, [Pol-Var|T]) :- X == Var.
What this does is basically like if I had the input [1,1,2,3,2] then the output would be [1,2,3,2] so it just removes any duplicates that are right beside eachother. So if I had the input [true-X, false-X, false-Y, true-Y, true-Z] then the output would be [false-X, true-Y, true-Z] and I want it to be [true-Z]. How can I do that?
As Daniel pointed out in his first comment, the real problem you're facing is the unwanted unification performed by Prolog between the arguments of such builtins like member/2 or delete/3. An old trick-of-the-trade of the Prolog community is to use double negation to achieve matching without unification, but as we'll see, this would not help you too much.
The simpler way to solve your problem, seems to rewrite member/2 and delete/3, so a possibility could be:
singles([],[]).
singles([H | T], L) :-
member_(H, T),
delete_(T, H, Y),
singles(Y, L).
singles([H | T], [H | T1]) :-
\+member_(H, T),
singles(T, T1).
member_(_-H, [_-T|_]) :- H == T, !.
member_(E, [_|R]) :- member_(E, R).
delete_([], _, []).
delete_([_-T|Ts], F-H, Rs) :- T == H, !, delete_(Ts, F-H, Rs).
delete_([T|Ts], H, [T|Rs]) :- delete_(Ts, H, Rs).
that yields
?- singles([true-X, false-Y, false-X, true-Z],S).
S = [false-Y, true-Z]
You can see you underspecified your requirements: from your test case, seems we should delete every occurrence of false-VAR irrespectively of VAR...

Count number of matching elements in two lists

I have 2 lists with random number of elemets. Eg A=[1,2,4,5] and B=[1,2,3]. Result should be 2.
Code that I tried:
domains
Numbers1 = integer*
Numbers2 = integer*
int_list=integer*
predicates
nondeterm prinadl(integer, int_list)
clauses
//here going the code that read number that I've entered, and according to entered numer,programm should do something
answer(T):- T=5,
P = 0,
write ("Enter the 1st list"), readterm (int_list, L),
write ("Enter the 2nd list"), readterm (int_list, L2),
L2 = [H|V], prinadl(H, L), P1 = P + 1,
write(L2, P1, V).
prinadl (X, L):- L=[X|_], !.
prinadl (X, L):- L=[_|T], prinadl (X, T).
I'm totally new with prolog. Can you please say me where I'm wrong? All I need is to get number of matches printed to the console.
Thanks in advance.
This answer is based on two things: first, guesswork. second, if_/3 by #false.
Let's define
the meta-predicate count_left_while2/4.
count_left_while2(P_2,Xs,Ys,N)
counts
the number N of corresponding list items in Xs and Ys fulfilling P_2. Proceeding from left to right, count_left_while2 stops at the first two items not satisfying P_2. It also stops when one list is empty, but the other one is not.
:- use_module(library(clpfd)).
:- meta_predicate count_left_while2(2,?,?,?).
count_left_while2(P_2,Xs,Ys,N) :-
N #>= 0,
list_list_countleft_while(Xs,Ys,N,P_2).
nil_or_cons([]).
nil_or_cons([_|_]).
:- meta_predicate list_list_countleft_while(?,?,?,2).
list_list_countleft_while([],Xs,0,_) :-
nil_or_cons(Xs).
list_list_countleft_while([X|Xs],Ys,N,P_2) :-
list_list_prev_countleft_while(Ys,Xs,X,N,P_2).
:- meta_predicate list_list_prev_countleft_while(?,?,?,?,2).
list_list_prev_countleft_while([],_,_,0,_).
list_list_prev_countleft_while([Y|Ys],Xs,X,N,P_2) :-
if_(call(P_2,X,Y),
( N0 #>= 0, N #= N0+1, list_list_countleft_while(Xs,Ys,N0,P_2) ),
N = 0).
Let's use it in combination with reified term equality predicate (=)/3, like this:
:- count_left_while2(=,[1,2,4,5],[1,2,3],N).
N = 2.

How to make program run in linear time?

So I would like to make this code to execute in linear time and I'm not sure what's making it take long than that so all help would be appriciated.
Sorry for not explaining earlier what the program does, medellangd is pretty much calculating the average length of the words sent in within the string.
medellangd([],0) :- !.
medellangd([Head1|[]], 0) :-
\+ check(Head1), !.
medellangd([Head1|[]], 1) :- !.
medellangd(Text, AvgLen) :-
medel(Text, X, Y),
AvgLen is X/Y.
medel([], 0, 0) :- !.
medel([Head1,Head2|Tail], Letters, Words) :-
check(Head1),
check(Head2), !,
medel([Head2|Tail], Letters1, Words),
Letters is Letters1 + 1.
medel([Head1|Tail], Letters, Words) :-
check(Head1),
medel(Tail, Letters1, Words1), !,
Letters is Letters1 + 1,
Words is Words1 + 1.
medel([_|T], Avg, Words) :-
medel(T, Avg, Words).
% We check that the input is a letter.
check(X) :-
(X >= 65, X =< 90);
(X >= 97, X =< 122).
Alright, seems like it was my method of checking it that was the problem, by using char-type on head instead of my check it solved my problem and I got i to run in linear time.

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

read line to atomic list in 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], '.'].

Resources