Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
bin/configlet
bin/configlet.exe
bin/jq*
exercises/**/bin
_template/bin
CHECKLIST
build
.gradle
Expand Down
16 changes: 14 additions & 2 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@
"basics"
],
"status": "wip"
},
{
"slug": "secrets",
"name": "secrets",
"uuid": "eeb45ce0-2caa-4283-b374-f086a9627da2",
"concepts": [
"bitwise-operations"
],
"prerequisites": [
"numbers"
],
"status":"wip"
}
],
"practice": [
Expand Down Expand Up @@ -1208,15 +1220,15 @@
"practices": [],
"prerequisites": [],
"difficulty": 6,
"status": "deprecated",
"topics": [
"conditionals",
"games",
"integers",
"lists",
"matrices",
"strings"
],
"status": "deprecated"
]
},
{
"slug": "rail-fence-cipher",
Expand Down
18 changes: 18 additions & 0 deletions exercises/concept/secrets/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Hints

## 1. Shift back the bits

- There are two operators for shifting to the right, but only one will always insert a 0.

## 2. Set some bits

- One of the bit wise operators will always set a bit to 1 where the bits in both values are 1.

## 3. Flip specific bits

- There is an bit operation that will flip a bit where the mask is 1.

## 4. Clear specific bits

- One of the bit operations clears bits where the bit in the mask is 0.
- But you may need to combine it with another operator to clear bits where the mask is 1.
58 changes: 58 additions & 0 deletions exercises/concept/secrets/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Instructions

Your friend has just sent you a message with an important secret.
Not wanting to make it easy for others to read it, the message was encrypted by performing a series of bit manipulations.
You will need to write the functions to help decrypt the message.

## 1. Shift back the bits

The first step in decrypting the message is to undo the shifting from the encryption process by shifting the bits back to the right.
There will be further steps in the decryption process that assume 0s are inserted from the left hand side.

Implement the `shiftBack` function that takes a value and the number of places to shift and performs the shift.

```kotlin
shiftBack(0b1001, 2)
# => 0b0010
```

## 2. Set some bits

Next, there are some bits that need to be set to 1.

Implement the `setBits` function that takes a value and a mask and returns the result of setting the bits in value to 1.
A bit from value should be set to 1 where the bit in the mask is also 1.
All other bits should be kept unchanged.

```kotlin
setBits(0b0110, 0b0101);
# => 0b0111
```

## 3. Flip specific bits

Some bits are flipped during encryption.
They will need to be flipped back to decrypt the message.

Implement the `flipBits` function that takes a value and the mask.
The mask indicates which bits in the value to flip.
If the bit is 1 in mask, the bit is flipped in the value.
All other bits are kept unchanged.

```java
flipBits(0b1100, 0b0101);
# => 0b1001
```

## 4. Clear specific bits

Lastly, there are also certain bits that always decrypt to 0.

Implement the `clearBits` function that takes a value and a mask.
The bits in the `value` should be set to 0 where the bit in the mask is 1.
All other bits should be kept unchanged.

```java
clearBits(0b0110, 0b0101);
# => 0b0010
```
96 changes: 96 additions & 0 deletions exercises/concept/secrets/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Introduction

Binary digits ultimately map directly to the transistors in your CPU or RAM, and whether each is "on" or "off".

Low-level manipulation, informally called "bit-twiddling", is particularly important in system languages.

Higher-level languages like Kotlin usually abstract away most of this detail.
However, a basic range of bit-level operations [is available][ref-bitwise].

## Bit-shift operations

`Int` or `Long` types, signed or unsigned, can be represented as a string of 1's and 0's and manipulated as a sequence of bits.

Perhaps surprisingly, `Byte` or `Short` types are not compatible with most bitwise operations in Kotlin.
All the examples below use 32-bit integers (`Int` or `UInt`).

```kotlin
val ns = 0b111 // 7 decimal
Integer.toBinaryString(ns) // => "111"
```

Bit-shifts just move everything to the left or right by a specified number of positions.
Some bits drop off one end, and the other end is padded with zeros or ones.

- Left shift with `shl`: zero-padding.
- Right shift with `shr` or `ushr`: zero padding for positive numbers (see later for negative numbers).

```kotlin
val ns = 0b111 // 7 decimal
ns shl 1 // => 0b1110, decimal 14
ns shr 1 // => 0b11, decimal 3
ns ushr 1 // => 0b11

val nu: UInt = 0b111u // 7 unsigned
nu shr 1 // => 0b11
```

Each left-shift doubles the value, and each right-shift halves it (subject to truncation).
This is more obvious in decimal representation:

```kotlin
3 shl 2 // => 12
24 shr 3 // => 3
```

Such bit-shifting is much faster than "proper" arithmetic, making the technique very popular in low-level coding.

With _negative integers_, we need to be a bit more careful.

Negative values are stored in [two's complement][wiki-2complement] form, which means that the left-most bit is 1.
No problem for a left-shift, but when right-shifting how do we pad the left-most bits?

```kotlin
val ns = -0b111 // => 0b11111111111111111111111111111001

// shift left: simple
ns shl 1 // => 0b11111111111111111111111111110010, decimal -14

// shift right: preserves sign bit
ns shr 1 // => 0b11111111111111111111111111111100, decimal -4

// unsigned shift right: left-pads with zeros
ns ushr 1 // => 0b01111111111111111111111111111100, decimal 2147483644
```

The `shr` operator performs [arithmetic shift][wiki-arithmetic], preserving the sign bit.

The `ushr` operator performs [logical shift][wiki-logical], padding with zeros as if the number was unsigned.

## Bitwise logic

We saw in the [Booleans Concept][concept-booleans] that the operators `&&` (and), `||` (or) and `!` (not) are used with boolean values.

There are equivalent operators `and`, `or`, `xor` ([exclusive-or][wiki-xor]) to compare the bits in two integers, and an `inv` (inversion) function to flip all the bits.

```kotlin
0b1011 and 0b0010 // bit is 1 in both numbers
// => 0b0010

0b1011 or 0b0010 // bit is 1 in at least one number
// => 0b1011

0b1011 xor 0b0010 // bit is 1 in exactly one number, not both
// => 0b1001

0b1011.inv() // flip all bits (in a 32-bit signed Int)
// => 0b11111111111111111111111111110100
```


[ref-bitwise]: https://kotlinlang.org/docs/numbers.html#bitwise-operations
[wiki-xor]: https://en.wikipedia.org/wiki/Exclusive_or
[wiki-2complement]: https://en.wikipedia.org/wiki/Two%27s_complement
[wiki-arithmetic]: https://en.wikipedia.org/wiki/Arithmetic_shift
[wiki-logical]: https://en.wikipedia.org/wiki/Logical_shift
[concept-booleans]: https://exercism.org/tracks/kotlin/concepts/booleans
21 changes: 21 additions & 0 deletions exercises/concept/secrets/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"authors": [
"kahgoh"
],
"files": {
"solution": [
"src/main/kotlin/Secrets.kt"
],
"test": [
"src/test/kotlin/SecretsTest.kt"
],
"exemplar": [
".meta/src/reference/kotlin/Secrets.kt"
]
},
"forked_from": [
"crystal/secrets"
],
"icon": "secrets",
"blurb": "Learn about bit manipulation by writing a program to decrypt a message."
}
15 changes: 15 additions & 0 deletions exercises/concept/secrets/.meta/src/reference/kotlin/Secrets.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
fun shiftBack(value: Int, amount: Int): Int {
return value ushr amount
}

fun setBits(value: Int, mask: Int): Int {
return value or mask
}

fun flipBits(value: Int, mask: Int): Int {
return value xor mask
}

fun clearBits(value: Int, mask: Int): Int {
return value and mask.inv()
}
23 changes: 23 additions & 0 deletions exercises/concept/secrets/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat

plugins {
kotlin("jvm")
}

repositories {
mavenCentral()
}

dependencies {
implementation(kotlin("stdlib-jdk8"))

testImplementation("junit:junit:4.13.2")
testImplementation(kotlin("test-junit"))
}

tasks.withType<Test> {
testLogging {
exceptionFormat = TestExceptionFormat.FULL
events("passed", "failed", "skipped")
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading