CodinGame: Mnożenie kwaternionów - Rust, NodeJS - Parsowanie, Algebra
W tym artykule zobaczymy, jak zaimplementować mnożenie kwaternionów w Rust i NodeJS. Dowiesz się o parsowaniu i algebrze.
Daniel Gustaw
• 17 min read
W tym artykule zobaczymy, jak zaimplementować mnożenie kwaternionów w Rust i NodeJS. Zachęcam do próby rozwiązania tego problemu przed zapoznaniem się z rozwiązaniami. Poniżej zamieszczam link do tego ćwiczenia:
Coding Games and Programming Challenges to Code Better
Kwaterniony należą do systemu liczb, który rozszerza liczby zespolone. Kwaternion jest definiowany przez sumę skalarnych wielokrotności stałych i, j, k i 1. Więcej informacji można znaleźć pod adresem:
Quaternion — from Wolfram MathWorld
Rozważ następujące własności:
jk = i
ki = j
ij = k
i² = j² = k² = -1
Te właściwości implikują również, że:
kj = -i
ik = -j
ji = -k
Kolejność mnożenia ma znaczenie.
Twój program musi wypisać wynik iloczynu liczby uproszczonych kwaternionów w nawiasach.
Zwróć uwagę na formatowanie
Współczynnik jest dołączany po lewej stronie stałej.
Jeśli współczynnik wynosi 1 lub -1, nie dołączaj symbolu 1.
Jeśli współczynnik lub termin skalarowy wynosi 0, nie dołączaj go.
Terminy muszą być wyświetlane w kolejności: ai + bj + ck + d.
Przykład mnożenia
(2i+2j)(j+1) = (2ij+2i+2j² +2j) = (2k+2i-2+2j) = (2i+2j+2k-2)
Wejście:
Linia 1: Wyrażenie expr do oceny. To zawsze będzie iloczyn uproszczonych wyrażeń w nawiasach.
Wyjście: Jedna linia zawierająca uproszczony wynik wyrażenia iloczynu. Nawiasy nie są wymagane.
Ograniczenia: Wszystkie współczynniki w jakiejkolwiek części oceny będą mniejsze niż 10^9
Wejście nie zawiera więcej niż 10 uproszczonych wyrażeń w nawiasach
Przykład
Wejście
(i+j)(k)
Wynik
i-j
Rozwiązanie
Postanowiłem zaprezentować tutaj tylko najważniejsze części. Pełne rozwiązanie można znaleźć w repozytorium:
GitHub - gustawdaniel/codingame-quaternion-multiplication](https://github.com/gustawdaniel/codingame-quaternion-multiplication)
Możemy podzielić nasz problem na trzy kroki:
- analiza wejścia do struktury Quaternion
- mnożenie Quaternionów
- formatowanie Quaterniona z powrotem do ciągu znaków
Te operacje na wysokim poziomie można zaimplementować w NodeJS
import {Quaternion} from "./lib";
process.stdin.on('data', (buff) => {
const line = buff.toString();
const qs = Quaternion.parse(line);
process.stdout.write(qs.reduce((p, n) => p.multiply(n)).format());
})
i w Rust
fn main() {
let mut input_line = String::new();
io::stdin().read_line(&mut input_line).unwrap();
let expr = input_line.trim_matches('\n').to_string();
let qs = Quaternion::parse(&expr);
let out = qs.into_iter().reduce(|p, n| p.multiply(n)).unwrap();
println!("{}", out);
}
Możesz zauważyć, że ten kod jest naprawdę podobny, ale w obu przypadkach musimy zaimplementować Struct/Class
o nazwie Quaternion
. Teraz przejdziemy przez trzy kroki wymienione wcześniej, używając TDD
. Testy są natywnie wspierane w rust, ale w NodeJS
zdecydowałem się użyć jest
jako frameworka do testowania.
Parsowanie wejścia do struktury Quaternion
Nasze wejście
(i+j)(k)
powinno być traktowane jako tablica kwaternionów - oddzielonych nawiasami. W dowolnych nawiasach mamy tablicę współczynników. Możemy więc podzielić nasze parsy na 4 części:
- dzielenie według nawiasów
- dzielenie dowolnego nawiasu na współczynniki
- tworzenie kwaternionów z tablic współczynników
- wyodrębnianie liczby z współczynnika
W NodeJS możemy zacząć od dwóch testów. Pierwszy dla prostych przypadków:
it('simple parse', () => {
const qs = Quaternion.parse('(i+j)');
expect(qs.length).toEqual(1);
expect(qs[0].r).toEqual(0);
expect(qs[0].i).toEqual(1);
expect(qs[0].j).toEqual(1);
expect(qs[0].k).toEqual(0);
});
Drugi dla bardziej zaawansowanych współczynników:
it('complex parse', () => {
const qs = Quaternion.parse('(9+i-j)(k-8.4j)');
expect(qs.length).toEqual(2);
expect(qs[0].r).toEqual(9);
expect(qs[0].i).toEqual(1);
expect(qs[0].j).toEqual(-1);
expect(qs[0].k).toEqual(0);
expect(qs[1].r).toEqual(0);
expect(qs[1].i).toEqual(0);
expect(qs[1].j).toEqual(-8.4);
expect(qs[1].k).toEqual(1);
});
Identyczne testy w rust
można wyrazić jako
#[cfg(test)]
mod tests {
use crate::{Quaternion};
#[test]
fn simple_parse() {
let qs = Quaternion::parse("(i+j)");
assert_eq!(qs.len(), 1);
assert_eq!(qs[0].r, 0f64);
assert_eq!(qs[0].i, 1f64);
assert_eq!(qs[0].j, 1f64);
assert_eq!(qs[0].k, 0f64);
}
#[test]
fn complex_parse() {
let qs = Quaternion::parse("(9+i-j)(k-8.4j)");
assert_eq!(qs.len(), 2);
assert_eq!(qs[0].r, 9f64);
assert_eq!(qs[0].i, 1f64);
assert_eq!(qs[0].j, -1f64);
assert_eq!(qs[0].k, 0f64);
assert_eq!(qs[1].r, 0f64);
assert_eq!(qs[1].i, 0f64);
assert_eq!(qs[1].j, -8.4f64);
assert_eq!(qs[1].k, 1f64);
}
}
Nasza bazowa klasa Quaternion
będzie miała 4
właściwości. W NodeJS:
export class Quaternion {
r: number = 0;
i: number = 0;
j: number = 0;
k: number = 0;
}
gdzie r
oznacza rzeczywistą
część, która dziedziczy arytmetykę z liczb rzeczywistych. W rust
używamy słowa kluczowego struct
zamiast class
#[derive(Debug)]
struct Quaternion {
r: f64,
i: f64,
j: f64,
k: f64,
}
Dzieleniu ciągu za pomocą wyrażeń regularnych
Aby podzielić dane wejściowe i dostarczyć tablice współczynników do konstruktorów Quaternion, możemy napisać metody w NodeJS:
static parse(input: string): Quaternion[] {
const qs = (input.match(/\(.*?\)/g) ?? []).map(
(e: string) => (e
.replace('(', '')
.replace(')', '')
.match(/[-+]?[\d.]*[ijk]?/g) ?? []
).filter(v => v).map(
v => v.replace(/^\+/, '')
)
);
return qs.map((q) => new Quaternion(q));
}
i Rust
impl Quaternion {
fn parse(input: &str) -> Vec<Quaternion> {
let re = Regex::new(r"\((.*?)\)").expect("can't create regex");
let qs = re.captures_iter(input).filter_map(|cap| Some(cap.get(1)?.as_str()))
.map(|m| m.to_string()).collect::<Vec<_>>();
let re = Regex::new(r"\+?(-?[\d.]*[ijk]?)").expect("can't create regex");
let res = qs.iter().map(|q| {
let args = re.captures_iter(&q).filter_map(|cap| Some(cap.get(1)?.as_str()))
.map(|m| m.to_string()).collect::<Vec<_>>();
Quaternion::new(args)
});
res.collect::<Vec<_>>()
}
}
Ogólnie rzecz biorąc, są te same wyrażenia regex, ale rust
wymaga tutaj zewnętrznej biblioteki o nazwie regex
. Dodatkowo rust
sprawdza poprawność wyrażeń regex i zapewnia obsługę błędów w nich, co może być pomijane domyślnie w kodzie node js
. Ogólnie czuję, że podejście node js
do regex jest bardziej czyste i czytelne.
Teraz mamy następujący problem. Nasze współczynniki mogą zawierać liczby, liczby z nazwami komponentów takimi jak: i
, j
lub k
, lub nawet samotne litery takie jak i
, co oznacza 1i
. Istnieją również możliwe znaki, takie jak -k
.
Potrzebujemy kodu, który wyodrębni liczby z nich. Nazwijmy i
, j
lub k
jako typ
, a pełny ciąg współczynnika jako input
. Następnie można uznać wyodrębnienie liczby za:
- usunięcie
typ
zinput
- jeśli reszta nie kończy się na cyfrę, dodaj
1
na końcu (przykład to-
) - w końcu skonwertuj jako float
Implementacja w node js
static getCoefficient(type: string, input: string): number {
const coefficient = input.replace(type, '');
return Number.parseFloat(/\d$/.test(coefficient) ? coefficient : coefficient + '1')
}
i analogiczny w rust
fn get_coefficient(t: &str, input: String) -> f64 {
let c = input.replace(t, "");
if Regex::new(r"\d$").expect("ff").is_match(&c) {
c.parse::<f64>().unwrap()
} else {
(c + "1").parse::<f64>().unwrap()
}
}
W Rust, ogólnie obsługa konwersji między typami wymaga więcej znaków, ale jest bardziej niezawodna. W tym przypadku użycie unwrap
zmusza nas do myślenia o możliwych sposobach radzenia sobie z problemami z analizą.
Teraz możemy przedstawić konstruktory. W nich przekażemy tablicę ciągów znaków z współczynnikami takimi jak 8
, -9k
lub i
. W Node.js
:
constructor(args: Array<string>) {
for (let arg of args) {
if (arg.includes('i')) {
this.i = Quaternion.getCoefficient('i', arg);
} else if (arg.includes('j')) {
this.j = Quaternion.getCoefficient('j', arg);
} else if (arg.includes('k')) {
this.k = Quaternion.getCoefficient('k', arg);
} else {
this.r = Number.parseFloat(arg);
}
}
}
lub w rust
:
fn new(args: Vec<String>) -> Quaternion {
let mut q = Quaternion {
i: 0f64,
j: 0f64,
k: 0f64,
r: 0f64,
};
for arg in args {
if arg.contains("i") {
q.i = Quaternion::get_coefficient("i", arg)
} else if arg.contains("j") {
q.j = Quaternion::get_coefficient("j", arg)
} else if arg.contains("k") {
q.k = Quaternion::get_coefficient("k", arg)
} else {
q.r = arg.parse::<f64>().unwrap()
}
}
q
}
Mnożenie kwaternionów
Mnożenie dwóch kwaternionów jest podobne do mnożenia wielomianów. Najpierw mnożymy ze sobą dowolne pary składników. Następnie możemy grupować je według rodzaju składnika i na końcu dodać współczynniki. Różnica polega na tym, że w mnożeniu wielomianów zawsze dodajemy potęgi, podczas gdy w kwaternionach używamy niekomutatywnej algebry dzielenia przedstawionej w tabeli:
Więc zasadniczo możemy podzielić nasz problem na:
- mnożenie elementów podstawowych
- mnożenie kombinacji liniowych elementów podstawowych
Mnożenie elementów podstawowych
Przepisałem wszystkie możliwe przypadki w pliku testowym w NodeJS
it('multiply base', () => {
expect(Quaternion.multiplyBase('r', 'r')).toEqual({c: 1, d: 'r'});
expect(Quaternion.multiplyBase('r', 'i')).toEqual({c: 1, d: 'i'});
expect(Quaternion.multiplyBase('r', 'j')).toEqual({c: 1, d: 'j'});
expect(Quaternion.multiplyBase('r', 'k')).toEqual({c: 1, d: 'k'});
expect(Quaternion.multiplyBase('i', 'r')).toEqual({c: 1, d: 'i'});
expect(Quaternion.multiplyBase('i', 'i')).toEqual({c: -1, d: 'r'});
expect(Quaternion.multiplyBase('i', 'j')).toEqual({c: 1, d: 'k'});
expect(Quaternion.multiplyBase('i', 'k')).toEqual({c: -1, d: 'j'});
expect(Quaternion.multiplyBase('j', 'r')).toEqual({c: 1, d: 'j'});
expect(Quaternion.multiplyBase('j', 'i')).toEqual({c: -1, d: 'k'});
expect(Quaternion.multiplyBase('j', 'j')).toEqual({c: -1, d: 'r'});
expect(Quaternion.multiplyBase('j', 'k')).toEqual({c: 1, d: 'i'});
expect(Quaternion.multiplyBase('k', 'r')).toEqual({c: 1, d: 'k'});
expect(Quaternion.multiplyBase('k', 'i')).toEqual({c: 1, d: 'j'});
expect(Quaternion.multiplyBase('k', 'j')).toEqual({c: -1, d: 'i'});
expect(Quaternion.multiplyBase('k', 'k')).toEqual({c: -1, d: 'r'});
})
i Rust
#[test]
fn multiply_base() {
assert_eq!(Quaternion::multiply_base('r', 'r'), SignedCoefficient { c: 1f64, d: 'r' });
assert_eq!(Quaternion::multiply_base('r', 'i'), SignedCoefficient { c: 1f64, d: 'i' });
assert_eq!(Quaternion::multiply_base('r', 'j'), SignedCoefficient { c: 1f64, d: 'j' });
assert_eq!(Quaternion::multiply_base('r', 'k'), SignedCoefficient { c: 1f64, d: 'k' });
assert_eq!(Quaternion::multiply_base('i', 'r'), SignedCoefficient { c: 1f64, d: 'i' });
assert_eq!(Quaternion::multiply_base('i', 'i'), SignedCoefficient { c: -1f64, d: 'r' });
assert_eq!(Quaternion::multiply_base('i', 'j'), SignedCoefficient { c: 1f64, d: 'k' });
assert_eq!(Quaternion::multiply_base('i', 'k'), SignedCoefficient { c: -1f64, d: 'j' });
assert_eq!(Quaternion::multiply_base('j', 'r'), SignedCoefficient { c: 1f64, d: 'j' });
assert_eq!(Quaternion::multiply_base('j', 'i'), SignedCoefficient { c: -1f64, d: 'k' });
assert_eq!(Quaternion::multiply_base('j', 'j'), SignedCoefficient { c: -1f64, d: 'r' });
assert_eq!(Quaternion::multiply_base('j', 'k'), SignedCoefficient { c: 1f64, d: 'i' });
assert_eq!(Quaternion::multiply_base('k', 'r'), SignedCoefficient { c: 1f64, d: 'k' });
assert_eq!(Quaternion::multiply_base('k', 'i'), SignedCoefficient { c: 1f64, d: 'j' });
assert_eq!(Quaternion::multiply_base('k', 'j'), SignedCoefficient { c: -1f64, d: 'i' });
assert_eq!(Quaternion::multiply_base('k', 'k'), SignedCoefficient { c: -1f64, d: 'r' });
}
W Rust
muszę zdefiniować SignedCoefficient
, które były prostymi anonimowymi obiektami w node
.
#[derive(Debug)]
struct SignedCoefficient {
c: f64,
d: char,
}
dodatkowo muszę wdrożyć relację równania na nich, aby użyć assert_eq
.
impl PartialEq<SignedCoefficient> for SignedCoefficient {
fn eq(&self, other: &SignedCoefficient) -> bool {
self.c == other.c && self.d == other.d
}
}
Funkcja multiplyBase
jest bardzo prosta i aby ją zbudować, musimy zauważyć, że:
- mnożenie przez 1 zawsze daje drugi element.
a * 1 = a
i1 * a = a
- z wyjątkiem 1 zawsze mamy
a * a = -1
- z wyjątkiem 1 i przekątnej zawsze otrzymujemy współczynnik różny od tych używanych do mnożenia, znak można określić za pomocą operacji
%2
oraz kierunku mnożenia.
Używając tych obserwacji, możemy zdefiniować mnożenie w node
jako
static multiplyBase(a: Base, b: Base): { c: -1 | 1, d: Base } {
if (a === 'r') return {c: 1, d: b};
if (b === 'r') return {c: 1, d: a};
if (a === b) return {c: -1, d: 'r'};
const diff = a.charCodeAt(0) - b.charCodeAt(0);
return {
c: (diff > 0 ? -1 : 1) * ((diff + 2) % 2 === 0 ? -1 : 1) as -1 | 1,
d: ['i', 'j', 'k'].find((e) => e !== a && e !== b) as Base
}
}
i w rust
fn multiply_base(a: char, b: char) -> SignedCoefficient {
if a == 'r' { return SignedCoefficient { c: 1f64, d: b }; }
if b == 'r' { return SignedCoefficient { c: 1f64, d: a }; }
if a == b { return SignedCoefficient { c: -1f64, d: 'r' }; }
let diff = u32::from(a) as i32 - u32::from(b) as i32;
SignedCoefficient {
c: (if diff > 0 { -1f64 } else { 1f64 }) * (if (diff + 2i32) % 2 == 0 { -1f64 } else { 1f64 }),
d: vec!['i', 'j', 'k'].iter().find(|&&e| e != a && e != b).unwrap().to_owned(),
}
}
Mnożenie kombinacji liniowych
Podzieliłem testy mnożenia na przypadki proste i złożone.
it('simple multiply', () => {
const res = (new Quaternion(['1']))
.multiply(new Quaternion(['1']))
expect(res.r).toEqual(1);
expect(res.i).toEqual(0);
expect(res.j).toEqual(0);
expect(res.k).toEqual(0);
})
it('complex multiply', () => {
const res = (new Quaternion(['2i', '2j']))
.multiply(new Quaternion(['j', '1']))
expect(res.r).toEqual(-2);
expect(res.i).toEqual(2);
expect(res.j).toEqual(2);
expect(res.k).toEqual(2);
})
i
#[test]
fn simple_multiply() {
let res = Quaternion::new(vec![String::from("1")])
.multiply(Quaternion::new(vec![String::from("1")]));
assert_eq!(res, Quaternion {
r: 1f64,
i: 0f64,
j: 0f64,
k: 0f64,
})
}
#[test]
fn complex_multiply() {
let res = Quaternion::new(vec![String::from("2i"), String::from("2j")])
.multiply(Quaternion::new(vec![String::from("j"), String::from("1")]));
assert_eq!(res, Quaternion {
r: -2f64,
i: 2f64,
j: 2f64,
k: 2f64,
})
}
Aby porównać kwaterniony, musimy zaimplementować PartialEq
impl PartialEq<Quaternion> for Quaternion {
fn eq(&self, other: &Quaternion) -> bool {
self.r == other.r && self.i == other.i && self.j == other.j && self.k == other.k
}
}
W NodeJS
można to uprościć do zagnieżdżonej pętli w ten sposób
multiply(a: Quaternion): Quaternion {
const res = new Quaternion([]);
for (let p of ['r', 'i', 'j', 'k'] as Array<Base>) {
for (let n of ['r', 'i', 'j', 'k'] as Array<Base>) {
const {c, d} = Quaternion.multiplyBase(p, n);
res[d] += c * this[p] * a[n];
}
}
return res;
}
c
to znak, d
to nazwa współczynnika.
W Rust
nie możemy uzyskać dostępu do dynamicznych właściwości, które są znakami, więc musimy dodać dwie metody pomocnicze, aby uzyskać i ustawić wartości przy użyciu znaków.
fn get(&self, key: char) -> f64 {
match key {
'r' => self.r,
'i' => self.i,
'j' => self.j,
'k' => self.k,
_ => 0f64
}
}
fn set(&mut self, key: char, value: f64) -> &Quaternion {
match key {
'r' => self.r = value,
'i' => self.i = value,
'j' => self.j = value,
'k' => self.k = value,
_ => ()
}
self
}
fn multiply(&self, a: Quaternion) -> Quaternion {
let mut res = Quaternion::new(vec![]);
for p in vec!['r', 'i', 'j', 'k'] {
for n in vec!['r', 'i', 'j', 'k'] {
let SignedCoefficient { c, d } = Quaternion::multiply_base(p, n);
res.set(d, res.get(d) + c * self.get(p) * a.get(n));
}
}
res
}
ale ogólnie rzecz biorąc, pomysł jest taki sam.
Teraz mamy program, który może odczytać dane wejściowe, przekształcić je w tablicę kwaternionów i pomnożyć je.
Ostatnim brakującym elementem jest sformatowanie wyniku jako łańcucha.
Formatowanie kwaternionu do łańcuchów
Formatowanie wyników można rozważać jako:
- formatowanie dowolnego pojedynczego współczynnika z użyciem specjalnych traktacji dla
1
- budowanie uporządkowanej tablicy współczynników, które są łączone w łańcuch
Te operacje są odwrotnością analizy przedstawionej w pierwszej części. Zacznijmy od testów w node js
it('format coefficient', () => {
expect(Quaternion.formatCoefficient('i', 20)).toEqual('20i');
expect(Quaternion.formatCoefficient('i', 1)).toEqual('i');
expect(Quaternion.formatCoefficient('', 0)).toEqual('0');
});
it('format', () => {
expect((new Quaternion([]).format())).toEqual('0');
expect((new Quaternion(['1']).format())).toEqual('1');
expect((new Quaternion(['i', '1']).format())).toEqual('i+1');
expect((new Quaternion(['i', '-3.4j', '1']).format())).toEqual('i-3.4j+1');
expect((new Quaternion(['j', 'k']).format())).toEqual('j+k');
})
Testy analogiczne w rust
#[test]
fn format_coefficient() {
assert_eq!(Quaternion::format_coefficient('i', 20f64), String::from("20i"));
assert_eq!(Quaternion::format_coefficient('i', 1f64), String::from("i"));
assert_eq!(Quaternion::format_coefficient(' ', 0f64), String::from("0"));
}
#[test]
fn format() {
assert_eq!(format!("{}", Quaternion::new(vec![])), String::from("0"));
assert_eq!(format!("{}", Quaternion::new(vec![String::from("1")])), String::from("1"));
assert_eq!(format!("{}", Quaternion::new(vec![String::from("i"), String::from("1")])), String::from("i+1"));
assert_eq!(format!("{}", Quaternion::new(vec![String::from("i"), String::from("-3.4j"), String::from("1")])), String::from("i-3.4j+1"));
assert_eq!(format!("{}", Quaternion::new(vec![String::from("j"), String::from("k")])), String::from("j+k"));
}
W funkcji formatCoefficient
obsługujemy przypadki takie jak 1
, -
i decydujemy, czy nazwy komponentów takie jak i
, j
czy k
powinny być uwzględnione w wyniku.
static formatCoefficient(type: Base | '', value: number) {
const out = `${Math.abs(value) === 1 ? (
Math.sign(value) === 1 ? '' : '-'
) : value}${type}`;
return /[\dijk]$/.test(out) ? out : `${out}1`;
}
i
fn format_coefficient(t: char, value: f64) -> String {
let out = if f64::abs(value) == 1f64 {
if f64::signum(value) == 1f64 {
String::from("") + &t.to_string()[..].trim()
} else {
String::from("-") + &t.to_string()[..].trim()
}
} else {
format!("{}", value) + &t.to_string()[..].trim()
};
match Regex::new(r"[\dijk]$").unwrap().captures(&out[..]) {
Some(_) => out,
None => out + "1"
}
}
}
W funkcji format
zbieramy te składniki i decydujemy, jak je połączyć. Nie możemy łączyć za pomocą +
, ponieważ niektóre elementy zaczynają się od -
. Musimy jednak zająć się przypadkiem 0
. Ostatecznie, w NodeJS
, mamy:
format(): string {
let out = [];
if (this.i) {
out.push(Quaternion.formatCoefficient('i', this.i));
}
if (this.j) {
out.push(Quaternion.formatCoefficient('j', this.j));
}
if (this.k) {
out.push(Quaternion.formatCoefficient('k', this.k));
}
if (this.r) {
out.push(Quaternion.formatCoefficient('', this.r));
}
if (!out.length) return '0';
return out.reduce((p, n) => p + (
p.length && Quaternion.getCoefficient('',n.replace(/[kij]/, '')) > 0 ? `+${n}` : `${n}`), ''
);
}
podczas gdy w implementacji rust
formatowanie może być wykonane przez funkcję fmt
impl fmt::Display for Quaternion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out: Vec<String> = vec![];
if self.i != 0f64 {
out.push(Quaternion::format_coefficient('i', self.i))
}
if self.j != 0f64 {
out.push(Quaternion::format_coefficient('j', self.j))
}
if self.k != 0f64 {
out.push(Quaternion::format_coefficient('k', self.k))
}
if self.r != 0f64 {
out.push(Quaternion::format_coefficient(' ', self.r))
}
let out = out.into_iter().reduce(
|p, n| format!(
"{}{}",
p.clone(),
if p.len() > 0 && Quaternion::get_coefficient(&"", n.replace("i", "").replace("j", "").replace("k", "")) > 0f64 {
format!("{}{}", "+", n)
} else {
n
}
)
);
write!(f, "{}", out.unwrap_or(String::from("0")))
}
}
Ostateczne testy e2e
Aby zweryfikować, że wszystkie komponenty programów odpowiadają sobie nawzajem, możemy przygotować kilka przypadków testowych e2e
w node
it('e2e', () => {
const cases = [
{
in: '(i+j)(k)',
out: 'i-j'
},
{
in: '(i+j+20)(j-9)',
out: '-9i+11j+k-181'
},
{
in: '(10i)(10j-k+1)(-99i+j-10k+7)(4)',
out: '-520i-38920j+6800k+7920'
},
{
in: '(i+j+k+1)(i+2j+4k+8)(i+3j+9k+27)(i+j+8k+8)(i-j+k-10)(99i-j+k-1)(k)(j)(i)(3)',
out: '11415288i-8751432j-5206896k+9766704'
}
]
for (const c of cases) {
const qs = Quaternion.parse(c.in);
const out = qs.reduce((p, n) => p.multiply(n)).format();
expect(out).toEqual(c.out);
}
})
i analogicznie w rust
#[test]
fn e2e() {
struct Case {
input: String,
output: String,
}
let cases: Vec<Case> = vec![
Case {
input: String::from("(i+j)(k)"),
output: String::from("i-j"),
},
Case {
input: String::from("(i+j+20)(j-9)"),
output: String::from("-9i+11j+k-181"),
},
Case {
input: String::from("(10i)(10j-k+1)(-99i+j-10k+7)(4)"),
output: String::from("-520i-38920j+6800k+7920"),
},
Case {
input: String::from("(i+j+k+1)(i+2j+4k+8)(i+3j+9k+27)(i+j+8k+8)(i-j+k-10)(99i-j+k-1)(k)(j)(i)(3)"),
output: String::from("11415288i-8751432j-5206896k+9766704"),
},
];
for c in cases {
let qs = Quaternion::parse(&c.input[..]);
let out = qs.into_iter().reduce(|p, n| p.multiply(n)).unwrap();
assert_eq!(format!("{}", out), c.output);
}
}
To koniec tego ćwiczenia. Jeśli jesteś zainteresowany nauką więcej o mnożeniu kwaternionów i o tym, jak jest to związane z geometrią, polecam ci film:
Jak widać, rust
i typescript
mają wiele podobnych elementów. Wszystkie opisy i elementy logiczne są identyczne, a jedyne różnice można zauważyć na poziomie składni, która w rust jest bardziej skoncentrowana na eliminacji niesprecyzowanych zachowań. Z drugiej strony w typescript kod może być pisany w nieco bardziej zwięzły sposób, co może poprawić czytelność.
Other articles
You can find interesting also.
Bolt (zawsze) Lite - MITM, Proxy, Insomnia i Vue
atak typu man-in-the-middle umożliwiający zamówienie bolt lite za pomocą aplikacji
Daniel Gustaw
• 5 min read
Scraping WordPress - 4300 wyroków sądów w sprawach frankowych bez linii kodu
Nie często się zdarza, żeby wykonanie usługi trwało której, niż jej wycenienie, ale przy scrapingu może się tak stać. Zobacz jak łatwe może być pobranie danych, szczególnie z Wordpressa.
Daniel Gustaw
• 2 min read
Kompilacja interpretera php 7 w BunsenLabs
Kompilacja to proces, który czasami wymaga instalacji paczek lub linkowania zależności. W tym przypadku zadanie polegało na dostarczeniu php7, na system na który nie miał go w dostępnych repozytoriach.
Daniel Gustaw
• 8 min read