AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79564589
Accepted
Ξένη Γήινος
Ξένη Γήινος
Asked: 2025-04-09 22:19:47 +0800 CST2025-04-09 22:19:47 +0800 CST 2025-04-09 22:19:47 +0800 CST

Como encontrar todos os pontos da grade que correspondem a frações não reduzidas em um quadrado?

  • 772

Dado um inteiro positivo N, podemos rotular todos os pontos da grade no quadrado N x N, começando em 1, o número total de pontos da grade é N x N, e os pontos da grade são list(itertools.product(range(1, N + 1), repeat=2)).

Agora, quero encontrar todas as tuplas (x, y)que satisfazem a condição x/y ser uma fração não reduzida. A seguir, uma implementação de força bruta que é garantidamente correta, mas é muito ineficiente:

import math
from itertools import product


def find_complex_points(lim: int) -> list[tuple[int, int]]:
    return [
        (x, y)
        for x, y in product(range(1, lim + 1), repeat=2)
        if math.gcd(x, y) > 1
    ]

Agora, a próxima função é um pouco mais inteligente, mas gera duplicatas e, como resultado, é apenas visivelmente mais rápida, mas não muito:

def find_complex_points_1(lim: int) -> set[tuple[int, int]]:
    lim += 1
    return {
        (x, y)
        for mult in range(2, lim)
        for x, y in product(range(mult, lim, mult), repeat=2)
    }
In [255]: %timeit find_complex_points(1024)
233 ms ± 4.44 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [256]: %timeit find_complex_points_1(1024)
194 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Existe uma maneira melhor de fazer isso?

(Meu objetivo é simples, quero criar uma matriz NumPy 2D do tipo uint8 com a forma (N, N), preenchê-la com 255 e tornar todos os pixels (x, y) 0 se (x+1)/(y+1) for uma fração não reduzida)


Eu criei um método que é muito mais inteligente do que os meus dois anteriores, e também tremendamente mais rápido, mas ele ainda gera duplicatas. Optei por não usar setaqui para que você possa copiar e colar o código como está e executar alguns testes e ver a saída exata na ordem em que são gerados:

def find_complex_points_2(lim: int) -> set[tuple[int, int]]:
    stack = dict.fromkeys(range(lim, 1, -1))
    lim += 1
    points = []
    while stack:
        x, _ = stack.popitem()
        points.append((x, x))
        mults = []
        for y in range(x * 2, lim, x):
            stack.pop(y, None)
            mults.append(y)
            points.extend([(x, y), (y, x)])
        
        for i, x in enumerate(mults):
            points.append((x, x))
            for y in mults[i + 1:]:
                points.extend([(x, y), (y, x)])
    
    return points
In [292]: sorted(set(find_complex_points_2(1024))) == find_complex_points(1024)
Out[292]: True

In [293]: %timeit find_complex_points_2(1024)
58.9 ms ± 580 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [294]: %timeit find_complex_points(1024)
226 ms ± 3.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Para esclarecer, a saída de find_complex_points_2(10)é:

In [287]: find_complex_points_2(10)
Out[287]:
[(2, 2),
 (2, 4),
 (4, 2),
 (2, 6),
 (6, 2),
 (2, 8),
 (8, 2),
 (2, 10),
 (10, 2),
 (4, 4),
 (4, 6),
 (6, 4),
 (4, 8),
 (8, 4),
 (4, 10),
 (10, 4),
 (6, 6),
 (6, 8),
 (8, 6),
 (6, 10),
 (10, 6),
 (8, 8),
 (8, 10),
 (10, 8),
 (10, 10),
 (3, 3),
 (3, 6),
 (6, 3),
 (3, 9),
 (9, 3),
 (6, 6),
 (6, 9),
 (9, 6),
 (9, 9),
 (5, 5),
 (5, 10),
 (10, 5),
 (10, 10),
 (7, 7)]

Como você pode ver, (10, 10)aparece duas vezes. Quero evitar cálculos redundantes.

Isso também acontece em find_complex_points_1, se eu não usar um conjunto, muitas duplicatas serão incluídas, porque o método usado inevitavelmente as gerará repetidamente, ao usar um conjunto ainda há computação desnecessária, ele simplesmente não coleta as duplicatas.

E não, na verdade eu quero que as coordenadas sejam substituídas pela soma de todos os números anteriores, então N é substituído por (N 2 + N) / 2.


Acabei de implementar a geração de imagens para ilustrar melhor o que quero:

import numpy as np
import numba as nb


@nb.njit(cache=True)
def resize_img(img: np.ndarray, h_scale: int, w_scale: int) -> np.ndarray:
    height, width = img.shape
    result = np.empty((height, h_scale, width, w_scale), np.uint8)
    result[...] = img[:, None, :, None]
    return result.reshape((height * h_scale, width * w_scale))


def find_composite_points(lim: int) -> set[tuple[int, int]]:
    stack = dict.fromkeys(range(lim, 1, -1))
    lim += 1
    points = set()
    while stack:
        x, _ = stack.popitem()
        points.add((x, x))
        mults = []
        for y in range(x * 2, lim, x):
            stack.pop(y, None)
            mults.append(y)
            points.update([(x, y), (y, x)])

        for i, x in enumerate(mults):
            points.add((x, x))
            for y in mults[i + 1 :]:
                points.update([(x, y), (y, x)])

    return points


def natural_sum(n: int) -> int:
    return (n + 1) * n // 2


def composite_image(lim: int, scale: int) -> np.ndarray:
    length = natural_sum(lim)
    img = np.full((length, length), 255, dtype=np.uint8)
    for x, y in find_composite_points(lim):
        x1, y1 = natural_sum(x - 1), natural_sum(y - 1)
        img[x1 : x1 + x, y1 : y1 + y] = 0

    return resize_img(img, scale, scale)

composite_image(12, 12)

insira a descrição da imagem aqui

python
  • 4 4 respostas
  • 240 Views

4 respostas

  • Voted
  1. Best Answer
    Jérôme Richard
    2025-04-10T07:35:23+08:002025-04-10T07:35:23+08:00

    O algoritmo do Weeble é executado em O(n² m²)onde mé o tamanho dos inteiros em bits (usando uma multiplicação ingênua). Como podemos assumir que a multiplicação de números é feita em tempo constante (devido aos inteiros nativos limitados usados ​​pelo Numpy), isso significa, O(n²)mas com uma constante oculta significativa que não deve ser negligenciada. Em termos de desempenho, o algoritmo é limitado por operações de falha de página ineficientes e pelo preenchimento de grandes matrizes temporárias. Está longe de ser ótimo.

    O algoritmo de Pete Kirkham deve ser executado O(n²)(embora seja difícil de provar formalmente) com uma constante oculta relativamente pequena. Esta é uma boa abordagem. No entanto, é muito lento devido à ineficiência das operações Numpy escalares, em vez das vetorizadas. Felizmente, ele pode ser facilmente vetorizado:

    array = np.full((N,N), 255, np.uint8)
    for d in range(2, N+1):
        array[d-1:N:d, d-1:N:d] = 0
    

    Observe que corrigi a implementação para retornar resultados corretos (com valores 0 e 255).

    Uma solução alternativa muito simples é usar o Numba para acelerar o código de Pete Kirkham. Dito isso, o código não é eficiente porque itera em itens de linhas diferentes no loop mais interno. Podemos corrigir isso facilmente trocando as variáveis:

    import numba as nb
    
    @nb.njit('(int32,)')
    def compute(N):
        array = np.full((N,N), 255, np.uint8)
        for denominator in range(2, N+1):
            for i in range(denominator, N+1, denominator):
                for j in range(denominator, N+1, denominator):
                    array[i-1, j-1] = 0
        return array
    

    Abordagem alternativa mais rápida

    Observe que a matriz de saída é simétrica, então nem precisamos calcular a parte inferior esquerda. De fato, gcd(a, b) == gcd(b, a). Infelizmente, não acho que possamos usar essa propriedade para tornar o código Numpy vetorizado, mas provavelmente podemos tornar o código Numba mais rápido.

    Além disso, a diagonal pode ser trivialmente definida como 0 (exceto o primeiro item), pois se gcd(a, a) == a. Tecnicamente, também podemos trivialmente definir a vizinhança direta da diagonal (ie ) como 255, já que . deve ser preenchido com valores alternados (ie ) já que . Uma estratégia semelhante pode ser aplicada para . Para outras diagonais, a situação começa a ficar mais complexa, pois certamente precisamos fatorar números. Fatorar números é sabidamente caro, mas esse custo é amortizado aqui, já que precisamos fazer isso várias vezes.gcd(a, a) > 1a > 1array.diagonal(1)gcd(a, a-1) = 1array.diagonal(1)[255, 0, 255, 0, ...]gcd(a, a-2) = gcd(a, 2) = 2 - (a % 2)array.diagonal(2)O(n)

    Outra simetria do mdc é gcd(a, b) = gcd(a, b-a) = gcd(a-b, b).

    Podemos aproveitar toda essa simetria do MDC para escrever uma implementação significativamente mais rápida usando programação dinâmica . Uma implementação ingênua (que combina todas as simetrias de forma bastante eficiente) é a seguinte:

    @nb.njit('(int32,)')
    def compute_v2(n):
        arr = np.empty((n,n), np.uint8)
        arr[:, 0] = 255
        arr[0, :] = 255
        for i in range(1, n):
            for j in range(1, i):
                arr[i, j] = arr[j, i-j-1]  # <--- very slow part
            arr[i, i] = 0
            for j in range(i+1, n):
                arr[i, j] = arr[i, j-i-1]
        return arr
    

    Infelizmente, a transposição é bastante ineficiente e leva quase todo o tempo... Otimizar é possível, mas não é fácil. Podemos dividir a computação em blocos dependentes (semelhante ao funcionamento do algoritmo de decomposição de LU em bloco ). Isso torna o código muito mais complexo e rápido, graças a um padrão de acesso mais eficiente:

    # Compute the tile arr[start:stop,start:stop].
    # Assume arr[:start,:start] has been already computed.
    # Assume start and stop are valid.
    @nb.njit('(uint8[:,::1], uint32, uint32)', inline='always')
    def compute_diag_tile(arr, start, stop):
        for i in range(start, stop):
            for j in range(start, i):
                arr[i, j] = arr[j, i-j-1]
            arr[i, i] = 0
            for j in range(i+1, stop):
                arr[i, j] = arr[i, j-i-1]
    
    # Compute the tile arr[start:stop,stop:].
    # Assume arr[start:stop,:stop] has been already computed.
    # Assume start and stop are valid.
    @nb.njit('(uint8[:,::1], uint32, uint32)', inline='always')
    def compute_upper_right_tile(arr, start, stop):
        n = np.uint32(arr.shape[1])
        for i in range(start, stop):
            for j in range(stop, n):
                arr[i, j] = arr[i, np.uint64(j-i-1)]
    
    # Compute the tile arr[stop:,start:stop].
    # Assume arr[start:stop,stop:] has been already computed; that is to say
    # compute_upper_right_tile has been called on the associated diag tile.
    # This function transposes the tile written by compute_upper_right_tile.
    # Assume start and stop are valid.
    @nb.njit('(uint8[:,::1], uint32, uint32)', inline='always')
    def compute_bottom_left_tile(arr, start, stop):
        n = np.uint32(arr.shape[0])
        for i in range(stop, n):
            for j in range(start, stop):
                arr[i, j] = arr[j, i]
    
    @nb.njit('(uint8[:,::1], uint32, uint32)', inline='always')
    def compute_tile_group(arr, start, stop):
        compute_diag_tile(arr, start, stop)
        compute_upper_right_tile(arr, start, stop)
        compute_bottom_left_tile(arr, start, stop)
    
    @nb.njit('(uint32,)')
    def compute_v3(n):
        chunk_size = 32
        arr = np.empty((n, n), np.uint8)
        arr[0, :] = 255
        arr[:, 0] = 255
        for start in range(1, n, chunk_size):
            if start + chunk_size <= n:
                compute_tile_group(arr, start, start + chunk_size)
            else:
                compute_tile_group(arr, start, n)
        return arr
    

    A transposição ainda é a parte mais lenta deste código. Ela pode ser otimizada ainda mais, mas às custas de um código significativamente maior. Prefiro manter isso razoavelmente simples, mas observe que uma maneira de tornar a transposição muito mais rápida é usar intrínsecos SIMD (certamente pelo menos >2 vezes mais rápido).


    Referência

    Aqui estão os resultados para N=1024minha máquina (CPU i5-9600KF):

    find_complex_points:                173    ms
    PeteKirkham's code:                 108    ms
    find_complex_points_1:               99    ms
    Weeble's code:                       70    ms
    find_complex_points_2:               38    ms
    Vectorized Numpy code:                4.0  ms    <-----
    PeteKirkham's code with Numba:        2.5  ms
    Numba code `compute_v2`:              0.70 ms
    Numba code `compute`:                 0.66 ms
    Numba code `compute_v3`:              0.45 ms    <-----
    find_complex_points_3:                0.44 ms
    

    O código Numba vetorizado é muito mais rápido que a outra implementação e os códigos Numba otimizados superam todas as implementações por uma grande margem (exceto o novo find_complex_points_3)!

    É possível paralelizar alguns códigos do Numba para torná-los ainda mais rápidos, mas isso não é trivial e certamente é rápido o suficiente de qualquer maneira, sem mencionar que não escala bem porque o código é bastante limitado pela memória para grandes N. Na verdade, uma cópia básica do Numpy leva cerca de 0,3 ms, o que pode ser considerado um tempo de execução de limite inferior.

    • 8
  2. Pete Kirkham
    2025-04-10T00:25:22+08:002025-04-10T00:25:22+08:00

    Como o que você quer é um array numpy, uma abordagem diferente seria começar com o array e usar algo como o crivo de Eratóstenes para marcar aqueles que não são reduzidos:

    array = np.full((N,N), 1, np.uint8)
    
    for denominator in range(2, N+1):
        for i in range(denominator, N+1, denominator):
            for j in range(denominator, N+1, denominator):
                array[j-1, i-1] = 0
    
    
    • 4
  3. Weeble
    2025-04-10T00:42:25+08:002025-04-10T00:42:25+08:00

    Até mesmo o algoritmo mais simples funciona muito mais rápido se você usar o numpy para todos os cálculos. Os loops e cálculos do Numpy são muito mais rápidos que os do CPython. Você pode usar numpy.gcd para os cálculos do MDC. Algo assim:

    In [13]: np.where(np.gcd(np.arange(1,1025), np.arange(1,1025)[:,np.newaxis]) > 1, np.ubyte(0) , np.ubyte(255))
    Out[13]:
    array([[255, 255, 255, ..., 255, 255, 255],
           [255,   0, 255, ...,   0, 255,   0],
           [255, 255,   0, ..., 255,   0, 255],
           ...,
           [255,   0, 255, ...,   0, 255,   0],
           [255, 255,   0, ..., 255,   0, 255],
           [255,   0, 255, ...,   0, 255,   0]],
          shape=(1024, 1024), dtype=uint8)
    

    Para mim, isso é cerca de 7 vezes mais rápido que o original find_complex_pointse cerca de 50% mais rápido que o find_complex_points_2. Tenho certeza de que um algoritmo inteligente pode fazer ainda melhor, mas isso parece um bom ponto de complexidade/desempenho.

    • 3
  4. Ξένη Γήινος
    2025-04-10T11:33:49+08:002025-04-10T11:33:49+08:00

    Eu consegui, vetorizei meu algoritmo mais inteligente usando o Numba. Agora, como o resultado deveria ser um array NumPy, eu deveria gerar um array diretamente.

    Agora, usar matrizes bidimensionais é ineficiente, usei uma matriz plana para garantir que o acesso à memória seja contínuo.

    A segunda otimização que fiz foi, em vez de usar um dicionário e remover repetidamente números compostos, posso simplesmente usar uma matriz para rastrear quais números são marcados como compostos, o que garante que a variável de estado não mude de tamanho.

    E então, uma grande fonte de duplicatas são as tuplas (x, x), que têm a garantia de serem geradas múltiplas vezes se o número composto tiver múltiplos divisores, então, em vez de gerar as tuplas repetidamente, simplesmente não as gere e gere todas essas tuplas no final para garantir que cada uma delas seja gerada exatamente uma vez.

    Entretanto, isso não garante que nenhuma duplicata seja gerada. Não tenho ideia do porquê ainda pode haver duplicatas, mas como estou usando uma matriz NumPy, isso não importa.


    Código

    import numpy as np
    import numba as nb
    
    
    @nb.njit(cache=True)
    def find_composite_points_2(lim: int) -> np.ndarray:
        lim += 1
        primes = np.full(lim, 1, dtype=np.uint8)
        primes[:2] = 0
        result = np.full(lim**2, 255, dtype=np.uint8)
        for n in range(2, lim):
            if primes[n]:
                mults = np.arange(2 * n, lim, n)
                for i, x in enumerate(mults):
                    primes[x] = 0
                    result[n * lim + x] = 0
                    result[x * lim + n] = 0
                    for y in mults[i + 1:]:
                        result[y * lim + x] = 0
                        result[x * lim + y] = 0
        
        for x in range(2, lim):
            result[x * lim + x] = 0
        
        return result.reshape(lim, lim)
    

    É mais rápido do que meu método anterior por uma margem muito ampla, porém ainda é menos eficiente do que o método mais rápido de Jérôme Richard :

    In [367]: %timeit find_composite_points_2(1024)
    1.4 ms ± 8.21 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
    
    In [368]: %timeit compute(1024)
    842 μs ± 8.17 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
    

    Mas acho que posso otimizá-lo ainda mais.

    E um exemplo de imagem de saída:

    insira a descrição da imagem aqui


    Eu me superei mais uma vez. Em vez de usar um array unidimensional, usei um array bidimensional aqui. Porque confirmei experimentalmente que usar um array unidimensional é de alguma forma menos eficiente do que usar um array bidimensional, possivelmente por causa de .reshape.

    Agora, em vez de usar um método inteligente e evitar gerar duplicatas, basta fazer força bruta e atribuir os múltiplos a 0 um por um. Os valores atribuídos permanecem atribuídos e, no NumPy, a atribuição pode ser feita usando fatiamento, que é vetorizado e muito rápido.

    Mas a principal otimização que economiza muito tempo é aplicar força bruta somente a números primos, porque não há necessidade alguma de aplicar força bruta a números compostos, já que eles são garantidos para serem processados ​​ao processar pelo menos um número primo, e há muito mais números compostos do que primos para qualquer intervalo finito dado, essa otimização simples economiza muito tempo.

    @nb.njit(cache=True)
    def find_composite_points_3(lim: int) -> np.ndarray:
        lim += 1
        primes = np.full(lim, 1, dtype=np.uint8)
        primes[:2] = 0
        result = np.full((lim, lim), 255, dtype=np.uint8)
        for n in range(2, lim):
            if primes[n]:
                primes[2 * n::n] = 0
                for x in range(n, lim, n):
                    result[x, n::n] = 0
        
        return result
    
    In [394]: %timeit find_composite_points_2(1024)
    1.39 ms ± 9.97 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
    
    In [395]: %timeit compute(1024)
    839 μs ± 4.92 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
    
    In [396]: %timeit find_composite_points_3(1024)
    505 μs ± 9.12 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
    

    Isso pode, é claro, ser combinado com uma matriz de primos pré-gerados, basta fazer uma peneira de roda, mas em vez de obter os índices de 1, basta usar a matriz booleana como está, então o primeiro loop na segunda função pode ser usado nb.prange(2, lim)para paralelizar, o que seria ainda mais rápido, mas já há tanto código que não postarei a última otimização.

    • 3

relate perguntas

  • Como divido o loop for em 3 quadros de dados individuais?

  • Como verificar se todas as colunas flutuantes em um Pandas DataFrame são aproximadamente iguais ou próximas

  • Como funciona o "load_dataset", já que não está detectando arquivos de exemplo?

  • Por que a comparação de string pandas.eval() retorna False

  • Python tkinter/ ttkboostrap dateentry não funciona quando no estado somente leitura

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve