g | x | w | all
Bytes Lang Time Link
122JavaScript Node.js240808T061927ZAndrew B
134Haskell220625T143014Zmatteo_c
010Husk210823T113708ZDominic
147BBC BASIC220617T124522ZAlice F
097C gcc210711T105300ZNoodle9
01405AB1E210711T080444Zhyper-ne
00905AB1E210823T095349ZKevin Cr
223brainfuck210713T201233ZNitrodon
019APL Dyalog Extended210711T072344ZAdá
014Vyxal j210712T071049Zemanresu
011Japt R210711T152135ZShaggy
070Python 2210711T065054Zdingledo
110Excel210711T170636ZAxuary
nanJ210711T064944ZJonah
008Jelly210711T150403Zlynn
011Jelly210711T062406Zhyper-ne
078JavaScript ES7210711T101345ZArnauld
073Perl 5210711T111447ZKjetil S
076Julia 1.0210711T114932ZMarcMush
025Charcoal210711T101630ZNeil
060Retina 0.8.2210711T095538ZNeil
075Factor210711T090740Zchunes
016Vyxal j210711T062520ZWasif
098Python 3.8 prerelease210711T063719ZWasif
1249brainfuck210711T065834Zhyper-ne
019Pyth210711T065134ZLeaky Nu
105Python 3210711T064516Zhyper-ne
013Jelly210711T062750ZLeaky Nu

JavaScript (Node.js), 122 bytes

_=>{q=(x,p=x-1)=>x-1?(p>1)?((x%p)&&q(x,p-1)):1:0
for(s='',k=i=1;i<41;i++,s+=`
`)for(j=1;j<=i;j++)s+='-|'[q(k++)]
return s}

Try it online!

Haskell, 134 bytes

[]#n=[]
a#n=take n a:drop n a#(n+1)
main=putStrLn$foldl(\a b->a++'\n':b)"-"$["|-"!!fromEnum(0`elem`map(x`mod`)[2..x-1])|x<-[2..820]]#2

Attempt This Online!

Husk, 10 bytes

Cḣ40mȯc¬ṗN

Try it online! (but you won't be able to easily view the output of non-printable characters) or Try this online instead to see the output switched to 0 and 1 characters.

Derived independently to Kevin Cruijssen's 05AB1E answer, but it turns-out to use almost exactly the same approach... Unfortunately Husk's prime-checker returns the index in the list of primes, rather than 1 or 0, costing one extra byte.

    mȯ       # map over
         N   # the infinite list of integers:
      c      # character corresponding to
       ¬     # logical NOT of
        ṗ    # index in list of primes;
C            # now cut into substrings
 ḣ40         # with lengths of 1..40

BBC BASIC, 147 bytes

L=820
T=1
C=0
F.N=1TOL
IFFNF(N-1)^2MODN=0TH.P."-";:EL.P."|";:ENDIF
C+=1
IFC=T TH.
T+=1
C=0
P.';
ENDIF
N.N
END
DEF FNF(A)IFA=1ORA=0TH.=1
=A*FNF(A-1)

I won't lie, this is definitely not the best algorithm, but I did at least golf it down as much as I could.

C (gcc), 101 \$\cdots\$ 98 97 bytes

p;r;d;f(n){for(n=r=0;++r<41;puts(""))for(;r*-~r>2*n;printf(L"-|"+p))for(p=d=n++;d>1;p=n%d--&&p);}

Try it online!

Saved a byte (and got below \$100\$!) thanks to ceilingcat!!!
Saved another byte thanks to Shaggy!!!
Saved yet another byte from Juan Ignacio Díaz!!!

05AB1E, 14 bytes

A820ÅTüŸ€¦pèJ»

Try it online!

-1 byte thanks to ovs

A820ÅT>üŸ€¦pèJ»  Full Program
A                Push "abc...xyz"
 820             Push 820
    ÅT           Pop 820; Push [0, 1, 3, 6, 10, ...] (triangular numbers)
      üŸ         Pairwise range; get [[0, 1], [1, 2, 3], [3, 4, 5, 6], ...]
        €¦       For each sublist, remove the first element
          p      Primality; get the 2D list of 1s and 0s
           è     Index into "abc...xyz"
            J    Join each row into a string
              »  Join the result into a multiline string

05AB1E, 9 bytes

∞40L£pçJ»

Uses unprintables \0 and \1 instead of - and |.

Try it online.

Explanation:

∞         # Push an infinite positive list: [1,2,3,...]
 40L      # Push list [1,2,3,...,38,39,40]
    £     # Split the first list into parts with the lengths of the second list
          # (removes any unused trailing items): [[1],[2,3],[4,5,6],...]
     p    # Check for each integer whether it's a prime (1 if it's a prime; 0 otherwise)
      ç   # Convert it to a character with this codepoint: \0 and \1
       J  # Join each inner list together to a single string
        » # And join those strings by newlines
          # (after which the result is output implicitly)

brainfuck, 223 bytes

-[>-<-----]>[>++++[<[[->>>+<<<]>]<+>>>-]<-]>>-[--->+<]>[<<<<<[>+[[>]>]>>[->+>]>[[-<+>]<<[-<<<[[<]<]>>[-]-[[>]>]>]+>>>>]<<<<<<<[[<]<]<]>+[->>>]<[-<+>]<-]<<<+<+<--[>[[[-<<<+>>>]>]<<-[---<+>]<.[-]<+<-]++++++++++.[-]>[-<+>]+<<]

Try it online!

Uses trial division up to 86 to determine every prime up to 820. Outputs T for - and V for |.

Populate list of 820 numbers not yet considered composite
-[>-<-----]>[>++++[<[[->>>+<<<]>]<+>>>-]<-]

tape is 1 0 0 1 0 0 1 etc 1 0 0 1 0 (0)

Create 85 to test visibility by up to N=86 (which is high enough and short to create)
>>-[--->+<]>

working tape will be 1 0 0 1 0 0 1 etc 1 1 0 1 1 0 (0 or 1) x y 0 0
with x plus y = 85 in first iteration

For all numbers from 85 down to 1: (N is this number plus one)
[

  For all numbers to sieve:
  <<<<<[

    Mark number seen
    >+

    Decrement x and increment y if x is nonzero
    [[>]>]>>[->+>]

    Otherwise, the last number was divisible by N
    >[

      Move y back to x
      [-<+>]

      If this isn't the first time we've seen a multiple of N:
      <<[

        Mark number composite
        -<<<[[<]<]>>[-]-[[>]>]>

      Mark that we've seen a multiple of N
      ]+>>>>

    ]

    Go to next number (for loop termination condition)
    <<<<<<<[[<]<]<

  Repeat until all 820 numbers checked
  ]

  Remove all "seen" markings and "seen multiple of N"
  >+[->>>]

  Copy whatever was left of y back to x and decrement
  <[-<+>]<-
]

Now all numbers have been checked and marked composite if applicable
We are 5 cells right of first number (1) now

Set up counters for triangle shape (x=1 for first row; and y=1 so y is always nonzero)
<<<+<+

Mark 1 as nonprime
<--

For each number
[

  While x is nonzero
  >[

    Move x and y left three cells
    [[-<<<+>>>]>]

    Add 85 to primality indicator to output ASCII
    <<-[---<+>]

    Output and remove number
    <.[-]

    Increment y and decrement x
    <+<-

  ]

  Output newline
  ++++++++++.[-]

  Copy y to x (now one more than it was; restore y=1)
  >[-<+>]+

<<]

APL (Dyalog Extended), 23 19 bytes

‒1 thanks to Razetime.

Full program.

↑⎕UCS{1⍭⍳≢⍵}⍢∊,\⍳40

Try it online!

⍳40indices 1 through 40

,\ prefixes of that; [1,[1,2],[1,2,3],,39,40]]

{}⍢∊ apply the following anonymous lambda to the enlisted (flattened) data:

 the argument; [1,1,2,1,2,3,,39,40]

 the length of that; 820

1⍭ indicate which elements are prime; [0,1,1,0,1,0,,0,0]

⎕UCS convert to characters in the Universal Character Set; [\x00,\x01,\x01,\x00,\x01,\x00,,\x00,\x00]

 mix the list of strings into a matrix, padding with spaces as necessary to make it rectangular; ["\x00 ","\x01\x01 ",\x00\x00\x00\x00\x00\x00\x00\x00"]

Vyxal j, 11 14 bytes

40ƛɽ÷Ṡnɾ+æ₁+Cṅ

Try it Online!

I'm posting this because it's significantly shorter than the other one. I didn't realise the non-number rule, but still fairly short.

40ƛ            # Map 1...40 to...
   ɽ÷Ṡ         # Sum of 1...n-1 
      nɾ+      # Added to each of 1...n, this produces the correct indices
         æ     # Is prime (vectorised)
          ₁+C  # Add 100 and get the corresponding character (d or e)
             ṅ # Joined
               # (j flag) join by newlines

Japt -R, 13 11 bytes

Uses f for composites and t for primes.

#(õ_ç Ëî°Tj

Test it

#(õ_ç Ëî°Tj
#(              :40
  õ             :Range [1,40]
   _            :Map each Z
    ç           :  Z spaces (could be any character)
      Ë         :  Map each character
       î        :    Slice the following to length (always 1)
        °T      :      Prefix increment T (initially 0)
          j     :      Is prime? (Returns a Boolean which gets coerced to a string by î)
                :Implicit output joined with newlines

Japt -R, 13 bytes

Preserving this version as I quite liked the ú trick. Uses q for qomposites(!) and p for primes.

40õ_ç¬Ëú²g°Tj

Test it

40õ_ç¬Ëú²g°Tj
40õ_              :Map each Z in the range [1,40]
    ç             :  Repeat to that length
     ¬            :    "q"
      Ë           :  Map each character
       ú          :    Left pad
        ²         :    With "p" to length 2
         g        :    Get the character at index
          °Tj     :      As above, with the Boolean this time coerced to 0 or 1
                  :Implicit output joined with newlines

Python 2, 70 bytes

p=n=i=1
while i<41:s='';exec"s+='-|'[p%n];p*=n*n;n+=1;"*i;i+=1;print s

Try it online!

The |s mark the position of primes.

The idea here, as with many prime-related tasks in Python, is to use Wilson's theorem. Essentially, \$ (n - 1)!^2 \bmod n \$ will equal 1 if \$ n \$ is prime, and 0 if it is not. @xnor gives a more in-depth explanation here if you're interested.

Excel, 110 bytes

=LET(a,SEQUENCE(820),CONCAT(IF(MMULT((MOD(a,TRANSPOSE(a))=0)*1,a^0)=2,"|","-")&IF(MOD(SQRT(8*a+1),1),"","
")))

Link to Spreadsheet

=LET(a,SEQUENCE(820),                               a = (1..820)
CONCAT(                                             concatenate the following
IF(MMULT((MOD(a,TRANSPOSE(a))=0)*1,a^0)=2,"|","-")& if a is prime (has 2 factors) then "|" else "-"
IF(MOD(SQRT(8*a+1),1),"","                          if a is triangular (8a+1 is a perfect square) then add a line feed
")))

J, 29 26 25 bytes

({&'-|'/.1 p:#\)@(#~i.41)

Try it online!

-3 thanks to an idea from Lynn!

-1 thanks to xash!

Jelly, 8 bytes

ØYxJĠẒỌY

Try it online!

ØY         The string "BCDFGHJKLMNPQRSTVWXZbcdfghjklmnpqrstvwxz"
  xJ       Repeat "B" once, "C" twice, "D" thrice... "z" 40 times.
    Ġ      Group indices by values: [[1], [2,3], [4,5,6], …, […820]]
                                     ↑B   ↑C     ↑D          ↑z
     Ẓ     Is prime?
      Ọ    Translate 0,1 into "\x00","\x01"
       Y   Join lines

Jelly, 11 bytes

40RRȷR¤ṁẒỌY

Try It Online!

The |s are at prime indexes

I figured out the pattern by smash-printing the rows into one flat string and realizing that the distribution looked familiar :P I had another idea to use base conversion but I decided to flatten the string first and then noticed.

Leaky Nun has two tricks in their answer - firstly, that you can take the range up to 1000 (which is shorter) since mold will discard extras, and secondly, I didn't notice other characters were allowed, so the digraph nilad ØA is shorter. Jonathan Allan also noticed that we can just use chr and output the unprintables 0x00 and 0x01, so thanks to both of them for the byte saves. Please go upvote Leaky Nun's answer as well.

40RRȷR¤ṁẒỌY  Main Link (Niladic)
40           Start at 40
  R          Range; [1, 2, ..., 40]
   R         Range; [[1], [1, 2], [1, 2, 3], ...]
       ṁ     Mold; to the shape of [[1], [1, 2], [1, 2, 3], ...], reshape
    ȷR¤      Range of 1000 ([1, 2, 3, ..., 1000]) - extra elements are discarded
        Ẓ    x => isprime(x)
         Ọ   chr
          Y  Join on newlines

There are a couple of ways to go about this idea - you can swap which list you generate first, you can index into a multitude of other strings, you can prime-check before or after molding since it vectorizes and thus doesn't really care what shape it is, etc.

JavaScript (ES7), 78 bytes

A recursive function going from \$k=820\$ to \$k=1\$ and testing whether \$\sqrt{8k+1}\$ is an integer to detect triangular numbers and figure out where line-feeds have to be inserted.

f=(k=820)=>k?f(k-1)+'-|'[(g=n=>n%--d?g(n):+!~-d)(d=k)]+[`
`[(1+8*k)**.5%1]]:''

Try it online!

Perl 5, 73 bytes

say join($/,map$"x$_,1..40)=~s/./!$n++|(grep$n%$_==0,2..$n-1)?"-":"|"/ger

Try it online!

say                         # print triangle string, say adds a \n at the end
join($/,map$"x$_,1..40) =~  # make multi-line triangle
                            # of $" (spaces) and $/ (newlines)
s/ /                        # replace spaces
  !$p++                     # count position, ! prevent first from becoming "|"
  |                         # bit-or instead of || or 'or'
  (grep $p%$_==0, 2..$p-1)  # check if $p position is a prime
  ? "-"                     # replace non-prime $p with -
  : "|"                     # replace prime $p with |
/ger                        # g=replace all, e=eval, r=return triangle to say

Julia 1.0, 76 bytes

f(y=1)=1:40 .|>i->print.(y:(y+=i).|>j->"
-|"[j==y||2+(2==sum(j.%(1:j).<1))])

Try it online!

Charcoal, 25 bytes

G↓→⁴⁰ψ¤⭆⁸²⁰§-|﹪ΠE⊕ι∨λ±¹⊕ι

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

G↓→⁴⁰ψ

Draw an empty triangle of side 40. (Unfortunately Charcoal doesn't have a 1-byte shortcut for a triangle in this orientation.) The y variable is the null byte, which has a special meaning for the Fill function, in that it counts as a fillable area.

¤⭆⁸²⁰§-|﹪ΠE⊕ι∨λ±¹⊕ι

Calculate the first 820 factorials, negate them, reduce them modulo the incremented index, and index the results into the string -|. Use this to fill the triangle drawn above. (Charcoal's Fill works by painting the string across the region to be filled, rather than repeating it horizontally and vertically. See @ASCII-only's answer to Bake a slice of Pi for another example of how it works.)

Retina 0.8.2, 60 bytes


820$*|
.(?<=^\1+(..+))
-
^.
-¶
+`(.)+¶(?=.+$)(?<-1>.)+.
$&¶

Try it online! Explanation:


820$*|

Insert 820 |s. (820 = 40 × 41 ÷ 2)

.(?<=^\1+(..+))
-

Change most of them to -s appropriately.

^.
-¶

Change the first one too, and start building the triangle.

+`(.)+¶(?=.+$)(?<-1>.)+.
$&¶

Build up each row of the triangle to be one character longer than the previous.

Factor, 75 bytes

820 [1,b] [ prime? 43 45 ? ] ""map-as 40 [1,b] [ cut swap print ] each drop

Try it online!

Vyxal j, 16 bytes

40ɾƛʁƛkA¥›:£æi;ṅ

Try it Online!

-1 byte thanks to @hyper-neutrino

Replace all those characters with numbers from 1, 2 and so on. The |'s are at prime indices. So then it's easy.

(pxeger spoiled it, though I came to it independently)

Python 3.8 (pre-release), 98 bytes

r=range
for _ in r(n:=1,41):s="";exec("s+='-|'[all(n%y for y in r(2,n))and n>1];n+=1;"*_);print(s)

Try it online!

-6 bytes thanks to @hyper-neutrino and @pxeger

brainfuck, 1249 bytes

++++++++++[>+>++++>++++++<<<-]>>.<.>>..<<.>.>.<.<.>>.<...<.>>.<.>.<..<.>.>.<.>.<..<.>.>.<.....<.>>.<.>.<.....<.>>.<...>.<.>.<..<.>.>.<.....>.<..<.>...>.<.>.<.....<.>>.<...>.<.>.<.....<.>>.<...>.<.....>.<..<.>.....>.<...>.<.>.<..<.>.>.<.>.<...>.<.......<.>......>.<...>.<.....<.>>.<.>.<.........>.<.>.<..<.>...>.<.....>.<...>.<....<.>.>.<.....>.<.>.<.........<.>>.<.>.<...>.<.>.<...........<.>>.<...........>.<...>.<.>.<..<.>.>.<.....>.<.>.<.........>.<..<.>...>.<.....>.<.....>.<.>.<.....<.>>.<...>.<.>.<.........>.<.......<.>......>.<...>.<.>.<...>.<........<.>.....>.<.....>.<.........>.<.>.<..<.>.>.<.....>.<.......>.<.....>.<.....<.>>.<...>.<.....>.<.......>.<...>.<.....<.>..>.<.........>.<.>.<.........>.<.>.<..<.>...>.<...>.<.....>.<.......>.<...>.<.>.<..<.>.>.<...........>.<.......>.<...>.<.....<.>..>.<...>.<.....>.<...........>.<.>.<.....<.>............>.<.....>.<.........>.<....<.>.>.<.....>.<.>.<.....>.<.........>.<.....>.<..<.>...>.<.>.<.....>.<.....>.<...>.<.>.<...........<.>>.<.........>.<.>.<...>.<.....>.<.....>.<.>.<.....<.>......>.<...>.<.....>.<.......>.<.........>.<..<.>.....>.<.........>.<.......>.<.....>.<.....>.<..<.>.>.<.......>.<.....>.<...>.<.......>.<...>.<.......<.>......>.<.........>.<...........>.<.>.<.........

Try it online!

Kind of boring; just a hardcoded solution. I'm trying to create a solution that computes it with the prime relation, but I'm not that experienced in brainfuck and also I'll need to find an interpreter that goes up to 820 if I don't want excessive pain and suffering.

Pyth, 19 bytes

jmsm@GP_hkrZ=+ZdS40

Try it online!

Uses a and b instead of - and |.

Python 3, 105 bytes

r=range
for i in r(1,41):
	for j in r(i):n=i*~-i//2-~j;print(end="-|"[all(n%y for y in r(2,n))])
	print()

Try it online!

One byte longer than wasif's, but no exec trick used here.

Jelly, 13 bytes

ȷRẒṁ40RR¤ịØAY

Try it online!

Uses Z and A instead of - and |.

This is basically the same answer as @hyper-neutrino's answer, which was published 3 minutes before mine, but our answers arose independently.