The Keyboard Shift Cipher

21

2

Given the following input:

  • An integer n where n > 0.
  • A string s where s is not empty and s~=[0-9A-Z]+ (alpha-numeric capitals only).

Using a standard, simplified QWERTY keyboard (as shown below):

1234567890
QWERTYUIOP
ASDFGHJKL
ZXCVBNM

Perform the following operation:

  • Find the original row that each character is in on the keyboard.
  • Replace the letter with the correct shifted equivalent for n based on its original position + n.
    • E.G. s="AB" and n=2: A would become D and B would become M.
  • If keyboard_row[position + n] > keyboard_row.length, wrap back to the start.
    • E.G. s="0P" and n=2: 0 would become 2 and P would become W.

Examples:

f("0PLM",1)    = 1QAZ
f("ZXCVB",2)   = CVBNM
f("HELLO",3)   = LYDDW
f("0PLM",11)   = 1QSV
f("0PLM",2130) = 0PHX

Rules


This is slightly more difficult than it seems at first glance.

Magic Octopus Urn

Posted 2018-02-05T13:42:35.040

Reputation: 19 422

2Are we allowed to take the input as character-array instead of string? Currently assumed we, but forgot to ask.. – Kevin Cruijssen – 2018-02-05T14:51:03.980

@KevinCruijssen shrug sure, it's not too outlandish. Unless it saves you a byte to break a tie I'm not complaining. – Magic Octopus Urn – 2018-02-05T22:43:35.283

Answers

11

Jelly, 13 bytes

ØQØDṭ,ṙ€¥⁸F€y

Try it online!

How it works

ØQØDṭ,ṙ€¥⁸F€y  Main link. Left argument: n (integer). Right argument: s (string)

ØQ             Qwerty; set the return value to
               ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"].
  ØD           Digits; yield "0123456789".
    ṭ          Tack, yielding ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM", "0123456789"].
        ¥⁸     Call the two links to the left as a dyadic chain, with right
               argument n.
      ṙ€       Rotate each string in the array n units to the left.
     ,         Yield the pair of the unmodified and the rotated string array.
          F€   Flatten each, mapping, e.g., ["QWERTYUIOP", ..., "0123456789"] to
               "QWERTYUIOPASDFGHJKLZXCVBNM0123456789".
            y  Translate s according to the mapping we've built.

Dennis

Posted 2018-02-05T13:42:35.040

Reputation: 196 637

2Jelly has keyboard-layout built-ins eh? – Magic Octopus Urn – 2018-02-05T14:13:50.683

4@MagicOctopusUrn No, only QWERTY right now :-P – Erik the Outgolfer – 2018-02-05T14:14:28.350

13 bytes? Which character set is that supposed to be? In UTF-8 it's 26 bytes! – Cephalopod – 2018-02-05T21:23:14.997

2

@Cephalopod Jelly uses the Jelly code page.

– Dennis – 2018-02-05T21:26:15.673

9

Python 2, 110 bytes

lambda s,n,y='1234567890'*99+'QWERTYUIOP'*99+'ASDFGHJKL'*99+'ZXCVBNM'*99:''.join(y[y.find(c)+n%630]for c in s)

Try it online!

This uses a big enough string (99 copies of each row) and the LCM between the rows lengths (630) to find the correct substitution avoiding individual correction between each row.

Rod

Posted 2018-02-05T13:42:35.040

Reputation: 17 588

7

Java 8, 159 158 bytes

n->s->{for(int i=s.length,j;i-->0;)for(String x:"1234567890;QWERTYUIOP;ASDFGHJKL;ZXCVBNM".split(";"))if((j=x.indexOf(s[i])+n)>=n)s[i]=x.charAt(j%x.length());}

-1 byte thanks to @OlivierGrégoire modifying the input-array instead of printing directly.

Explanation:

Try it online.

n->s->{  // Method with integer and character-array parameters, and no return-type
  for(int i=s.length,j;i-->0;)
         //  Loop over the input character-array with index
    for(String x:"1234567890;QWERTYUIOP;ASDFGHJKL;ZXCVBNM".split(";"))
         //   Inner loop over the qwerty-lines
      if((j=x.indexOf(s[i])+n)>=n)
         //    If the current qwerty-line contains the character
         //     Set `j` to the index of this character on that line + input `n`
        s[i]=x.charAt(j%x.length());}
         //     Replace the character at index `i`
         //     with the new character (at index `j` modulo length_of_qwerty_line)

Kevin Cruijssen

Posted 2018-02-05T13:42:35.040

Reputation: 67 575

1158 bytes, at the cost of input-outputting the char[]. – Olivier Grégoire – 2018-02-06T09:30:21.493

5

Retina, 49 bytes

"$&"+T`9o`dQW\ERTYUI\OPQASDFG\HJK\LAZXC\VBNMZ
0A`

Try it online! Takes input n and s on separate lines. Explanation:

"$&"+

Repeat n times.

T`9o`dQW\ERTYUI\OPQASDFG\HJK\LAZXC\VBNMZ

Shift all the characters one key to the right.

0A`

Delete n.

Neil

Posted 2018-02-05T13:42:35.040

Reputation: 95 035

5

JavaScript (ES6), 101 99 bytes

Takes input in currying syntax (s)(n). Works with arrays of characters.

s=>n=>s.map(c=>(S='1QAZ2WSX3EDC4RFV5TGB6YHN7UJM8IK_9OL_0P')[(p=S.search(c)+n*4)%(-~'9986'[p%4]*4)])

Test cases

let f =

s=>n=>s.map(c=>(S='1QAZ2WSX3EDC4RFV5TGB6YHN7UJM8IK_9OL_0P')[(p=S.search(c)+n*4)%(-~'9986'[p%4]*4)])

console.log(JSON.stringify(f([..."0PLM"])(1)))    // 1QAZ
console.log(JSON.stringify(f([..."ZXCVB"])(2)))   // CVBNM
console.log(JSON.stringify(f([..."HELLO"])(3)))   // LYDDW
console.log(JSON.stringify(f([..."0PLM"])(11)))   // 1QSV
console.log(JSON.stringify(f([..."0PLM"])(2130))) // 0PHX

How?

We look for the position p of each character of the input within a string S where the keyboard rows are interleaved: the first 4 characters are '1QAZ' (first column of the keyboard), the next 4 characters are '2WSX' (second column of the keyboard) and so on. Unused positions are padded with underscores and the last ones are simply discarded.

col # | 0    | 1    | 2    | 3    | 4    | 5    | 6    | 7    | 8    | 9
------+------+------+------+------+------+------+------+------+------+---
row # | 0123 | 0123 | 0123 | 0123 | 0123 | 0123 | 0123 | 0123 | 0123 | 01
------+------+------+------+------+------+------+------+------+------+---
char. | 1QAZ | 2WSX | 3EDC | 4RFV | 5TGB | 6YHN | 7UJM | 8IK_ | 9OL_ | 0P

This allows us to easily identify the row with p mod 4 and eliminates the need for explicit separators between the rows.

We advance by 4n positions, apply the correct modulo for this row (40, 40, 36 and 28 respectively) and pick the replacement character found at this new position in S.

Arnauld

Posted 2018-02-05T13:42:35.040

Reputation: 111 334

3

Jelly, 18 bytes

ØQØDṭẋ€‘}Ẏ©iЀ⁸+ị®

Try it online!

Erik the Outgolfer

Posted 2018-02-05T13:42:35.040

Reputation: 38 134

3

C,  152  149 bytes

Thanks to @gastropner for saving three bytes!

j,l;f(S,n){for(char*s=S,*k;*s;++s)for(k="1234567890\0QWERTYUIOP\0ASDFGHJKL\0ZXCVBNM\0";l=strlen(k);k+=l+1)for(j=l;j--;)k[j]-*s||putchar(k[(j+n)%l]);}

Try it online!

Unrolled:

j,l;
f(S,n)
{
    for (char*s=S, *k; *s; ++s)
        for (k="1234567890\0QWERTYUIOP\0ASDFGHJKL\0ZXCVBNM\0"; l=strlen(k); k+=l+1)
            for (j=l; j--;)
                k[j]-*s || putchar(k[(j+n)%l]);
}

Steadybox

Posted 2018-02-05T13:42:35.040

Reputation: 15 798

Either I'm hallucinating, or the inner loop can be changed to for(j=l;j--;) but I don't know why without any other changed. Still, should get you to 149. – gastropner – 2018-02-07T21:12:00.353

@gastropner Ah, yes, the search order doesn't matter, so it works. Thanks! – Steadybox – 2018-02-07T22:11:47.050

2

Red, 152 bytes

f: func[s n][foreach c s[foreach[t l]["1234567890"10"QWERTYUIOP"10"ASDFGHJKL"9"ZXCVBNM"7][if p: find t c[if(i:(index? p)+ n // l)= 0[i: l]prin t/(i)]]]]

Try it online!

Ungolfed:

f: func [s n][1
    foreach c s [
        foreach [t l] ["1234567890"10"QWERTYUIOP"10"ASDFGHJKL"9"ZXCVBNM"7][
            p: find t c
            if p [ 
                i: (index? p) + n // l
                if i = 0 [i: l]
                prin t/(i) ]]]]

Galen Ivanov

Posted 2018-02-05T13:42:35.040

Reputation: 13 815

2

Haskell, 99 bytes

f(s,n)=[dropWhile(/=c)(cycle r)!!n|c<-s,r<-words"1234567890 QWERTYUIOP ASDFGHJKL ZXCVBNM",elem c r]

Try it online!

Roman Czyborra

Posted 2018-02-05T13:42:35.040

Reputation: 604

You can use s#n= ... instead of f(s,n)= ... which is only an example notation used for the examples. – Laikoni – 2018-02-06T08:16:15.083

1

Perl 5, 94 + 1 (-p) = 95 bytes

$s=<>;for$i(1234567890,QWERTYUIOP,ASDFGHJKL,ZXCVBNM){eval"y/$i/".(substr$i,$s%length$i)."$i/"}

Try it online!

Xcali

Posted 2018-02-05T13:42:35.040

Reputation: 7 671

Damn, I didn't see your answer. They're basically the same, feel free to use my optimisations and I'll remove my answer. Let me know, if not, I'll just remove this comment :) – Dom Hastings – 2018-02-05T17:04:03.020

@DomHastings They are different enough. Please keep both. I like to see variations in approach. I learn from all of them... – Ton Hospel – 2018-02-05T22:22:21.790

1

Japt, 20 bytes

Running out the door to dinner so more golfing and an explanation to follow.

;£=D·i9òs)æøX)gV+UbX

Try it

Shaggy

Posted 2018-02-05T13:42:35.040

Reputation: 24 623

1

Perl, 59 58 57 56 bytes

Includes + for -p

Give input on STDIN as 2 lines, first the string, then the repeat

(echo 0PLM; echo 2130) | perl -pe '$a="OPQWERTYUILASDF-MZXCVBNM0-90";eval"y/HI$a/J$a/;"x<>'

Ton Hospel

Posted 2018-02-05T13:42:35.040

Reputation: 14 114

Wow, I can't believe you got 29 bytes off mine! I was pretty happy with it originally... – Dom Hastings – 2018-02-09T08:14:15.547

0

Perl 5, 85 bytes

84 bytes code + 1 for -p.

eval join"",map"y/$_/@{[/./&&$'.$&]}/;",(1234567890,QWERTYUIOP,ASDFGHJKL,ZXCVBNM)x<>

Try it online!

Dom Hastings

Posted 2018-02-05T13:42:35.040

Reputation: 16 415

0

Clean, 144 119 bytes

import StdEnv

\n s=[l.[(i+n)rem(size l)]\\c<-s,l<-["1234567890","QWERTYUIOP","ASDFGHJKL","ZXCVBNM"],i<-[0..]&j<-:l|j==c]

Try it online!

Lambda function with the signature Int ![Char] -> [Char]

Οurous

Posted 2018-02-05T13:42:35.040

Reputation: 7 916

0

Ruby, 101 bytes

->s,n{n.times{s.tr! '1234567890QWERTYUIOPASDFGHJKLZXCVBNM','2345678901WERTYUIOPQSDFGHJKLAXCVBNMZ'};s}

Try it online!

I'm honestly a little disappointed that I couldn't do better with 'cleverer' methods. The closest I got was along the lines of

a=%w{1234567890 QWERTYUIOP ASDFGHJKL ZXCVBNM}
b=a.map{|r|r[1..-1]<<r[0]}*''
a*=''
n.times{s.tr! a,b}

for a net gain of 7 characters.

benj2240

Posted 2018-02-05T13:42:35.040

Reputation: 801