21
\$\begingroup\$

The cosine similarity of two vectors \$A\$ and \$B\$ is defined using their dot product and magnitude as:

\$\frac{A\cdot B}{\|A\|\|B\|}\$

Or in other terms

\$\frac{\sum_{i=1}^nA_iB_i}{\sqrt{\sum_{i=1}^nA_i^2}\sqrt{\sum_{i=1}^nB_i^2}}\$

Challenge

Given two nonempty, nonzero vectors containing only integers (can be taken as lists) calculate their cosine similarity.

  • If their lengths are not equal, then the shorter vector should be padded on the right to length of the longer vector with zeroes.

Input and output may be done via any reasonable method.

Test cases below.

[1,2,3], [4,5,6] => ~0.974
[9, 62, 0, 3], [25, 3, 6] => ~0.25
[-7, 4, 9, 8], [16, 1, -2, 5] => ~-0.35
[82, -9], [7, 52, 3] => ~0.024

This is , so the shortest answer wins.

\$\endgroup\$
10
  • 1
    \$\begingroup\$ @mathscat To the right, let me add that to the challenge \$\endgroup\$
    – emirps
    Apr 5 at 13:11
  • 1
    \$\begingroup\$ @Arnauld Yes, they may contain either positive or negative integers. \$\endgroup\$
    – emirps
    Apr 5 at 13:46
  • 3
    \$\begingroup\$ @AidenChow You can assume both lists are nonempty \$\endgroup\$
    – emirps
    Apr 5 at 13:48
  • 3
    \$\begingroup\$ I'd like to try to write an answer in a language that only handles integers. May I output (for instance) 100x the cosine-similarity - in other words, 2 decimal places but without the decimal point? \$\endgroup\$ Apr 5 at 14:28
  • 2
    \$\begingroup\$ @DominicvanEssen I believe it's community consensus that that's allowed, but if it isn't, then yes, you could use that output format \$\endgroup\$
    – emirps
    Apr 5 at 14:31

29 Answers 29

7
\$\begingroup\$

05AB1E, 9 bytes

`*OInOtP/

Input as a pair of lists.

Try it online or verify all test cases.

Explanation:

`          # Push both lists of the (implicit) input-pair separately to the stack
 *         # Multiply the values at the same positions in the lists together
           # (discarding trailing items of unequal length input-lists)
  O        # Sum all these values together
   I       # Push the input-pair of lists again
    n      # Square each inner value
     O     # Sum the two inner lists
      t    # Take the square root of both sums
       P   # Take the product of this pair
        /  # Divide the earlier dot-product by this
           # (after which the result is output implicitly)

Here a minor equal-bytes alternative: øPInOtP/O. I haven't been able to find anything shorter unfortunately, but there are a lot of approaches for this at 1 or 2 bytes longer: try it online.

\$\endgroup\$
6
\$\begingroup\$

Wolfram Language (Mathematica), 60 31 27 bytes

Dot@@Normalize/@PadRight@#&

Try it online!

Many thanks to @att and @ polfosol ఠ_ఠ ! They should have posted their answers (-_-)

\$\endgroup\$
4
  • 1
    \$\begingroup\$ Why not just Cos[VectorAngle@@PadRight@#]&? \$\endgroup\$ Apr 5 at 19:12
  • \$\begingroup\$ @polfosolఠ_ఠ Thank you very much, don't know this trick! Just N is needed 'couse floats expected \$\endgroup\$
    – lesobrod
    Apr 5 at 19:24
  • 2
    \$\begingroup\$ Dot@@Normalize/@PadRight@N@#& \$\endgroup\$
    – att
    Apr 5 at 19:49
  • 1
    \$\begingroup\$ Since the OP didn't explicitly ask for a numeral output, I think it would be fine to drop N@. I was also thinking about a workaround for Cos[VectorAngle but @att beat me to it ;) \$\endgroup\$ Apr 5 at 20:15
6
\$\begingroup\$

Desmos, 42 bytes

This non-padding version miraculously works out:

f(a,b)=total(ab)/(a^2.totalb^2.total)^{.5}

Try It On Desmos!

Try It On Desmos! - Prettified

I originally had constructed an answer that was 123 bytes (around 3 times longer than with no padding!!!) long that did actually do the padding, but then I realized, to my complete surprise, that the test case outputs were matching on both my padding and no-padding answers (which is this 42 byte answer), even for test cases that I made up myself. This led me to dig a bit deeper into why this was the case, resulting in the explanation below.

Explanation

The question essentially asks to pad the shorter list with zeros at the end to make both lists the same, and then multiply each element of the lists together and sum them, then divide it by the square root of the product of the sums of the elements of each of the two lists squared.

Now let's say that the unpadded, original input lists are a=[a_1,a_2,...,a_A] and b=[b_1,b_2,...,b_B], where the capitalized version of the list represents the length of the list. WLOG, assume that A<B. Now let c and d be the padded versions of a and b respectively, meaning c=[a_1,a_2,...,a_A,0,...,0], where there are B-A zeros, and d=[b_1,b_2,...,b_B], which is b unchanged.

Now to explain my non-padding answer first:

In Desmos, every time an operation is applied to two lists, it will almost always clip the resulting list to the length of the shorter list out of the two lists. This includes multiplication (implicitly vectorized), which is used in this answer.

For the numerator of the formula given in the question, it is essentially equivalent to using vectorized multiplication on the two lists (which will multiply the lists element-by-element), and then taking the total sum of that vectorized product. We know that vectorized multiplication will clip the lists to the length of the shorter lists, which means that when we take the product a*b, we will get the result [a_1 * b_1, a_2 * b_2,...,a_A * b_A], where it is clipped off at the shorter list a. Taking the total of this would give a_1 * b_1 + a_2 * b_2 + ... + a_A * b_A.

Wait! Now let's take a look at what happens when we take the product c*d and sum that list instead. Recall that c=[a_1,a_2,...,a_A,0,...,0] and d=[b_1,b_2,...,b_B], so c*d would be [a_1 * b_1, a_2 * b_2,...,a_A * b_A, 0 * b_(A+1),...,0 * b_B]. Taking the sum of all those terms gives a_1 * b_1 + a_2 * b_2 + ... + a_A * b_A, due to all the zeros vanishing from the sum. But this is the exact same sum that we got without padding!!! This indicates that padding at least does not affect the final result of the numerator of the formula, so it is fine to take the sum without padding the two lists beforehand, as it will result in the same answer as with padding anyways.

Now let's consider what is happening in the denominator. In my answer with no padding, I am simply just multiplying each of the lists by themselves (i.e. squaring them), and then summing the resulting elements. So basically, we have that a^2 = [(a_1)^2, (a_2)^2,...,(a_A)^2] and b^2 = [(b_1)^2, (b_2)^2,...,(b_B)^2], which means that their sums are (a_1)^2 + (a_2)^2 + ... + (a_A)^2 and (b_1)^2 + (b_2)^2 + ... + (b_B)^2 respectively.

But now consider the sums when there is padding involved. We then have that c^2 = [(a_1)^2, (a_2)^2,...,(a_A)^2, 0^2,...,0^2], and d^2 would be the same as b^2 because as stated earlier, d is the same as b unchanged. So, the sum of d^2 would obviously match that of b^2. But what about the sum of c^2? We have that the sum of c^2 is (a_1)^2 + (a_2)^2 + ... + (a_A)^2 + 0^2 + ... + 0^2, but because all the zeros just vanish, the sum simplifies down to (a_1)^2 + (a_2)^2 + ... + (a_A)^2. Comparing this to the sum that we got with no padding, we can see that they are exactly the same!!!

Obviously, we have assumed that A<B at the very start of our proof. But what happens if B>A? Then simply just switch all the a's and b's, A and B's, and also c and d and you will have the proof for the B>A case.

Lastly, what happens if A=B; in other words, what happens if they are both the same length? The clipping will shorten the longer list into the same length of the shorter list, but because they are the same length, this will not affect either of the lists. Similarly, padding will lengthen the shorter length to match the length of the longer list, but again, because the two lists are the exact same length, this won't affect either of the lists. Because both clipping and padding preserve the original lists, both of them will obviously result in the same calculations and therefore the same outputs as well.

Thus, by proving that the sums in both the numerator and denominator are the same with both padding (which is what the question is asking for) and with no padding (which is what is happening in my answer), my answer is valid, even though I am not actually doing any padding like the question asks for.

\$\endgroup\$
4
  • \$\begingroup\$ TIL you can do l.total instead of total(l). \$\endgroup\$
    – Bbrk24
    Apr 5 at 14:44
  • \$\begingroup\$ @Bbrk24 Well a^2.total and total(aa) do the exact same thing for the same byte count, but I think a^2.total looks a bit cooler :P \$\endgroup\$
    – Aiden Chow
    Apr 5 at 14:52
  • \$\begingroup\$ FYI this kind of function calling is called dot calls in Desmos; here's a tip that goes over them. \$\endgroup\$
    – Aiden Chow
    Apr 5 at 14:55
  • \$\begingroup\$ f(a,b)=total(ab)(a^2.totalb^2.total)^{-.5} also works for the same byte count. \$\endgroup\$
    – Neil
    Apr 7 at 18:18
6
\$\begingroup\$

Jelly, 7 bytes

ÆḊ€÷ƒḋ/

A monadic Link that accepts the pair of vectors and yields their cosine similarity.

Try it online!

How?

ÆḊ€÷ƒḋ/ - Link: pair of lists, V
  €     - for each vector:
ÆḊ      -   get its norm
      / - reduce V by:
     ḋ  -   dot-product
    ƒ   - starting with the dot-product reduce the norms by:
   ÷    -   division

Here's a dyadic version, also at seven bytes:

,ÆḊ€÷ƒḋ

Try it online!

\$\endgroup\$
5
\$\begingroup\$

Vyxal s, 7 bytes

*□²Ṡ√Π/

Try it Online!

Link includes test cases. flag prints numbers as decimals (for convenience) rather than as rationals, but is not needed.

-1 thanks to @lyxal

Explanation

*□²Ṡ√Π/  # Implicit input
*        # Vectorised multiplication
 □²      # Input list squared
   Ṡ√    # Sum and square root
     Π   # Product
      /  # Divide
         # s flag sums the list
         # Implicit output

Old:

Þ•2(?²∑√)*/  # Implicit input
Þ•           # Dot product: seems to handle the padding with 0s
  2(    )    # Repeat twice:
    ?        #  Next input
     ²       #  Squared
      ∑      #  Summed
       √     #  Square rooted
         *   # Multiply both
          /  # Divide
             # Implicit output
\$\endgroup\$
4
  • 1
    \$\begingroup\$ The 8 byter is exactly what I had \$\endgroup\$
    – lyxal
    Apr 5 at 13:20
  • \$\begingroup\$ Try it Online! for a nasty 7 byter :p \$\endgroup\$
    – lyxal
    Apr 5 at 13:22
  • 2
    \$\begingroup\$ vectorisation autofills with 0s regardless of operation \$\endgroup\$
    – lyxal
    Apr 5 at 13:23
  • 1
    \$\begingroup\$ the "nastiness" of the 7 byter is that the dot product is essentially taken in two parts: the multiply part explicitly and the sum part via flag. Honestly surprised the algebra works out \$\endgroup\$
    – lyxal
    Apr 5 at 13:25
5
\$\begingroup\$

MATLAB, 37 32 bytes

-5 bytes thanks to @loopywalt

@(a,b)sum(diag(a'*b))/norm(a'*b)

Instead of padding a or b to the length of the other, or cropping it, which should be equivalent in the numerator, we take the outer product of the two vectors, then sum the diagonal (ie trace). This is equivalent to the dot product of the two vectors cropped to the shortest length.

Unfortunately, this doesn't work if the two vectors have different length, trace requires a square matrix:

@(a,b)trace(a'*b)/norm(a'*b)
\$\endgroup\$
5
  • 1
    \$\begingroup\$ At least in octave you can do /norm(a'*b) instead of /norm(a)/norm(b). \$\endgroup\$
    – loopy walt
    Apr 7 at 23:38
  • \$\begingroup\$ @loopywalt Is that the same? I think you’d need the Frobenius norm, no? That’s norm(a'*b,"fro") in MATLAB. I’d suspect it’s the same meaning in Octave? \$\endgroup\$ Apr 7 at 23:59
  • 1
    \$\begingroup\$ Not in general the same but in the special case of a rank 1 matrix. Frobenius and (Euclidean) operator norms that is. \$\endgroup\$
    – loopy walt
    Apr 8 at 0:19
  • \$\begingroup\$ @loopywalt You are right, I did not know that. Thank you! \$\endgroup\$ Apr 8 at 0:27
  • 1
    \$\begingroup\$ In Octave, trace seemingly has the desired effect on non-square matrices. You can also add a Try It Online! link to your answer for Octave, e.g., this borrowed from the Octave answer. \$\endgroup\$
    – Giuseppe
    Apr 8 at 0:48
4
\$\begingroup\$

JavaScript (ES6), 76 bytes

Expects (a)(b).

a=>b=>[...a+b].reduce(t=>t+~~a[i]*~~b[i++]/h(...a)/h(...b),i=0,h=Math.hypot)

Try it online!

Commented

a =>             // outer function taking the 1st vector a[]
b =>             // inner function taking the 2nd vector b[]
[...a + b]       // build a large enough array to make sure that we
                 // iterate on all values of the largest vector
.reduce(t =>     // using t as an accumulator, for each entry in there:
  t +            //   add to t:
    ~~a[i]       //     either a[i] or 0
    * ~~b[i++]   //     multiplied by either b[i] or 0 (then increment i)
    / h(...a)    //     divided by hypot(a)
    / h(...b),   //     divided by hypot(b)
  i = 0,         //   start with t = i = 0
  h = Math.hypot //   h is an alias for Math.hypot
)                // end of reduce()
\$\endgroup\$
4
\$\begingroup\$

R, 52 bytes

Edit: +2 bytes to fix when either vector is length-1, thanks to Giuseppe

\(x,y,`?`=sum)?x*y*(seq(!x)==seq(!y))/(?x^2*?y^2)^.5

Attempt This Online!

\$\endgroup\$
3
  • 1
    \$\begingroup\$ the right-padding requirement is incredibly annoying here. But you should use a%*%b instead of sum(a*b), and I think you need seq(!x) and seq(!y): Try it online \$\endgroup\$
    – Giuseppe
    Apr 5 at 15:09
  • 1
    \$\begingroup\$ (plus a little rearrangement to get rid of those parentheses) \$\endgroup\$
    – Giuseppe
    Apr 5 at 15:09
  • \$\begingroup\$ @Giuseppe - That's a LOT of rearrangement, you should post it yourself. I even did try %*% but didn't manage to get it so short because I didn't think of using ^-.5 to be able to filp the whole lot around... \$\endgroup\$ Apr 5 at 16:30
4
\$\begingroup\$

TI-BASIC (TI-84),  43 41 bytes

Padding is expensive.

:Prompt A,B
:max(dim(ʟA),dim(ʟB→dim(ʟA
:Ans→dim(ʟB
:sum(ʟAʟB/√(sum(ʟB²)sum(ʟA²

Example usage:

prgmCOSSIM
A=?{9,62,0,3}
B=?{25,3,6}
              .2531554274

For comparison:

TI-BASIC (TI-84), no padding, 19 17 bytes

Errors when the inputs aren't equal length. Takes the first input via Ans.

:Prompt A
:sum(ʟAAns/√(sum(Ans²)sum(ʟA²

Example usage:

{1,2,3}:prgmCOSSIM
A=?{4,5,6}
               .974631462
\$\endgroup\$
5
  • \$\begingroup\$ You can remove the Disp for each to shave off a byte; the output will still be displayed. You can also remove the ) before the / for each. \$\endgroup\$
    – Yousername
    Apr 5 at 19:25
  • \$\begingroup\$ Also, max(dim(ʟA),dim(ʟB can be replaced with dim(ʟA)dim(ʟB. \$\endgroup\$
    – Yousername
    Apr 5 at 19:33
  • \$\begingroup\$ @Yousername removing the close paren makes sense, but why can I remove max(? \$\endgroup\$
    – Bbrk24
    Apr 5 at 19:42
  • \$\begingroup\$ The product of the lengths of the lists will always be at least the maximum length, since they will never be less than 1. However, this might make the resulting list length over the maximum allowed even if the input lengths are not, so you may choose to keep max(. \$\endgroup\$
    – Yousername
    Apr 5 at 19:50
  • \$\begingroup\$ I'm worried that could cause problems if the lists are too long, since there's a hard limit of 999 on list length. Something like + would hit that limit a lot slower than implicit multiplication (32 elements vs 500 elements), but it still hits it at all. max( doesn't. \$\endgroup\$
    – Bbrk24
    Apr 5 at 19:53
4
\$\begingroup\$

APL(Dyalog Unicode), 17 bytes SBCS

↓∘↑+.×.÷.5*⍨1⊥¨×⍨

Try it on APLgolf!

Lots of dots.. .

Input an array containing the two vectors.

\$\endgroup\$
1
  • \$\begingroup\$ I really though you could use monadic on vectors ({⍵÷+/×⍨⍵}) here, but I couldn't find anything. \$\endgroup\$
    – ovs
    Apr 5 at 21:40
4
\$\begingroup\$

Brachylog, 20 bytes

z₀×ᵐ+D&{^₂ᵐ+√}ᵐ×;D↔/

Try it online!

Explanation

z₀×ᵐ+D&{^₂ᵐ+√}ᵐ×;D↔/
                      Input is a list of two lists representing the two vectors
z₀                    Zip the two vectors, clipping to the shorter length
  ×ᵐ                  Multiply each pair of corresponding values
    +                 Sum the products
     D                Save this value as D (for Dot product)
      &               Start over with the original list of lists
       {     }ᵐ       Do this to each vector:
        ^₂ᵐ             Square each of its elements
           +            Sum the squares
            √           Get the square root of the sum
               ×      Multiply the two results
                ;D    Pair with D from earlier
                  ↔   Reverse the list, putting D first
                   /  Divide
\$\endgroup\$
4
\$\begingroup\$

Python NumPy, 50 bytes

lambda a,b:(z:=a*b[:,1>0]).trace()/(z*z).sum()**.5

Attempt This Online!

\$\endgroup\$
4
\$\begingroup\$

R, 40 bytes

\(x,y)sum(diag(z<-x%*%t(y)))/norm(z,"f")

Attempt This Online!

R port of Cris Luengo's MATLAB answer.

R, 48 bytes

\(x,y)(x%*%x*y%*%y)^-.5*(x*y)%*%!seq(!x)-seq(!y)

Attempt This Online!

A slight rearrangement and golf of Dominic van Essen's answer, posted at his request.

Works by maximizing operator precedence and vector recycling.

\$\endgroup\$
4
\$\begingroup\$

Pyth, 13 11 bytes

-2 bytes thanks to FryAmTheEggman

cF+s*MCQ.aM

Try it online!

Explanation

cF+s*MCQ.aMQ    # implicitly add Q
                # implicitly assign Q = eval(input())
      CQ        # transpose the input, truncating to the shortest element
    *M          # map each element to its product
   s            # sum the elements
  +             # prepend this to
        .aMQ    # the input elements mapped to their norms
cF              # fold this over division
\$\endgroup\$
1
  • \$\begingroup\$ @FryAmTheEggman Good catch, not sure why I missed that. \$\endgroup\$ Apr 10 at 13:40
3
\$\begingroup\$

Pip -x, 17 bytes

$*_MS Zg/RT$+*SQg

Takes the vectors as two command-line arguments, each one formatted as a Pip list. Attempt This Online!

Explanation

We use the equivalent formula \$\sum_{i=1}^n\frac{A_i}{\sqrt{\sum_{i=1}^nA_i^2}}\frac{B_i}{\sqrt{\sum_{i=1}^nB_i^2}}\$:

$*_MS Zg/RT$+*SQg
                   ; g is list of command-line args, evaluated (-x flag)
              SQg  ; Square each value in the list of two vectors
           $+*     ; Sum each squared vector
         RT        ; Take the square root of each sum
       g/          ; Divide the list of two vectors by that list of two magnitudes
      Z            ; Zip (transpose) the list of two vectors, pairing up corresponding
                   ; elements and trimming the longer vector to the length of the
                   ; shorter one (which, at the dot product stage, is equivalent to
                   ; right-padding the shorter one with zeros)
   MS              ; Map this function to each pair of values and sum the results:
$*_                ;   Take the product of the values
\$\endgroup\$
3
\$\begingroup\$

Python, 73 bytes

-95 bytes thanks to Neil, -3 bytes thanks to att and -3 bytes thanks to xnor.

This works despite the zip function stopping at the shorter sequence, since we right pad with 0. Essentially, the elements of the longer sequence do not affect the calculation.

lambda a,b:d(a,b)/(d(a,a)*d(b,b))**.5;d=lambda*a:sum(map(int.__mul__,*a))

Attempt This Online!

P.S. I edited out the other versions of this answer made with scipy, numpy and scikit-learn, because they were significantly longer.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ sum(map(int.__mul__,*a)) for -3 bytes \$\endgroup\$
    – xnor
    Apr 5 at 23:35
  • \$\begingroup\$ @xnor Thank you for the contribution. This should be definitely be a golfing tip in the python tips page. \$\endgroup\$
    – solid.py
    Apr 6 at 8:39
3
\$\begingroup\$

Julia 1.0, 68 60 44 43 bytes

x^y=sum(prod,zip(x,y))
a\b=a^b/√(a^a*b^b)

Try it online!

  • The power operator ^ is redefined for the intermediate function. This adds a few bytes where a square or square root is needed, but its high precedence in order of operations cuts down on parentheses used.
  • zip is used to accommodate vectors with different lengths.
    • Otherwise, x^y=sum(x.*y) could be used.

Alternatively, LSHFunctions.cossim could be adapted for this challenge.

  • -8 bytes thanks to Giuseppe: rewrite with an improved formula
  • -16 bytes (!!) thanks to Giuseppe: sum supports a calling function
  • -1 byte thanks to MarcMush: replace sqrt with
\$\endgroup\$
4
  • 1
    \$\begingroup\$ A little rearrangement gives 60 bytes \$\endgroup\$
    – Giuseppe
    Apr 6 at 15:32
  • 1
    \$\begingroup\$ Oh, looks like you can use the optional(? I don't know Julia that well) f argument to sum to get to 44 bytes \$\endgroup\$
    – Giuseppe
    Apr 6 at 18:02
  • \$\begingroup\$ Oh, that's brilliant \$\endgroup\$ Apr 6 at 18:06
  • 2
    \$\begingroup\$ is 3 bytes, shaving one byte compared to sqrt \$\endgroup\$
    – MarcMush
    Apr 6 at 19:10
3
\$\begingroup\$

Octave, 110 61 bytes

saved 49 bytes thanks to the comment

@(A,B)A(1:(p=min(numel(A),numel(B))))*B(1:p)'/norm(A)/norm(B)

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ 44 bytes: @(A,B)postpad(A,numel(B))*B'/norm(A)/norm(B) (found by checking Rearranging Matrices in the documentation). \$\endgroup\$
    – Neil
    Apr 7 at 18:38
  • \$\begingroup\$ I've got it down to 37 bytes in MATLAB, with code that should also run in Octave (but I didn't try it): codegolf.stackexchange.com/a/259849/91877 \$\endgroup\$ Apr 7 at 23:22
2
\$\begingroup\$

Vyxal, 9 bytes

Þ•∇"²Ṡ√Π/

Edit: Thunno ninja'd me but kinda want to leave my answer here as it (was) shorter

Try it Online!

How it works:

Þ•∇"²Ṡ√Π/
Þ•          Calculate the dot product
  ∇         Shift stack
   "        Pair the vectors
    ²Ṡ√     Square, take vectorised sum and square root
       Π/   Push product and divide
\$\endgroup\$
3
  • \$\begingroup\$ I don't think the flag is necessary, it's just there for convenience \$\endgroup\$
    – The Thonnu
    Apr 5 at 13:21
  • 1
    \$\begingroup\$ as the other Vyxal answer and community consensus states, you don't need to specifically include \$\endgroup\$
    – emirps
    Apr 5 at 13:22
  • \$\begingroup\$ I can't keep up answer in Vyxal ( The only way is ask my own question and immediately answer it ) \$\endgroup\$
    – lesobrod
    Apr 5 at 15:06
2
\$\begingroup\$

Pyt, 20 bytes

Đ←Đ04Ș*Ʃ⇹²Ʃ↔+²Ʃ⇹↔*√/

Try it online!

Đ←Đ04Ș                       Gets stack set up for calculations
      *Ʃ                     A·B
        ⇹²Ʃ↔+²Ʃ⇹↔*√          ||A||*||B||
                   /         divide; implicit print
\$\endgroup\$
2
\$\begingroup\$

Charcoal, 16 14 bytes

I∕Σ×θη₂ΠE²ΣXA²

Attempt This Online! Link is to verbose version of code. Explanation:

    θ           First input
   ×            Vectorised multiply by
     η          Second input
  Σ             Take the sum
 ∕              Divided by
         ²      Literal integer `2`
        E       Map over implicit range
            A   Next input
           X    Vectorised raise to power
             ²  Literal integer `2`
          Σ     Take the sum
       Π        Take the product
      ₂         Take the square root
I               Cast to string
                Implicitly print
\$\endgroup\$
0
2
\$\begingroup\$

Factor + math.similarity, 17 bytes

cosine-similarity

Attempt This Online!

\$\endgroup\$
2
\$\begingroup\$

Nibbles, 14.5 bytes (29 nibbles)

^/^+*100!$_*~*;~$+.$^$~$_-2

Nibbles only handles integers, and perhaps isn't the ideal language to calculate a value that will always lie in the range between -1 and 1. So this program outputs the cosine similarity multiplied by 100: the extra code for this costs +3.5 bytes - without it, the program would be /+!$_*^*;~$+.$^$~$_-2 (11 bytes), but can only output to an accuracy of zero decimal places, so either 1, 0 or -1.

^/^+*100!$_*~*;~$+.$^$~$_-2
                                # First get the dot-product multiplied by 100:
        !                       #   zip together
         $_                     #   arg1 and arg2
           *                    #   by multiplication
    *100                        #   multiply each element by 100
   +                            #   and get the sum
  ^         ~                   # and then square the dot-product;
                                # Now get the squares of the product of the magnitudes:
              ;~$               #   apply to arg1 and save as function:
                  .$            #     map over each element:
                    ^$~         #       square
                 +              #     and sum the squared elements
                       $_       #   apply the same function to arg2
             *                  #   and multiply the two results together;
 /                              # Now divide the squared 100x dot-product
                                # by the squares of the product of the magnitudes
^                        -2     # and return the square-root of the result

enter image description here

\$\endgroup\$
2
\$\begingroup\$

Raku, 45 bytes

->\a,\b{sum(a Z*b)/sqrt sum(a Z*a)*sum b Z*b}

Try it online!

This is a straightforward translation of the cosine similarity formula. x Z* y zips two lists together with multiplication, and sum and sqrt are self-explanatory.

\$\endgroup\$
2
\$\begingroup\$

BQN, 20 17 bytes

-3 bytes thanks to att

D←+´⊣×≠⊸↑
D÷×○√○D

The first line defines a helper function; the second line is the solution, an anonymous tacit function that takes two lists of numbers as left and right arguments. Try it at BQN online!

Explanation

The helper function D calculates the dot product of two vectors (or, given one argument, the dot product of a vector with itself). The default behavior of turned out to be very convenient here.

D←+´⊣×≠⊸↑
D←         Assign this function to D:
      ≠⊸↑    Take (length of left arg) elements from (right arg)
               If left arg is shorter, trims right arg to that length
               If left arg is longer, pads right arg with zeros
    ⊣×       Multiply itemwise with left arg
  +´         Sum

D÷×○√○D
     ○D  Apply D to each argument (i.e. multiply it by itself and sum)
   ○√    Apply square root to each of those sums (magnitude of each vector)
  ×      Multiply those two results
D        Apply D to both arguments (i.e. get their dot product)
 ÷       Divide the dot product by the product of magnitudes
\$\endgroup\$
2
  • \$\begingroup\$ G÷×○√○(G←+´⊣×≠⊸↑) for 17 \$\endgroup\$
    – att
    Apr 8 at 4:14
  • \$\begingroup\$ @att Oh, nice! I was vaguely wondering if I could eliminate the repetition of , but I didn't realize that that dot product function would work just as well with one argument as with two. \$\endgroup\$
    – DLosc
    Apr 10 at 17:16
1
\$\begingroup\$

Arturo, 69 bytes

$[a b][//∑map couple a b'p->∏p∏map@[a b]=>[sqrt∑map&'x->x^2]]

Try it

\$\endgroup\$
1
\$\begingroup\$

Japt v2.0a0, 13 bytes

í*V x÷NËx²¬Ã×

Try it

\$\endgroup\$
1
\$\begingroup\$

jq, 68 bytes

(transpose|map((.[0]*.[1])?)|add)/(map(map(.*.)|add|sqrt)|.[0]*.[1])

Try it online!

transpose pads the arrays with nulls, which we then simply remove with ? when the multiplication fails, instead of replacing with zero.

\$\endgroup\$
0
\$\begingroup\$

JavaScript, 175 bytes

(e,r)=>(l="length",p=(e,r)=>Array.from({...e,[l]:r[l]},e=>e??0),e[l]<r[l]?e=p(e,r):r=p(r,e),e[_="reduce"]((e,$,c)=>e+$*r[c],0)/(e[_]((e,r)=>e+r*r,0)*r[_]((e,r)=>e+r*r,0))**.5)

Try it:

c=

// start code
(e,r)=>(l="length",p=(e,r)=>Array.from({...e,[l]:r[l]},e=>e??0),e[l]<r[l]?e=p(e,r):r=p(r,e),e[_="reduce"]((e,$,c)=>e+$*r[c],0)/(e[_]((e,r)=>e+r*r,0)*r[_]((e,r)=>e+r*r,0))**.5)
// end code

console.log(c([1,2,3], [4,5,6]))
console.log(c([9, 62, 0, 3], [25, 3, 6]))
console.log(c([-7, 4, 9, 8], [16, 1, -2, 5]))
console.log(c([82, -9], [7, 52, 3]))

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.