CodeWars: Pick Peaks

Table of Contents

Beginning

The problem given is to write a function that returns the location and values of local maxima within a list (array). The inputs will be (possibly empty) lists with integers. The first and last elements cannot be called peaks since we don't know what comes before the first element or after the last element.

Code

Imports

# pypi
from expects import equal, expect

The Submission

def pick_peaks(array: list) -> dict:
    """Find local maxima

    Args:
     array: list of integers to search

    Returns:
     pos, peaks dict of maxima
    """
    output = dict(pos=[], peaks=[])
    peak = position = None

    for index in range(1, len(array)):
        if array[index - 1] < array[index]:
            position = index
            peak = array[index]
        elif peak is not None and array[index - 1] > array[index]:
            output["pos"].append(position)
            output["peaks"].append(peak)
            peak = position = None
    return output
expect(pick_peaks([1,2,3,6,4,1,2,3,2,1])).to(equal({"pos":[3,7], "peaks":[6,3]}))
expect(pick_peaks([3,2,3,6,4,1,2,3,2,1,2,3])).to(equal({"pos":[3,7], "peaks":[6,3]}))
expect(pick_peaks([3,2,3,6,4,1,2,3,2,1,2,2,2,1])).to(equal({"pos":[3,7,10], "peaks":[6,3,2]}))
expect(pick_peaks([2,1,3,1,2,2,2,2,1])).to(equal({"pos":[2,4], "peaks":[3,2]}))
expect(pick_peaks([2,1,3,1,2,2,2,2])).to(equal({"pos":[2], "peaks":[3]}))
expect(pick_peaks([2,1,3,2,2,2,2,5,6])).to(equal({"pos":[2], "peaks":[3]}))
expect(pick_peaks([2,1,3,2,2,2,2,1])).to(equal({"pos":[2], "peaks":[3]}))
expect(pick_peaks([1,2,5,4,3,2,3,6,4,1,2,3,3,4,5,3,2,1,2,3,5,5,4,3])).to(equal({"pos":[2,7,14,20], "peaks":[5,6,5,5]}))
expect(pick_peaks([18, 18, 10, -3, -4, 15, 15, -1, 13, 17, 11, 4, 18, -4, 19, 4, 18, 10, -4, 8, 13, 9, 16, 18, 6, 7])).to(equal({'pos': [5, 9, 12, 14, 16, 20, 23], 'peaks': [15, 17, 18, 19, 18, 13, 18]}))
expect(pick_peaks([])).to(equal({"pos":[],"peaks":[]}))
expect(pick_peaks([1,1,1,1])).to(equal({"pos":[],"peaks":[]}))

CodeWars: Simple Pig Latin

Description

Move the first letter of each word to the end of it, then add "ay" to the end of the word. Leave punctuation marks untouched.

Code

Imports

# pypi
from expects import equal, expect

Submission

import re

LETTERS = re.compile(r"[a-zA-Z]")
WORD_BOUNDARY = re.compile(r"\b")


def convert(token: str) -> str:
    """Convert a single word to pig-latin

    Args:
     token: string representing a single token

    Returns: 
     pig-latinized word (if appropriate)
    """
    return (f"{token[1:]}{token[0]}ay"
            if token and LETTERS.match(token) else token)


def pig_it(text: str) -> str:
    """Basic pig latin converter

    Moves first letter of words to the end and adds 'ay' to the end

    Args:
     text: string to pig-latinize

    Returns:
     pig-latin version of text
    """
    return "".join(convert(token) for token in WORD_BOUNDARY.split(text))
expect(pig_it('Pig latin is cool')).to(equal('igPay atinlay siay oolcay'))
expect(pig_it('This is my string')).to(equal('hisTay siay ymay tringsay'))
expect(pig_it("Hello World !")).to(equal("elloHay orldWay !"))

CodeWars: RGB To Hexadecimal

Description

Given three arguments r, g, and b which are integers from 0 to 255 representing RGB values, convert them to a six digit (zero-padded) hexadecimal string. Invalid (out of range) values need to be rounded to nearest value.

The Code

Imports

# pypi
from expects import equal, expect

Submission

HEX_DIGITS = "0123456789ABCDEF"
RGB_TO_HEX = dict(zip(range(len(HEX_DIGITS)), HEX_DIGITS))
HEX = 16

def rgb(r: int, g: int, b: int) -> str:
    """Convert RGB to Hexadecimal

    If the values are out of bounds they will be set to the nearest limit

    e.g. -1 becomes 0 and 2919 becomes 255

    Non-integers will raise an error

    Args:
     r: red channel (0-255)
     g: green channel (0-255)
     b: blue channel (0-255)

    Returns:
     6-digit hexadecimal equivalent of r, g, b
    """
    colors = (max(min(color, 255), 0) for color in (r, g, b))
    converted = ((RGB_TO_HEX[color//HEX], RGB_TO_HEX[color % HEX])
                 for color in colors)
    return "".join((y for x in converted for y in x))
expect(rgb(0,0,0)).to(equal("000000"))
expect(rgb(1,2,3)).to(equal("010203"))
expect(rgb(255,255,255)).to(equal("FFFFFF"))
expect(rgb(254,253,252)).to(equal("FEFDFC"))
expect(rgb(-20,275,125)).to(equal("00FF7D"))

Alternatives

A surprising number of people used the string formatting - {:02X} to convert the numbers to hexadecimal. I think that's sort of the problem with these earlier puzzles - there's a big question of how much of python's built in functionality you should use. I guess since I use string formatting a lot that might make sense as a shortcut in this case.

def rgb_2(r: int, g: int, b: int) -> str:
    """Convert RGB to Hexadecimal

    If the values are out of bounds they will be set to the nearest limit

    e.g. -1 becomes 0 and 2919 becomes 255

    Non-integers will raise an error

    Args:
     r: red channel (0-255)
     g: green channel (0-255)
     b: blue channel (0-255)

    Returns:
     6-digit hexadecimal equivalent of r, g, b
    """
    return "".join((f"{max(min(color, 255), 0):02X}" for color in (r, g, b)))
expect(rgb_2(0,0,0)).to(equal("000000"))
expect(rgb_2(1,2,3)).to(equal("010203"))
expect(rgb_2(255,255,255)).to(equal("FFFFFF"))
expect(rgb_2(254,253,252)).to(equal("FEFDFC"))
expect(rgb_2(-20,275,125)).to(equal("00FF7D"))

Not quite so readable, but short.

CodeWars: Rot13

Description

Given a string, replace each letter with the one that comes 13 letters after it in the alphabet. Ignore non-English aphabetical characters.

The Code

Imports

# python
from string import ascii_lowercase as lowercase
from string import ascii_uppercase as uppercase

# pypi
from expects import equal, expect

The Submitted Function

This is the version I submitted to CodeWars. It uses the dict update method to build a dict (although the solutions below seem neater) and the get method to handle the cases where the letter in the message isn't in the dictionary.

def rot13(message: str) -> str:
    """Implement a Caesar Cipher by shifting letters 13 places

    Non-english letters are left as-is

    Args:
     message: string to encode

    Return:
     the encoded version of the input string
    """
    code = {letter: lowercase[(index + 13) % 26] 
             for index, letter in enumerate(lowercase)}
    code.update((letter, uppercase[(index + 13) % 26])
                  for index, letter in enumerate(uppercase))
    return "".join(code.get(letter, letter) for letter in message)

A Test

def tester(encoder):
    inputs = ("test", "Test", "Test5")
    expecteds = ("grfg", "Grfg", "Grfg5")

    for message, expected in zip(inputs, expecteds):
        encoded = encoder(message)
        expect(encoded).to(equal(expected))
        expect(encoder(encoded)).to(equal(message))
    return

tester(rot13)

Alternatives

Quite a few of the other solutions (on the first page, anyway) used the built in str.maketrans and str.translate methods (they complement each other). I didn't see anything in the documentation about how defaults are handled so I'd have to look into it more. The top answer also used slicing instead of modulo (lower[13:] + lower[:13]) which might be better. The comments mention that the top answer actually won't work anymore since the maketrans and translate functions got moved out of string (which is where it's importing it from).

The top solutions seem to have a mix of current python and deprecated python (python 2?) so you'd have to be careful in using any of them.

Using the Slicing

If you were use slicing instead of the modulo I think it might look like this (from here on out I'm going to declare the code-books outside the function the way I would normally do it, I kind of didn't really think about it when submitting the solution above so I'll leave it as is).

CODE = dict(zip(lowercase + uppercase,
                lowercase[13:] + lowercase[:13] +
                uppercase[13:] + uppercase[:13]))

def rot13_2(message: str) -> str:
    """Implement a Caesar Cipher by shifting letters 13 places

    Non-english letters are left as-is

    Args:
     message: string to encode

    Return:
     the encoded version of the input string
    """
    return "".join(CODE.get(letter, letter) for letter in message)
tester(rot13_2)

This is more compact, although I'm not sure that the slicing is as immediately obvious as the use of the modulo is.

Using translate and maketrans

Here's a version using the built-in maketrans and translate functions.

CODE = str.maketrans(lowercase + uppercase,
                     lowercase[13:] + lowercase[:13] +
                     uppercase[13:] + uppercase[:13])


def rot13_3(message: str) -> str:
    """Implement a Caesar Cipher by shifting letters 13 places

    Non-english letters are left as-is

    Args:
     message: string to encode

    Return:
     the encoded version of the input string
    """
    return message.translate(CODE)
tester(rot13_3)
coded = rot13_3("I have been to paradise 3 times, but I have never been to me. "
                "Oh, the humanity!")
print(coded)
print(rot13_3(coded))
V unir orra gb cnenqvfr 3 gvzrf, ohg V unir arire orra gb zr. Bu, gur uhznavgl!
I have been to paradise 3 times, but I have never been to me. Oh, the humanity!

It kind of seems too much to use translate for this exercise, but it does feel cleaner than the dictionary, so I'll have to keep it in mind for the future.