Find the average of a word

Inspired by this chat message

Your task will be to take a word and find the average position of its letters on the keyboard as a letter.

Keyboard Layout

Since layouts vary from keyboard to keyboard, we will be using a standard based of of my own keyboard in this question.

The keyboard has 3 rows, the top row from left to right contains the keys

QWERTYUIOP


The second row contains the letters

ASDFGHJKL


The final row contains

ZXCVBNM


Each letter is 1 unit horizontal from its neighbor to the left. This means that W is 1 away from Q and E is 1 away from W and so on.

The keys at the beginning of each row have the positions:

Q : 0,0
A : 1/3,1
Z : 2/3,2


This means that the rows are separated one unit vertically and the bottom two rows are shifted by a third from the row above them.

You should take a word as input and output the letter that is closest to the average position of the letters in its word. The average of a set of vectors is

(average x value, average y value)


When two keys are equidistant from the average you may output either as the "closest" key.

This is so answers will be scored in bytes with fewer bytes being better.

Example solution

Let's calculate the average of APL.

We convert each letter to a vector

A -> (1/3,1)
P -> (9,0)
L -> (8 1/3,1)


We add these up the three vectors to get (17 2/3, 2). We then divide each coordinate by 3 (The number of letters in the word) to get (5 8/9, 2/3).

The closest letter to (5 8/9, 2/3) is J at (6 1/3,1) so our result is J.

Test Cases

APL  -> J
TEXT -> R
PPCG -> J
QQQQ -> Q
ZZZZ -> Z
PPPP -> P
MMMM -> M
QQSS -> A or W

• May the I/O be lowercase, or is uppercase mandatory? – Kevin Cruijssen Nov 3 '20 at 14:52
• @KevinCruijssen You can do lower case. – Wheat Wizard Nov 3 '20 at 15:38

Python 3, 130 bytes

lambda w,d={'QAZWSXEDCRFVTGBYHNUJMIKOOLPP'[i]:i%3*1j+i/3for i in range(28)}:min(d,key=lambda c:abs(d[c]-sum(map(d.get,w))/len(w)))


Try it online!

d={'QAZWSXEDCRFVTGBYHNUJMIKOOLPP'[i]:i%3*1j+i/3for i in range(28)} constructs the mapping from letters to points (represented as complex numbers, (x+y*1j)).

As for the lambda body, sum(map(d.get,w))/len(w) computes the average position of word w, and putting that in min(d,key=lambda c:abs(d[c]-…)) finds the closest letter to that position. (For complex numbers, abs(A-B) corresponds to the Euclidean distance between (A.real, A.imag) and (B.real, B.imag).)

Jelly, 37 bytes

ØQi€µT÷3Ḣ+Ṁ;T
Ç€ZÆmðạ²SðÐ€ØAÇ€¤iṂ$ịØA  Try it online! lol way too long Explanation ØQi€µT÷3Ḣ+Ṁ;T Helper Link; compute the position of a key € For each row of ØQ ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"] (hooray for builtins) i Find the first occurrence of the argument µ Start a new monadic chain T List of indices of truthy values; singleton list with the row of the key ÷ Divide the index by 3 3 Ḣ Take the first element + Add it to the original list Ṁ Take the maximum (the adjusted horizontal position of the key) ; Append T The index of the truthy value (the row) Ç€ZÆmðạ²SðÐ€ØAÇ€¤iṂ$ịØA  Main Link
€                       For each character in the input
Ç                        Compute its position using the helper link
Z                      Zip (all of the horizontal positions are in the first list; all of the vertical positions are in the second list)
Æm                    Take the arithmetic mean (of each sublist)
ðạ²Sð               Dyadic chain to compute the distance (squared) between two coordinates
ạ                  Take the absolute difference between each coordinate value (auto-vectorization)
²                 Square each value
S                Take the sum (returns the distance squared but for comparison that's fine)
Ð€             Take the distance between the mean position and each element in
ØAÇ€¤        [Nilad:] the positions of each character in the uppercase alphabet
€         For each character in
ØA           "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Ç          Compute its position
iṂ$Find the index of the minimum (closest) i First occurrence of in the list of distances Ṃ the minimum ị Index back into ØA The alphabet  • @downvoter the issue with the test case has been fixed; please remove your downvote or explain why you wish to downvote my answer – hyper-neutrino Sep 24 '17 at 17:41 • I think it disagrees again? F doesn't seem to be an allowed output anymore... – Erik the Outgolfer Sep 24 '17 at 18:02 • @EriktheOutgolfer Ah okay I didn't update that remark after FCM corrected the test case – hyper-neutrino Sep 24 '17 at 18:03 • Somehow Mathematica doesn't have a bultin for keyboard rows. – Razetime Sep 19 '20 at 15:25 • @Razetime ah, I see. yeah finding something mathematica doesn't just have a builtin for is always pretty rare :D – hyper-neutrino Sep 19 '20 at 16:54 C# (.NET Core), 250 + 13 bytes +13 bytes for using System; n=>{var a=new[]{"QWERTYUIOP","ASDFGHJKL","ZXCVBNM"};float x=0,y=0;int i=0,j=0,l=n.Length;foreach(char c in n){for(i=0,j=0;i<2;){if(a[i][j]==c)break;if(++j>=a[i].Length){i++;j=0;}}x+=j;y+=i;}return a[(int)Math.Round(y/3)][(int)Math.Round(x/l+y/l/3)];}  Try it online! Little sidenote: This outputs F for TEXT, since that was the original desired output. Outputting R instead of F was changed after this answer was posted. JavaScript (ES6), 166 bytes f= s=>[...s].map(c=>(h+=c=s.search(c),v+=c%3,l++),h=v=l=0,s='QAZWSXEDCRFVTGBYHNUJMIKKOLLP')&&[...s].map((c,i)=>(i=(i-h/l)*(i-h/l)+(i=i%3-v/l)*i*9,i)<m&&(m=i,r=c),m=9)&&r <input oninput=o.textContent=/^[A-Z]+$/.test(this.value)?f(this.value):><pre id=o>

6 bytes could be saved by switching to ES7. Previous 131-byte solution used a simplistic distance check which is no longer acceptable.

f=
s=>([...s].map(c=>(h+=c=s.search(c),v+=c%3,l++),h=v=l=0,s='QAZWSXEDCRFVTGBYHNUJMIKKOLLP'),v=(v/l+.5|0),h=((h/l-v)/3+.5|0),s[h*3+v])
<input oninput=o.textContent=/^[A-Z]+$/.test(this.value)?f(this.value):><pre id=o> Java, 257 243 242 237 bytes char h(String s){int l=s.length(),i=l+28;s="QAZWSXEDCRFVTGBYHNUJMIK<OL>P"+s;float d=9,x=0,y=0,e;for(;i>28;y+=(e=s.indexOf(s.charAt(--i)))%3/l,x+=e/3/l);for(;i-->0;)if((e=(x-i/3f)*(x-i/3f)+(y-i%3)*(y-i%3))<d){d=e;l=i;}return s.charAt(l);}  Saved 14 bytes - the distance away from the best key will be less than 3 units Java (OpenJDK 8), 452431424400389324322296285281276274260258 257 bytes Something to start golfing from s->{String c="QWERTYUIOPASDFGHJKL;ZXCVBNM";int i=0,r=0,l=0;double x=0,y=0,D=99,t,f=s.length();for(;i<f;x+=l%10+l/10/3d,y+=l/10)l=c.indexOf(s.charAt(i++));for(;r<27;r++)if(D>(t=Math.pow(x/f-r/10/3d-r%10,2)+Math.pow(y/f-r/10,2))){D=t;l=r;}return c.charAt(l);}  Try it online! • Gives me the wrong result for TEXT. – Ian H. Sep 24 '17 at 17:36 • @IanH. It gives me 'R' which is what OP asked for – Roberto Graham Sep 24 '17 at 17:39 • Didn't see that was changed in the task, my bad. – Ian H. Sep 24 '17 at 17:41 • – Jonathan Frech Sep 24 '17 at 17:47 • I hadn't seen this answer when I posted mine, should I have suggested mine as a comment or are multiple answers for the same language ok? – Tahg Sep 24 '17 at 21:18 Charcoal, 79 bytes ≔⪪”$±=Ｋ≕⦃Vj@η⟲.w\o:7➙1”¶ηＦθＦ³Ｆ⌕Ａ§ηκι⊞υ⟦λκ⟧ＩＥ²∕ΣＥυ§λιＬθ§§η⁺·⁵∕ΣＥυ⊟ιＬθ⁺·⁵∕ΣＥυ⊟ιＬθ


Try it online!

Neil's golf using string functions.

Charcoal, 107 bytes

Ｐ”\$±=Ｋ≕⦃Vj@η⟲.w\o:7➙1”≔⟦⟧α≔⟦⟧βＦχ«Ｆ³«ＪικＦθ«¿⁼λＫＫ«⊞α⎇›ⅉ⁰⁺∕¹¦³ⅈⅈ⊞βⅉ»»»»Ｊ⌊∕⁺Σα·⁵Ｌθ⌈∕⁺Σβ·⁵Ｌθ≔ＫＫχ⎚χ¶Ｉ∕ΣαＬθ¶Ｉ∕ΣβＬθ


Try it online!

Draws the keyboard on the canvas and finds the average that way.

05AB1E, 44 bytes

žVIšεεžVsδkDZkDŠ3/+à‚]ćøÅAUòDžVsèŠè€θDX.xkè


Not too happy with it, but it works.. :/ Can definitely be golfed some more using a different approach.

I/O in lowercase.

Explanation:

Step 1: create a list with all the coordinate-values, and convert the input in a similar matter as well:

žV                     # Push builtin ["qwertyuiop","asdfghjkl","zxcvbnm"]
Iš                   # Prepend the input-string to this list
ε                  # Map over each string:
ε                 #  Inner map over each individual character:
žV               #   Push ["qwertyuiop","asdfghjkl","zxcvbnm"] again
s              #   Swap so the current character is at the top of the stack
δk            #   Get the index of this character in each of the three strings
D           #   Duplicate this list of indices
Z          #   Get the largest index (without popping)
k         #   Get the index of this largest index in the triplet
D        #   Duplicate this index
Š       #   Triple swap the three values on the stack (a,b,c→c,a,b)
3/     #   Divide it by 3
+    #   Add it to each index in the list we duplicated
à   #   Pop and push the maximum again
‚  #   And pair it with the duplicated & triple-swapped index
]                  # Close the nested maps


Try just this first step.

Step 2: get the average coordinate of the input:

ć                      # Extract head; pop and push remainder-list and first item separated
ø                     # Zip/transpose; swapping rows/columns
ÅA                   # Get the arithmetic mean of both inner triplets


Try just the first two steps.

Step 3: get the coordinate closest to this average coordinate, and convert it back to a character to output:

                      # Pop and push both separated to the stack
U                     # Pop and store the second one in variable X
ò                    # Round the first value to the nearest integer
D                   # Duplicate this integer
žV                 # Push ["qwertyuiop","asdfghjkl","zxcvbnm"] yet again
s                # Swap so the integer is at the top of the stack
è               # Index it into the list of strings
Š              # Triple-swap the values on the stack (a,b,c→c,a,b)
è             # Index the duplicated integer into the remainder-list
€θ           # Leave the last value of each inner pair
D          # Duplicate the list
X.x       # Pop the copy and leave the value closest to value X
k      # Get the index of this value in the duplicated list
è     # And index it into the string we triple-swapped
# (after which this character is output implicitly as result)


Jelly, 33 31 bytes

Æịµ÷3Ċ+
ØQœiⱮÇÆmØQŒṪ¤Çạ¥ÐṂœịØQX


Try it online!

Uses some features that were presumably not implemented when HyperNeutrino wrote his solution, so considering this only beats it by 4 6 bytes, I wouldn't be surprised if a modernized version of his would come out shorter--but I started writing this before I even noticed his existed.

Æịµ÷3Ḟ+                    Helper link (monadic):
Æị                         convert list to complex,
µ   +                    add the complex number to
Ċ                     the imaginary part of
÷3                      its quotient by 3.

ØQœiⱮ                      multidimensional indices of input in QWERTY layout,
Æm                   average;
ØQŒṪ¤              for all multidimensional indices in the layout
ÐṂ         keep those with the smallest
ạ¥           absolute difference between the average and
Ç             their result from the helper link;
œịØQ     index back into QWERTY
X    and choose one key at random.


Mathematica, 234 bytes

(r=(K=Round)@Mean[If[(w=First[q=#&@@Position[(s=X/@{"QWERTYUIOP","ASDFGHJKL","ZXCVBNM"}),#]])==1,q=q-1,If[w==2,q=q+{-1,-2/3},q=q+{-1,-1/3}]]&/@(X=Characters)@#];z=If[(h=#&@@r)==0,r=r+1,If[h==1,r=r+{1,2/3},r=r+{1,1/3}]];s[[##]]&@@K@z)&

• Fixed! works for all test cases. – ZaMoC Sep 24 '17 at 18:58