g | x | w | all
Bytes Lang Time Link
148Python 3.8 prerelease241216T034255ZV_R
014Jelly241129T135823ZJonathan
092R241204T015329ZEonema
02205AB1E241128T101604ZKevin Cr
016Nekomata241205T034859Zalephalp
116R241204T023158ZEonema
156Python3241128T153833ZAjax1234
038Charcoal241128T064452ZNeil
086JavaScript V8241127T213129ZArnauld
076JavaScript V8241128T053927Zl4m2

Python 3.8 (pre-release), 148 bytes

If we accept a comma-separated list of tuples as output, and not only a comma-separated list of strings:

import itertools as i
f=lambda n:[s for s in i.product(*["nsew"]*n)if not(-1 in(l:=[s[:x].count("n")-s[:x].count("s")for x in range(n+1)])or l[-1])]

Try it online!

Explanation

We first generate every possible tuple of length n consisting of one-character strings from "nsew":

[s for s in i.product(*["nsew"]*n)]

We check the number of "n" characters minus the number of "s" characters in each of s[:1], s[:2], ... , s[:n]:

l:=[s[:x].count("n")-s[:x].count("s")for x in range(n+1)]

l contains -1 at the first point where the number of south steps is greater than the number of north steps, if that ever happens. The final item in l is not 0 (i.e. it is True) if the number of north and south steps is unbalanced in the whole tuple. We check that neither of these conditions occurs:

(-1 not in l) and (not l[-1])

which is golfed slightly as:

not(-1 in l or l[-1])

Jelly,  16 15  14 bytes

4ṗµı*ÄḞoṂṪƊµÐḟ

A monadic Link that accepts a non-negative integer and yields a list of the walks as a list of lists of integers:

E  1
N  2
W  3
S  4

Try it online! Or see the test-suite (runs for [0..input]; reformats using ENWS and sorts walks).

How?

4ṗµı*ÄḞoṂṪƊµÐḟ - Link: WalkLength
4ṗ             - four Cartesian power {WalkLength} -> PotentialWalks
  µ        µÐḟ - discard those PotentialWalks for which:
   ı           -   i (square root of -1)
    *          -   exponentiate {PotentialWalk} (vectorises) -> Directions
     Ä         -   cumulative sums {Directions} -> Locations
          Ɗ    -   last three links as a monad - f(Locations):
      Ḟ        -     real part -> DistancesNorth
        Ṃ      -     minimum {DistancesNorth} -> MinDistanceNorth
       o       -     {DistancesNorth} logical OR {MinDistanceNorth} (vectorises)
         Ṫ     -   tail

R, 121 116 92 bytes

-5 thanks to @pajonk:

Additional -24 thanks to @pajonk by using 0123 for NESW

f=\(n,w={}){q=sum(Re(1i^w))
if(!n&!q)cat(w,"
",sep="")
if(n&q>=0)Map(\(d)f(n-1,c(w,d)),0:3)}

Attempt This Online!

Expanded:

# recursive function
f <- function(n, w=integer()) {
  # calculate net N-S position: 1i^w converts to complex, Re takes 
  #   N-S component
  q <- sum(Re(1i^w))
  # if at end of walk and at 0 N-S position
  if(!n & !q)
    # then print out the walk
    cat(w,"\n",sep="")
  # if not at end of walk and not in the ocean
  if(n&q>=0)
    # try walking in each direction (recursive case)
    Map(
      function(d) {
        f(n - 1, c(w, d))
      },
      0:3
    )
  }

# recursive function:
f <- function(n, w={}) {
  # net N-S position
  q <- sum(w=="N")-sum(w=="S")
  # if at end of walk and at 0 N-S position
  if(!n & !q)
    # then print out the walk
    cat(w, "\n", sep="")
  # if not at end of walk and not in the ocean
  if(n & q >= 0)
    # then for each direction
    lapply(c("E","N","S","W"),
      # try walking that way
      function(d) f(n - 1, c(w, d))
    )
}

05AB1E, 25 22 bytes

'€ÃIãʒ„ewмÇÈ·<ηO¤_sd`P

Outputs in lowercase.

Try it online.

Explanation:

'€Ã                   '# Push dictionary string "news"
   Iã                  # Cartesian product with the input
     ʒ                 # Filter this list by:
      „ewм             #  Remove all "e"s and "w"
          Ç            #  Convert the characters to codepoint-integers
           È·<         #  IsEven *2 -1 (North is now 1; South is -1)
              η        #  Get the prefixes
               O       #  Sum each prefix
                ¤      #  Get the last sum (without popping the list)
                 _     #  Check whether it equals 0
                       #  (aka there are an equal amount of "n" and "s")
                  s    #  Swap to get the list of cumulative sums again
                   d   #  Check for each whether it's non-negative (>=0)
                       #  (aka for every "s" there should have been at least one
                       #  preceding "n")
                    `  #  Dump all those >=0 checks to the stack
                     P #  Take the product of the stack to verify all are truthy
                       # (after which the filtered list is output implicitly)

See this 05AB1E tip of mine (section How to use the dictionary?) to understand why '€Ã is "news".

Nekomata, 16 bytes

ᵐįŋᵖ{∫ŤlƆž≥}→ᵐɗH

Attempt This Online!

Use , , ÷, ¢ (characters code 12, 21, 10, 1 in Nekomata's custom encoding) to represent N, W, S, E respectively.

ᵐįŋᵖ{∫ŤlƆž≥}→ᵐɗH        Take n=4 as an example
ᵐį                  Find an n-tuple of [0,1] and [1,0]
                        One possible result: [[0,1],[1,0],[1,0],[0,1]]
  ŋ                 Optionally negate some elements
                        One possible result: [[0,1],[1,0],[-1,0],[0,-1]]
   ᵖ{      }        Check that:
     ∫                  Cumulative sum
                            [[0,1],[1,0],[-1,0],[0,-1]] -> [[0,1],[1,1],[0,1],[0,0]]
      Ť                 Transpose
                            [[0,1],[1,1],[0,1],[0,0]] -> [[0,1,0,0],[1,1,1,0]]
       l                Last element
                            [[0,1,0,0],[1,1,1,0]] -> [1,1,1,0]
        Ɔ               Split into the last element and the rest
                            [1,1,1,0] -> [1,1,1],0
         ž              Check that the last element is 0
                            [1,1,1],0 passes the check
          ≥             Check that every element in the rest is not less than the last element
                            [1,1,1],0 passes the check
                        So [[0,1],[1,0],[-1,0],[0,-1]] passes the check
            →       Increment each element
                        [[0,1],[1,0],[-1,0],[0,-1]] -> [[1,2],[2,1],[0,1],[1,0]]
             ᵐɗ     Convert each element from decimal digits to a number
                        [[1,2],[2,1],[0,1],[1,0]] -> [12,21,1,10]
               H    Convert to characters
                        [12,21,1,10] -> "←∙¢÷"

By default, Nekomata outputs all valid solutions.


Or 11 bytes if we can output a list of [0,1],[0,-1],[1,0],[-1,0] instead of a string:

ᵐįŋᵖ{∫ŤlƆž≥

Attempt This Online!


Another interesting approach is to find all strings of matching parentheses with length 2n+2, remove the first and last parentheses, and replace each pair of parentheses with a direction. I can't find a short way to do this in Nekomata, but this may be helpful for some other languages.

R, 121 116 bytes

-5 thanks to @pajonk:

Without recursion this time. Funnily enough, it also ended up as 121 bytes.

\(n)Map(\(i,w=i%/%4^(1:n-1)%%4,s=cumsum(Re(1i^w)))if(!any(s<0,s[n]))cat(c("N","E","S","W")[w+1],"
",sep=""),1:4^n-1)

Try it online! (Note- TIO doesn't yet support \ for function.)

Expanded (before edits):

function(n) {
  lapply(
    # for all numbers 1 to 4^n
    1:4^n-1, 
    function(
        i,
        # convert to base 4, as a numeric vector
        w=i%/%4^(1:n-1)%%4,
        # calculate the north-south position at each step
        s=cumsum(Re(1i^w))
      ) {
      # if always on land and ends up at the same point
      if (all(s >= 0) & !s[n])
        # lookup the letters for the directions, concatenate and print
        cat(c("N","E","S","W")[w+1], "\n", sep="")
    }
  )
}

Python3, 156 bytes

C=str.count
def f(n):
 q=['']
 for i in q:
  if len(i)==n:
   if C(i,'N')==C(i,'S'):yield i
  else:
   for j in'NSEW':
    if C(U:=i+j,'N')>=C(U,'S'):q+=[U]

Try it online!

Charcoal, 38 bytes

Nθ⊞υωFυ«≔⁼№ιN№ιSη¿‹LιθF✂SNWEη⊞υ⁺ικ¿η⟦ι

Try it online! Link is to verbose version of code. Explanation:

Nθ

Input n.

⊞υωFυ«

Start a breadth-first search for walks of length n.

≔⁼№ιN№ιSη

See if this walk ends at the seaside.

¿‹Lιθ

If this walk is not long enough, then...

F✂SNWEη

... for each valid next step...

⊞υ⁺ικ

... add that step to the walk and add that walk to the search list.

¿η⟦ι

Otherwise if the walk ends at the seaside then output that walk.

By comparison, generating all walks of length n and filtering out those that step into the sea or do not end up at the seaside takes 39 bytes even on ATO and technically does not generate the correct output for n=0 (it outputs nothing instead of a single empty string but you can't tell that the cursor is in the wrong place).

JavaScript (V8), 86 bytes

-4 thanks to @l4m2

n=>{for(i=4**n;i--;t||print(s))for(s=t="",j=n;j--;t-=t<0||~-q%2)s+="NESW"[q=i>>j*2&3]}

Try it online!

Commented

n => {                 // n = input
  for(                 // outer loop:
    i = 4 ** n;        //   start with i = 4 ** n
    i--;               //   stop when i = 0
    t || print(s)      //   if t = 0, print s
  )                    //
    for(               //   inner loop:
      s =              //     s = Nice-walk string
      t = "",          //     t = north/south counter
      j = n;           //     start with j = n
      j--;             //     stop when j = 0
      t -=             //     update t:
        t < 0 ||       //       -1 if it's already negative
        ~-q % 2        //       otherwise: +1 for north, -1 for south,
                       //       unchanged for east and west
    )                  //
      s += "NESW"[     //     update s:
        q = i >> j * 2 //       the character index q is obtained by
            & 3        //       extracting the next 2 bits from i
      ]                //       according to j
}                      //

JavaScript (V8), 76 bytes

f=(n,y=1,s='',i=2)=>y*n--?[...'ENWS'].map(c=>f(n,i--%2+y,s+c)):--y||print(s)

Try it online!