g | x | w | all
Bytes Lang Time Link
008Vyxal241116T005055Zemanresu
036Haskell + hgl241120T185245ZWheat Wi
nanScala 3241116T033427Z138 Aspe
189C# .NET Core241118T223647ZPepik
070APL+WIN241120T114624ZGraham
124C++ clang241118T182557Zioveri
022Pip r241119T192347ZDLosc
030Uiua241119T030549ZErikDaPa
050Perl 5 F241115T210837ZXcali
01205AB1E241118T085917ZKevin Cr
079Python + NumPy241116T073709ZAlbert.L
016Japt241115T173018ZShaggy
092Ruby241115T172048ZJordan
094R241117T045504ZEonema
079Ruby241117T004341ZLevel Ri
069JavaScript ES6241115T211329ZArnauld
122Python241115T181150Zmovatica
026Charcoal241115T170848ZNeil
018K ngn/k241115T224747Zovs
014Jelly241115T171040ZJonathan
097Retina241115T170507ZNeil

Vyxal, 8 bytes

C<v¦+3Ḋ∑

Try it Online!

I/O as a matrix of charcodes. Inspired by ovs's K answer (and then backported to that to save two bytes)

       ∑ # Over each line, sum the results of
C<       # For each number, check if it's not a space
C        # by checking if if casting it to a char
 <       # results in a larger value - ' ' < '3', '<'/'>' > '6'
  v¦     # Take the cumulative sum of these
    +    # Add to the original charcodes
     3Ḋ  # And check if it's divisible by 3

As to why this works, here's an example:

Haskell + hgl, 36 bytes

ca"1"<<tx<m(gkY$rX">{1_<>|<><{1_|.")

Attempt This Online!

Explanation

No parser, 37 38 bytes

cn by3<<tx<m(zwp^.m Or~<cna" "<<pxx)

Attempt This Online!

Explanation

Parser no regex, 47 bytes

ca"1"<<tx<m(gkY$on(hh=#+)cx3"><><>< ""1000010")

Attempt This Online!

Reflection

This is ok. I'm a little disappointed by the fact the parser-no-regex version is so much longer than the no parser version when the shortest solution is the regex version. I want parsers to be more efficient than they are. It would even help regex parser answers. I don't really have a way to fix the thing I am frustrated with, but I have some thoughts in general:

Scala 3, 178 161 bytes

Saved 17 bytes thanks to @corvus_192


Golfed version. Attempt This Online!

s=>{val z=s split "\n"flatMap(l=>"><>|<><".r.findAllMatchIn(l)map(m=>m.start+(if(m.matched=="<><")2 else 0)));if(z.isEmpty)z else(0to z.max)map(i=>z.count(i==))}

Ungolfed version. Attempt This Online!

import scala.util.matching.Regex

object Main {
  def main(args: Array[String]): Unit = {
    val inputString = """  ><>        ><>       ><>      ><>      ><>
<><    <><            ><>    <><    ><>
 ><>      ><>     ><><><        ><>><>   <><
    ><>  ><>        <><     ><>    <><
 ><>        ><>         ><>     ><>       ><>
><>  <><<><       ><>  <><><>        ><>   ><>
   ><>     ><>        ><>        ><>    <><
 ><>  ><>       <><        <><      ><>   ><>"""

    val positions = findPatternPositions(inputString)
    val result = countPositions(positions)
    val outputString = result.mkString("")
    println(outputString)
  }

  def findPatternPositions(s: String): List[Int] = {
    val pattern = "><>|<><".r
    s.split("\n").flatMap { line =>
      pattern.findAllMatchIn(line).map { m =>
        m.start + (if (m.matched == "<><") 2 else 0)
      }
    }.toList
  }

  def countPositions(positions: List[Int]): List[Int] = {
    if (positions.isEmpty) List()
    else {
      val maxPosition = positions.max
      (0 to maxPosition).map { i =>
        positions.count(_ == i)
      }.toList
    }
  }
}

C# (.NET Core), 189 bytes

int[]c(string f){var p=f.Split('\n');var t=new int[p.Max(s=>s.Length)];foreach(var x in p)for(int m=0;m<2;m++)foreach(Match n in Regex.Matches(x,m>0?"<><":"><>"))t[n.Index+m*2]++;return t;}

Try it online!

Full code:

    
using System;
using System.Text.RegularExpressions;

public class Program
{
    public static void Main()
    {
        string r = "<>< ><> ><>><>" + Environment.NewLine +
                          "  <>< <><     ><>    ><>" + Environment.NewLine +
                          " <>< <><" + Environment.NewLine;

        int[] c (string f)   
        {
            var p = f.Split('\n');
            int m = 0;

            foreach (var x in p)
                m = Math.Max(m, x.Length);

            var t = new int[m];

            foreach (var x in p)
            {
                foreach (Match n in Regex.Matches(x, "<><"))
                    t[n.Index + 2]++;
                foreach (Match n in Regex.Matches(x, "><>"))
                    t[n.Index]++;
            }
            
            return t;
        }
        
        Console.WriteLine(string.Join(",", c(r)));
    }
}



```

APL+WIN, 70 bytes

Prompts for school as a 2 dimension array.

m←((i←(⍴i)÷3),3)⍴i←(n←v≠' ')/v←,j←⎕⋄+⌿(⍴j)⍴n\,(m[;1]='>'),0,m[;,3]='<'

Try it online! Thanks to Dyalog Classic

C++ (clang), 191 124 bytes

[](auto&r,auto&v){for(auto&s:r)for(int i=0;i+2<s.size();i++)v.resize(max(v.size(),s.size())),s[i]>59?v[i+62-s[i]]++,i+=2:0;}

Try it online!

Reduction thanks to jdt and ceilingcat

Pip -r, 22 bytes

$+^gR`\S`*3{'<Q@a?Rhh}

Takes input from stdin; requires it to be padded to a rectangle with spaces. Attempt This Online!

Explanation

$+^gR`\S`*3{'<Q@a?Rhh}
   g                    ; List of lines of stdin (-r flag)
    R                   ; In each line, replace
     `\S`*3             ; regex match of three non-space characters (guaranteed
                        ; to be a fish because only well-formed fish are present)
           {         }  ; with this callback function:
            '<Q@a?      ;   If the fish starts with <
                  Rh    ;   Reverse of h (001)
                    h   ;   Else, h (100)
  ^                     ; Split into a 2D matrix of characters
$+                      ; Sum (treating spaces as 0s)

Uiua, 30 bytes

+@0/+⬚0⊜(=-:@>◿3+2\+≠@ .)⊸≠@\n

Try it online!, inspired by ovs's K solution

Explanation:

+@0/+⬚0⊜(=-:@>◿3+2\+≠@ .)⊸≠@\n
       ⊜(               )⊸≠@\n => split string by newlines
                    ≠@         => non-whitespace indexes
                +2\+           => accumulative addition starting at 2
              ◿3               => modulo 3
          -:@>                 => subtract 62 (>)
         =             .       => compare with original row
     ⬚0                        => fill 0s if needed
+@0/+                          => row addition and then stringify (optional)

Perl 5 -F, 57 50 bytes

s/(>)<>|<></$1?100:aa1/ge;s/./$;[pos]+=$&/ge}{say@

Try it online!

05AB1E, 12 bytes

ðÊ€ηOIÇ+3ÖøO

Similar as a bunch of other answers.

Takes the input as a list of lists of characters, including trailing spaces to make all lines the same length.

Try it online or verify all test cases.

Explanation:

ðÊ           # Check for each inner-most character in the (implicit) input-matrix
             # that it's NOT equal to a space " "
  €η         # Take the prefixes of each inner list of 0s/1s
    O        # Sum each inner-most prefix
     I       # Push the input-matrix again
      Ç      # Convert each character to its codepoint-integer
       +     # Vectorized add the values at the same positions
        3Ö   # Check for each whether it's divisible by 3
          ø  # Zip/transpose; swapping rows/columns
           O # Sum each inner list (the columns before the zip) together
             # (after which the resulting list of sums is output implicitly)

Python + NumPy, 79 bytes

lambda s:sum((cumsum((X:=c_[s].view("i4")%-8)<0,1)+X)%3>1,0)
from numpy import*

Attempt This Online!

Takes a list of rows which are strings.

Or

Python + NumPy, 77 bytes (@emanresu A)

assuming input lines are equal length.

lambda s:sum((cumsum((X:=c_[s].view("i4"))>32,1)+X)%3<1,0)
from numpy import*

Attempt This Online!

How?

Using numpy is convenient here, because the array constructor pads strings to equal length (with zeros, so normally you wouldn't necessarily notice). The view cast to integer does roughly the same as ord in standard python; it also reveals the padding.

Japt, 24 21 16 bytes

Takes input as a 2D array of codepoints.

Ëí+DËgHÃå+ÃÕËxv3

Try it

Ëí+DËgHÃå+ÃÕËxv3     :Implicit input of 2D array of codepoints
Ë                    :Map each D
 î+                  :  Interleave with, reducing each pair by addition
   DË                :    Map D
     g               :      Sign of difference with
      H              :        32
       Ã             :    End map
        å+           :    Cumulatively reduce by addition
          Ã          :End map
           Õ         :Transpose
            Ë        :Map
             x       :  Reduce by addition of
              v3     :    Divisible by 3?

Ruby, 92 bytes

Takes an array of lines as input.

->a{h=Hash.new 0
a.map{_1.scan(/(><>)|<></){h[$`.size+($1?0:2)]+=1}}
(0...a[0].size).map &h}

Attempt This Online!

R, 95 94 bytes

-1 thanks to pajonk

\(x)Reduce(\(v,m)v+`[<-`(!1:max(nchar(x)),(m*attr(m,"m"))[-1,],1),gregexec("<>(<)|(>)<>",x),0)

Attempt This Online!

Ungolfed:

\(x) {
  Reduce(
    # for each vector of fish tail indices, increment the correct slots in the output 
    #   vector
    \(v,m) {
      v +
        # assigning 1's to the indices of fish tails in a vector of zeroes
        # (this works because the assignment form of [] silently returns the new vector
        `[<-`(
          0*1:max(nchar(x)),
          # multiply match indices by match lengths so that zero-length matches 
          #   aren't included, and exclude row 1 (total match)
          # (this works since assigning to index 0 leaves the vector unchanged)
          (m*attr(m,"m"))[-1,],
          1
         )
    },
    # find the fish tails and return a list of integer vectors, where each vector is
    #   the indices of the fish tails
    gregexec("<>(<)|(>)<>",x),
    # initial value for `Reduce`
    0
  )
}

Ruby, 83 79 bytes

->s{a=[c=x=0]*w=s=~/\n/
s.bytes{|i|c+=i/60
i>10?a[(i-62+x+=1)%w]+=c%3/2:x=0}
a}

Try it online!

Function that takes a newline separated string as an argument and returns an array.

Assumes there is no line longer than the first line.

c is a count of the number of < and > (characters over ascii 60) found. When we are at the middle character of a fish c%3 reaches its max value of 2. The direction of the character (ascii 60 or 62) points toward the tail, and the appropriate adjacent column a[x+1] or a[x-1] is incremented.

The magic constant might be expected to be -61, based on the average of 60 and 62, but another 1 must be subtracted to compensate for the increment of x.

JavaScript (ES6), 69 bytes

-2 thanks to @l4m2

Expects an array of arrays of characters (which may be of different lengths). Returns an array of integers.

a=>a.map(r=>r.map((c,i)=>b[i]=~~b[i]+(c=="<>"[a=c+3|-~a%3])),b=[])&&b

Try it online!

Commented

a =>                // a[] = input array
a.map(r =>          // for each row r[] in a[]:
  r.map((c, i) =>   //   for each character c at position i in r[]:
    b[i] =          //     update b[i]:
      ~~b[i] + (    //       coerce it to an integer in case it's still undefined
        c == "<>"[  //       test whether c is a fish tail:
          a =       //         update a:
            c + 3 | //           this gives 3 if c is a space (which forces the
                    //           test to fail and resets a to 3) or NaN otherwise
            -~a % 3 //           computes (a + 1) mod 3; if the result is 2, this
                    //           is the middle part of the fish (never a tail)
        ]           //       end of lookup
      )             //
  ),                //   end of inner map()
  b = []            //   start with b = []
) && b              // end of outer map(); return b[]

Python, 142 126 122 bytes

lambda s:[*map([m.start()+2*(m[0]<'>')for r in s for m in re.finditer(r'\S..',r)].count,range(max(map(len,s))))]
import re

Attempt This Online!

Charcoal, 40 35 28 26 bytes

WSP⭆ι⁺¬﹪⁺℅κL⁻…ι⊕λ ³Σ⊟KD⊕λ→

Try it online! Link is to verbose version of code. Takes input as a list of newline-terminated strings. Explanation: Now inspired by @Ausername's version of @ovs' answer.

WS

Loop through each row of fish.

P⭆ι⁺¬﹪⁺℅κL⁻…ι⊕λ ³Σ⊟KD⊕λ→

For each character, count the number of non-spaces so far including the current character, add on the current character's ordinal, and check whether the result is divisible by 3. If so, then increment the appropriate digit of the result, otherwise replace blanks with zeros. Replace the current result with the new computed values. (Unfortunately Charcoal has no way of adding the values in two arrays to each other so it has to peek each digit of the result in turn.)

K (ngn/k), 18 bytes

+/{x=62-3!2+\~^x}'

Try it online!

  {             }'   / for each row:
   x=62-3!2+\~^x     /   mark the tails
+/                   / add the rows to get column count
                     / example: "><>><> <><  "
             ~^x     / is not null (not space)
                     / 1 1 1 1 1 1 0 1 1 1 0 0
          2+\        / cumulative sums, starting at 2
                     / 3 4 5 6 7 8 8 9 10 11 11 11
        3!           / modulo 3. maps each fish to 0 1 2
                     / 0 1 2 0 1 2 2 0 1 2 2 2
     62-             / subtract from 62 (>). Maps each fish to ">=<"
                     / ">=<>=<<>=<<<"
   x=                / character-wise equal to original row
                     / 1 0 0 1 0 0 0 0 0 1 0 0

Jelly, 14 bytes

>⁶Ä3ḍk⁸Ġ€F=2)S

A monadic Link that accepts a list of lists of < > characters and yields a list of the column-wise tail counts.

Try it online!

How?

Partition each row after either the right-hand side of each fish (whether a head or a tail) or a space character like so:

  ><>   ><><><
00111000111111    <- greater than space?
00123333456789    <- cumulative sums
11001111001001    <- is divisible by three?
ww[f]www[f][f][]  <- identified "parts" (by partitioning after 1s) 
                        shown here as: water (w);
                                       fish ([f]); and
                                       a trailing empty list ([])

Then identify the "tails" of each "part" using the group-indices-by-value monad, Ġ:

Part Type Part Ġ(Part) flatten(Ġ(Part))=2?
[f] "<><" [[1,3],[2]] [0,0,1]
[f] "><>" [[2],[1,3]] [1,0,0]
w " " [[1]] [0]
[] "" [] []
>⁶Ä3ḍk⁸Ġ€F=2)S - Link: list of lists of characters, School
            )  - for each (Row of the School):
>⁶             -    {Row} greater than the space character? (vectorises)
  Ä            -    cumulative sums of {that}
   3ḍ          -    three divides {that}? (vectorises)
     k⁸        -    partition the Row after truthy indices of {that}
       Ġ€      -    group the indices of each of {those} by their values
         F     -    flatten {that}
          =2   -    {that} equals two? (vectorises)
             S - sum {that} (column-wise)

Retina, 97 bytes

N$^`
$.&
(\S)(\S)\1
 $2 
 <
1 
> 
 1
+`(?<=^(.)*)(.)((.*¶)+(?<-1>.)*)1(?(1)^)
$.(_$2*)$3 
 
0
0G`

Try it online! Explanation:

N$^`
$.&

Sort the rows in descending order of length.

(\S)(\S)\1
 $2 

Chop the heads and tails off all the fish.

 <
1 
> 
 1

Place 1s where the tails used to be.

+`(?<=^(.)*)(.)((.*¶)+(?<-1>.)*)1(?(1)^)
$.(_$2*)$3 

Accumulate the column totals.

 
0

Replace any remaining spaces with zeros.

0G`

Keep just the totals.