2

Say I have a unique list of length 9 of the values between 1 and 9 inclusive in a random order (think sudoku), and I want to extract a the sub-list of the items that occur between the values 1 and 9 (exclusive). IE: between1and9([1,3,5,4,2,9,7,8,6],[3,5,4,2]) should be true.

At the moment I'm trying to use flatten/2, but not having much luck. Here's my current tactic (assuming I enforce List ins 1..9, maplist(all_distinct, List), length(List, 9) elsewhere to keep it tidy here/seperation of concerns):

between1and9(List,Between) :-
  flatten([_,[1],Between,[9],_], List);
  flatten([_,[9],Between,[1],_], List).

This version fails though when 1 or 9 are at the first or last position in List, or if they're adjacent within List. between1and9([_,1,9,_,_,_,_,_,_],[]) is true, but between1and9([_,1,9,_,_,_,_,_,_],_) is false (and fails when I try to use it as a constraint to solve a bigger problem.)

It seems to be the same problem casuing both failures, flatten doesn't seem to like treating unknowns as empty lists unless they're made explicit somewhere.

I can see why that would potentially be, if flatten could "invent" empty lists in the first argument it would mean an infinite set of solutions for anything in the first argument. Although my full program has other constraints to prevent this, I can understand why flatten might not want to accomodate it.

I can account for the edge cases (pun intended) by matching every permutation with disjunctions (ie: flatten([_,1,B,9,_],L);flatten([_,9,B,1,_],L);flatten([_,1,B,9]);flatten..., And account for the Between as an empty list with: \*above permutations on flatten*\; ( Between = [], (\*permutations for either edge and 1/9*\) )

But that seems to be making an already longwinded solution (10 permutations of flatten in total) even worse (18) so I have two (strongly related) questions:

  1. If I could do the following:

    between1and9(L,B) :-
      ( ( X = 1, Y = 9 ); ( X = 9, Y = 1 ) ),
      ( ( Z1 = _; Z1 = [] ), ( Z2 = _ ; Z2 = [] ) ),
      ( B = _; B = [] ),
      flatten([Z1,X,B,Y,Z2],L).
    

    I wouldn't have to manually type out each permutation of match for flatten. Unfortunately this and a few variations on it all unilaterally fail. Am I missing somethign obvious here? (I suspect opperator precedence but I've tried a few different versions.)

  2. Or am I doing this completely wrong? The flatten/2 documentation suggests that in most cases it's an anti-pattern, is there a more prolog-ish* way to go about solving this problem? Given all the pitfalls I'm realising as I go through this I'm almost certain there is.

(Sorry, I'm painfully aware that a lot of the terminology I'm using to describe things in this is probably very wrong, I'm only kind of familiar with predicate/formal logic and much more used-to describing control flow type programming. Even though I understand logic programming in practice reasonably well I'm struggling to find the language to talk about it robustly yet, I will amend this question with any corrections I get.)

Some background: I'm new to prolog and testing out my understanding by trying to extend one of the many sudoku solvers to solve a strange variety of sudoku I found in some puzzles I printed out years ago where you're shown the sum of all the numbers that appear between the 1 and the 9 in any given row or column as an extra hint, it's kind of like a mix of sudoku and picross. The solver as it stands now is on swish: SumSudoku(swish). Although it may be a mess when you get to it.

*Corollary quesiton: is there a prolog version of the word "pythonic?"

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Tim Hope
  • 784
  • 1
  • 7
  • 17

1 Answers1

2

You could use good old append/3 for this. Is it possible that you wanted append/3 all along but somehow thought it is called flatten?

For the "1 comes before 9" case, you'd write:

between_1_and_9(List, Sublist) :-
    append(_, [1|Rest], List),
    append(Sublist, [9|_], Rest).

You need to swap 1 and 9 for the "9 comes before 1" case.

This also leaves a "spurious choice point" (Thank you @PauloMoura for the comment). Make sure to get rid of it somehow.

As for "Pythonic" (and this comes from a recovering Pythonista), I can only say, rest assured:

There is always more than one obvious way to do it in Prolog.

You don't even have to be Dutch.

User9213
  • 1,316
  • 6
  • 12
  • Ah that works! It makes life so much simpler. I hadn't forgotten about append, I think I just fell into the trap of trying to work with the whole list at once rather than slicing it in 2 places to define the sub-list. – Tim Hope Mar 29 '19 at 08:05
  • 1
    @TimHope The nice thing about `append/3` is that in many cases, "appending" and "splicing" are indeed the two sides of the same coin. I really wish we'd have a better word (in natural languages) to describe that kind of symmetric relationship. As it stands, predicates like `append/3` and `select/3` are actively hiding the true nature of their definitions. – User9213 Mar 29 '19 at 08:09
  • 1
    Note that this solution leaves a spurious choice-point. Easy to fix, however, assuming that the first argument is always bound as it seems to be the case. – Paulo Moura Mar 29 '19 at 08:20
  • @PauloMoura It does. Since I didn't even write the whole predicate (only half of it) I don't know where is the correct place to cut. I will edit the anwer. – User9213 Mar 29 '19 at 08:33