Split list

This will do it, although it’s a bit ugly – by reordering the goals depending on whether S is a variable or not. (This can probably be done more elegantly using freeze/2 or when/2):

split_string_at_nth1(Nth,S,Start,End):-
    (   var(S)
    ->  string_codes(Start,SL),
        string_codes(End,EL),
        split_list_at_nth1(Nth,L,SL,EL),
        string_codes(S,L)
    ;   string_codes(S,L),
        split_list_at_nth1(Nth,L,SL,EL),
        string_codes(Start,SL),
        string_codes(End,EL)
    ).

split_list_at_nth1(Nth, Long, Start, End) :-
    length(Start, Nth),
    append(Start, End, Long).
?- split_string_at_nth1(3, "abcde", Start, End).
Start = "abc",
End = "de".

?- split_string_at_nth1(I, X, "abc", "de").
I = 3,
X = "abcde".
1 Like

Thank you very much. I am still figuring out the impact of goal ordering. This helped me a lot.

This goes off into infinity with:

?- split_list_at_nth1(N, [a,b,c], [a,b,c|T], End).
N = 3,
T = End, End = [] ;
ERROR: Stack limit (1.0Gb) exceeded

Here is a version which seems reasonable:

split_list_at_nth1(Nth1, Long, Start, End) :-
    (   nonvar(Nth1) -> must_be(nonneg, Nth1), Once = true
    ;   is_list(Long), (is_list(Start) ; is_list(End)) -> Once = true
    ;   is_list(End), is_list(Start) -> Once = true
    ;   true
    ),
    split_list_at_nth1_(Long, 0, Nth1, Once, Start, End).

split_list_at_nth1_(L, N, N, Once, [], L) :-
    (Once == true -> ! ; true).
split_list_at_nth1_([H|T], N, Nth1, Once, [H|Upto], End) :-
    N1 is N + 1,
    split_list_at_nth1_(T, N1, Nth1, Once, Upto, End).

… which produces:

?- split_list_at_nth1(N, [a,b,c], [a,b,c|T], End).
N = 3,
T = End, End = [].

This produces an unwanted choicepoint, which doesn’t seem worth the expected additional processing to guard against:

?- split_list_at_nth1(N, [a,b,c], St, [a,b,c|T]).
N = 0,
St = T, T = [] ;
false.

Edit: Removed unnecessary once.

1 Like