Remove item from multiple lists

82 Views Asked by At

I have a subproblem for which the solution is way larger than what I would suspect is necessary.

The problem is defined as follows

Remove X from all groups where group has id between Y and Z or A and B

expressed as a pseudo query it looks like this with Y,Z,A,B set to 0,1,3,4

remove(X, 
       [ period(0,1), period(3,4) ],
       [ 
          group(0, [ subgroup([_,_,X,_,_]), subgroup([X])]),
          group(1, [ subgroup([X])]),
          group(2, [ subgroup([_,_,X])]),
          group(3, [ subgroup([_,X,_])]),
          group(4, [ subgroup([X,_,_])])
       ], UpdatedGroups).

The result will be

UpdatedGroups = [ 
    group(0, [ subgroup([_,_,_,_]), subgroup([])]),
    group(1, [ subgroup([])]),
    group(2, [ subgroup([_,_,X])]),
    group(3, [ subgroup([_,_])]),
    group(4, [ subgroup([_,_])])
]

So, my solution to this is:

While start of current period is less than or equal end of current period, do removal of X in groups, while "incrementing" start of day. Repeat until no more periods

The removal of X in groups is done by "looping" all groups and check if it matches the period, and if it does remove the user from subgroups, which again is done by "looping".

This is a very tedious but straight forward solution, now my problem is that I quite often find myself doing stuff like this, and cannot find approaches to do this in a less comprehensive way.

Are there other approaches than mine that does not cover 50+ lines?


Updated

Thanks a lot, the code became so much cleaner - it might go further, but now it is possible to actually post here (this is modified a bit - but the logic is there)

inPeriods(Day, [ period(Start,End) | _ ]) :- between(Start,End, Day).
inPeriods(Day, [ _ | RemainingPeriods ]) :- inPeriods(Day, RemainingPeriods).

ruleGroupsInPeriod(Periods, rulegroup(Day,_)) :- inPeriods(Day, Periods).

removeUserFromRelevantRuleGroups(UserId, Periods, RuleGroups, UpdatedRuleGroups) :-
    include(ruleGroupsInPeriod(Periods), RuleGroups, IncludedRuleGroups).
    exclude(ruleGroupsInPeriod(Periods), RuleGroups, ExcludedRuleGroups),
    maplist(updateRuleGroup(UserId), IncludedRuleGroups, UpdatedIncludedRuleGroups)
    append(UpdatedIncludedRuleGroups, ExcludedRuleGroups, UpdatedRuleGroups).

updateRuleGroup(UserId, rulegroup(X, RuleList), rulegroup(X, UpdatedRuleList)) :-
    maplist(updateRule(UserId), RuleList, UpdatedRuleList).

updateRule(UserId, rule(X, UserList), rule(X, UpdatedUserList)) :-
    delete(UserList, UserId, UpdatedUserList).
1

There are 1 best solutions below

1
On BEST ANSWER

Yes.

The pattern you describe is very common, and all serious Prolog systems ship with powerful meta-predicates (i.e., predicates whose arguments denote predicates) that let you easily describe this and many other common situations in a flexible manner, using at most a few simple additional definitions for your concrete relations.

Richard O'Keefe's proposal for An elementary Prolog Library contains the descriptions and implementations of many such predicates, which are becoming increasingly available in all major Prolog implementations and also in the Prologue for Prolog.

In particular, you should study:

  • include/3
  • exclude/3
  • maplist/[2,3]

Note though that many of the described predicates are impure in the sense that they will destroy declarative properties of your code. In contrast to true relations, you won't be able to use them in all directions while preserving logical soundness.

Exercise: Which of the three predicates mentioned above, if any, preserve when everything else is pure, and which, if any, do not?