How to count and sum recursively in Prolog - recursion

I'm trying to figure out how to use recursion on count and sum rules.
I usually do it with lists, using findall and length or findall and sum_list, but I'm not sure if that's my best option on all cases.
This is my approach with lists:
%person(name, surname, age)
person('A', 'H', 22).
person('B', 'G', 24).
person('C', 'F', 20).
person('D', 'E', 44).
person('E', 'D', 45).
person('F', 'C', 51).
person('G', 'B', 40).
person('H', 'A', 51).
count_person(Total_count) :- % rule to count how many person are.
findall(N, person(N, _, _), List),
length(List, Total_count).
sum_ages(Total_sum) :- % rule to sum all the ages.
findall(Age, person(_, _, Age), List),
sum_list(List, Total_sum).
or here: https://swish.swi-prolog.org/p/cswl.pl
How should I do this using recursion?

You should take a look at library(aggregate).
For instance:
count_person(Total_count) :-
aggregate(count, A^B^C^person(A,B,C), Total_count).
or the simpler form (try to understand the difference, it's a a good way to learn the basic about variables quantification)
count_person(Total_count) :-
aggregate_all(count, person(_,_,_), Total_count).
The library has grown out of the necessity to simplify the implementation of typical aggregation functions available in SQL (since Prolog is relational at heart):
sum_ages(Total_sum) :-
aggregate(sum(Age), A^B^person(A,B,Age), Total_sum).
You can also get combined aggregates in a step. Average is readily implemented:
ave_ages(Ave) :-
aggregate(t(count,sum(Age)), A^B^person(A,B,Age), t(Count,Sum)), Ave is Sum/Count.
If you implement using count_person/1 and sum_ages/1 the interpreter would scan twice the goal...

I do not have an elegant solution. But with retract and assert you can control the recursion:
:- dynamic([person/3,person1/3]).
count_person(N) :-
count_person(0,N).
count_person(Acc,N) :-
retract(person(A,B,C)),
!,
assert(person1(A,B,C)),
N1 is Acc+1,
count_person(N1,N).
count_person(N,N) :-
clean_db.
clean_db :-
retract(person1(A,B,C)),
assert(person(A,B,C)),
fail.
clean_db.

Related

Find which sum of any numbers in an array equals amount

I have a customer who sends electronic payments but doesn't bother to specify which invoices. I'm left guessing which ones and I would rather not try every single combination manually. I need some sort of pseudo-code to do it and then I can adapt it but I'm not sure I can come up with a good algorithm myself. . I'm familiar with php, bash, and python but I can adapt.
I would need an array with the following numbers: [357.15, 223.73, 106.99, 89.96, 312.39, 120.00]. Those are the amounts of the invoices. Then I would need to find a sum of any combination of two or more of those numbers that adds up to 596.57. Once found the program would need to tell me exactly which numbers it used to reach the sum so I can then know which invoices got paid.
This is very similar to the Subset Sum problem and can be solved using a similar approach to the typical brute-force method used for that problem. I have to do this often enough that I keep a simple template of this algorithm handy for when I need it. What is posted below is a slightly modified version1.
This has no restrictions on whether the values are integer or float. The basic idea is to iterate over the list of input values and keep a running list of every subset that sums to less than the target value (since there might be a later value in the inputs that will yield the target). It could be modified to handle negative values as well by removing the rule that only keeps candidate subsets if they sum to less than the target. In that case, you'd keep all subsets, and then search through them at the end.
import copy
def find_subsets(base_values, taget):
possible_matches = [[0, []]] # [[known_attainable_value, [list, of, components]], [...], ...]
matches = [] # we'll return ALL subsets that sum to `target`
for base_value in base_values:
temp = copy.deepcopy(possible_matches) # Can't modify in loop, so use a copy
for possible_match in possible_matches:
new_val = possible_match[0] + base_value
if new_val <= target:
new_possible_match = [new_val, possible_match[1]]
new_possible_match[1].append(base_value)
temp.append(new_possible_match)
if new_val == target:
matches.append(new_possible_match[1])
possible_matches = temp
return matches
find_subsets([list, of input, values], target_sum)
This is a very inefficient algorithm and it will blow up quickly as the size of the input grows. The Subset Sum problem is NP-Complete, so you are not likely to find a generalized solution that will work in all cases and is efficient.
1: The way lists are being used here is kludgy. If the goal was to simply find any match, the nested lists could be replaced with a dictionary, and we could exit right away once a match is found. But doing that will cause intermediate subsets that sum to the same value to also map to the same dictionary slot, so only one subset with that sum is kept. Since we need to report all matching subsets (because the values represent checks and are presumably not fungible even if the dollar amounts are equal), a dictionary won't work.
You can use itertools.combinations(t,r) to list all combinations of r elements in array t.
So we loop on the possible values of r, then on the results of itertools.combinations:
import itertools
def find_sum(t, obj):
t = [x for x in t if x < obj] # filter out elements which are too big
for r in range(1, len(t)+1): # loop on number of elements
for subt in itertools.combinations(t, r): # loop on combinations of r elements
if sum(subt) == obj:
return subt
return None
find_sum([1,2,3,4], 6)
# (2, 4)
find_sum([1,2,3,4], 10)
# (1, 2, 3, 4)
find_sum([1,2,3,4], 11)
# none
find_sum([35715, 22373, 10699, 8996, 31239, 12000], 59657)
# none
Rounding errors:
The code above is meant to be used with integers, rather than floats.
To use with floats, replace the test sum(subt) == obj with the more forgiving test sum(subt) - obj < 0.01.
Relevant documentation:
itertools.combinations

How to remove common elements from both lists in python3.6?

If
L1=[2,4,6,8,2,4,6,8]
L2=[1,3,2,2,4]
then after performing the operation my result should be:
L1=[6,8,4,6,8]
L2=[1,3]
The operation should remove elements present in common in both List1 and List2. Tell me a method to do this.
For less complexity I suggest:
uniq = set(L1).intersection(L2)
L1_uniq = [x for x in L1 if x not in uniq]
L2_uniq = [x for x in L2 if x not in uniq]
L1_unique=[i for i in L1 if i not in L2]
L2_unique=[i for i in L2 if i not in L1]
This is called list comprehension, which is a very useful feature of python. It makes use of for loop, which can be expressed explicitly as:
L1_unique=[]
for i in L1:
if i not in L2:
L1.append(i)
Which is equivalent to a double for loop:
for i in L1:
for j in L2:
if i==j:
break
else:
L1_unique.append(i)
As the other answer presented (and I voted up), having a set based on the intersect of the two lists before list comprehension can reduce time complexity, because it ultimately reduces number of searches in the second list. (You can simply run %%timeit to see if you use IPython)
In principle, you may want to modify the structure of the second list, so that you do not have to traverse the entire list in case of unsuccessful search . But I doubt if it can be faster than list comprehension in practice.

Delete all duplicated elements in a vector in Julia 1.1

I am trying to write a code which deletes all repeated elements in a Vector. How do I do this?
I already tried using unique and union but they both delete all the repeated items but 1. I want all to be deleted.
For example: let x = [1,2,3,4,1,6,2]. Using union or unique returns [1,2,3,4,6]. What I want as my result is [3,4,6].
There are lots of ways to go about this. One approach that is fairly straightforward and probably reasonably fast is to use countmap from StatsBase:
using StatsBase
function f1(x)
d = countmap(x)
return [ key for (key, val) in d if val == 1 ]
end
or as a one-liner:
[ key for (key, val) in countmap(x) if val == 1 ]
countmap creates a dictionary mapping each unique value from x to the number of times it occurs in x. The solution can then be easily found by extracting every key from the dictionary that maps to val of 1, ie all elements of x that occur precisely once.
It might be faster in some situations to use sort!(x) and then construct an index for the elements of the sorted x that only occur once, but this will be messier to code, and also the output will be in sorted order, which you may not want. The countmap method preserves the original ordering.

Prolog having trouble with recursion and LPN exercise

I need to do LPN practical 3 of section 3.4: writing a travel/3 rule with which a route can be searched for. (link)
I currently have the following solution:
%% Base cases
travel(X,Y, go(X,Y)) :- byCar(X,Y).
travel(X,Y, go(X,Y)) :- byPlane(X,Y).
travel(X,Y, go(X,Y)) :- byTrain(X,Y).
%% Recursive cases
travel(X,Y, go(X,Z,G)) :- travel(X,Z,go(X,Z)), travel(Z,Y,go(Z,Y)).
With these rules, it will find a solution, but when handing it in, I keep getting a time limit error, meaning the program tries too many other things first, before figuring out the solution. Is there any way I could speed this up? If so, what in this code could I improve?
Thanks :)
Almost there, but you have to use G (which I named Rest here):
%% Base cases
travel(X,Y, go(X,Y)) :- byCar(X,Y).
travel(X,Y, go(X,Y)) :- byPlane(X,Y).
travel(X,Y, go(X,Y)) :- byTrain(X,Y).
%% Recursive cases
travel(X,Y, go(X,Z, Rest)):-
travel(X,Z, go(X,Z)), % you go from X to Z in one step
travel(Z,Y, Rest). % you go from Z to Y in any # of steps
Example:
travel(singapore,raglan,R).
R = go(singapore, auckland,
go(auckland, hamilton,
go(hamilton, raglan)))
From here, doing the 4 is fairly easy, you just have to add the how:
%% Base cases
travel(X,Y, go(X,Y,car)) :- byCar(X,Y).
travel(X,Y, go(X,Y,plane)) :- byPlane(X,Y).
travel(X,Y, go(X,Y,train)) :- byTrain(X,Y).
%% Recursive cases
travel(X,Y, go(X,Z,Move,Rest)):-
travel(X,Z, go(X,Z,Move)),
travel(Z,Y, Rest).
Example:
travel(singapore,raglan,R).
R = go(singapore, auckland, plane,
go(auckland, hamilton, car,
go(hamilton, raglan, car)))

Python/Sage: Skip a specific combination in nested for-loop

while writing a somewhat simple function in sage I encountered a Problem:
I want to skip a specific combination, the case where both variables are the same. In short i only need the Variable-Combinations AB, BC and CA.
for Ax in [A, B, C]:
for Bx in [A, B, C]:
if Ax==Bx:
continue??
else:
do stuff
I have tried and tried whatever came to my mind, but it always had the same Error. The loop contains a system of Equations that won't work with two identical Variables.
Thanks in Advance for any help, it is very appreciated.
Use itertools.combinations:
import itertools as IT
for Ax, Bx in IT.combinations(['A','B','C'], 2):
print(Ax, Bx)
yields
('A', 'B')
('A', 'C')
('B', 'C')

Resources