Tìm kiếm từ tối thiểu


18

Tuần trước, chúng tôi đã làm việc để tạo ra chuỗi 1-D ngắn nhất bằng cách sử dụng 10.000 từ hàng đầu trong ngôn ngữ tiếng Anh . Bây giờ, hãy thử thách tương tự trong 2D!

Những gì bạn cần làm là lấy tất cả các từ trên, và đặt chúng vào một hình chữ nhật càng nhỏ càng tốt, cho phép chồng chéo. Ví dụ: nếu từ của bạn là ["ape","pen","ab","be","pa"], thì một hình chữ nhật có thể sẽ là:

.b..
apen

Hình chữ nhật trên sẽ cho điểm 5.

Quy tắc:

  • Chồng chéo nhiều chữ cái trong một từ được cho phép
  • Các từ có thể đi theo bất kỳ hướng nào trong 8 hướng
  • Từ ngữ không thể bao bọc xung quanh
  • Bạn có thể sử dụng bất kỳ ký tự nào cho các vị trí trống

Bạn cần tạo một tìm kiếm từ có chứa 10.000 từ hàng đầu bằng tiếng Anh (theo Google). Điểm của bạn bằng số lượng ký tự trong tìm kiếm từ của bạn (không bao gồm các ký tự không được sử dụng). Nếu có một sự ràng buộc, hoặc nếu một bài nộp được chứng minh là tối ưu, thì bài nộp được đăng đầu tiên sẽ thắng.


1
Tôi muốn lưu ý rằng tôi biết về thử thách tìm kiếm từ trước đó , nhưng cho rằng không có câu trả lời nào ở đó sẽ chạy trong một khoảng thời gian hợp lý cho thử thách này, tôi không tin đó là một bản sao.
Nathan Merrill


Tôi sợ giải pháp tối ưu sẽ trở thành lưới nx 1, khiến vấn đề này cuối cùng giống như vấn đề cuối cùng (lý do: các giao điểm tiếp tuyến sẽ hiếm khi lưu nhiều ký tự nhưng thường sẽ giới thiệu "lỗ hổng", lãng phí không gian). Có lẽ bạn nên chấm điểm trên chiều rộng + chiều cao, thay vì chiều rộng * chiều cao, để nó ủng hộ mạnh mẽ các giải pháp vuông (thú vị hơn).
Dave

Hmmm ... Tôi sợ rằng các giải pháp đơn giản sẽ là các chuỗi từ xếp chồng lên nhau. Tôi nghĩ rằng không ghi được các vị trí trống có thể là một ý tưởng hay
Nathan Merrill

Rủi ro với điều đó là không cần phải giữ kích thước lưới nhỏ; một lưới 1000x1000 với một danh sách ngang và dọc trải dài sẽ ghi điểm giống như một mô hình xoắn ốc được thắt chặt / tương tự. Có thể thử chiều rộng + chiều cao, sau đó loại trừ các chữ cái làm khoảng trống? Có thể cần thêm một chút suy nghĩ. Chỉnh sửa: hoặc có thể loại trừ các chữ cái trước tiên sau đó chiều rộng + chiều cao như một bộ ngắt kết nối sẽ hoạt động tốt hơn.
Dave

Câu trả lời:


7

Rust, 31430 30081 ký tự được sử dụng

Đây là một thuật toán tham lam sắp xếp: chúng tôi bắt đầu với một lưới trống và liên tục thêm từ có thể được thêm bằng một vài chữ cái mới nhất, với các mối quan hệ bị phá vỡ bằng cách thích các từ dài hơn. Để thực hiện việc này một cách nhanh chóng, chúng tôi duy trì hàng đợi ưu tiên của các vị trí từ ứng cử viên (được triển khai dưới dạng vectơ của các vectơ, với một vectơ cho mỗi số chữ cái mới, chứa một deque cho mỗi độ dài từ). Đối với mỗi chữ cái mới được thêm vào, chúng tôi liệt kê tất cả các vị trí ứng cử viên chạy qua chữ cái đó.

Biên dịch và chạy với rustc -O wordsearch.rs; ./wordsearch < google-10000-english.txt. Trên máy tính xách tay của tôi, điều này chạy trong 70 giây, sử dụng RAM 531 MiB.

Đầu ra vừa vặn trong một hình chữ nhật với 248 cột và 253 hàng.

nhập mô tả hình ảnh ở đây

use std::collections::{HashMap, HashSet, VecDeque};
use std::io::prelude::*;
use std::iter::once;
use std::vec::Vec;

type Coord = i16;
type Pos = (Coord, Coord);
type Dir = u8;
type Word = u16;

struct Placement { word: Word, dir: Dir, pos: Pos }

static DIRS: [Pos; 8] =
    [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)];

fn fit(grid: &HashMap<Pos, u8>, (x, y): Pos, d: Dir, word: &String) -> Option<usize> {
    let (dx, dy) = DIRS[d as usize];
    let mut n = 0;
    for (i, c) in word.bytes().enumerate() {
        if let Some(c1) = grid.get(&(x + (i as Coord)*dx, y + (i as Coord)*dy)) {
            if c != *c1 {
                return None;
            }
        } else {
            n += 1;
        }
    }
    return Some(n)
}

struct PlacementQueue { queue: Vec<Vec<VecDeque<Placement>>>, extra: usize }

impl PlacementQueue {
    fn new() -> PlacementQueue {
        return PlacementQueue { queue: Vec::new(), extra: std::usize::MAX }
    }

    fn enqueue(self: &mut PlacementQueue, extra: usize, total: usize, placement: Placement) {
        while self.queue.len() <= extra {
            self.queue.push(Vec::new());
        }
        while self.queue[extra].len() <= total {
            self.queue[extra].push(VecDeque::new());
        }
        self.queue[extra][total].push_back(placement);
        if self.extra > extra {
            self.extra = extra;
        }
    }

    fn dequeue(self: &mut PlacementQueue) -> Option<Placement> {
        while self.extra < self.queue.len() {
            let mut subqueue = &mut self.queue[self.extra];
            while !subqueue.is_empty() {
                let total = subqueue.len() - 1;
                if let Some(placement) = subqueue[total].pop_front() {
                    return Some(placement);
                }
                subqueue.pop();
            }
            self.extra += 1;
        }
        return None
    }
}

fn main() {
    let stdin = std::io::stdin();
    let all_words: Vec<String> =
        stdin.lock().lines().map(|l| l.unwrap()).collect();
    let words: Vec<&String> = {
        let subwords: HashSet<&str> =
            all_words.iter().flat_map(|word| {
                (0..word.len() - 1).flat_map(move |i| {
                    (i + 1..word.len() - (i == 0) as usize).map(move |j| {
                        &word[i..j]
                    })
                })
            }).collect();
        all_words.iter().filter(|word| !subwords.contains(&word[..])).collect()
    };
    let letters: Vec<Vec<(usize, usize)>> =
        (0..128).map(|c| {
            words.iter().enumerate().flat_map(|(w, word)| {
                word.bytes().enumerate().filter(|&(_, c1)| c == c1).map(move |(i, _)| (w, i))
            }).collect()
        }).collect();

    let mut used = vec![false; words.len()];
    let mut remaining = words.len();
    let mut grids: Vec<HashMap<Pos, u8>> = Vec::new();

    while remaining != 0 {
        let mut grid: HashMap<Pos, u8> = HashMap::new();
        let mut queue = PlacementQueue::new();
        for (w, word) in words.iter().enumerate() {
            if used[w] {
                continue;
            }
            queue.enqueue(0, word.len(), Placement {
                pos: (0, 0),
                dir: 0,
                word: w as Word
            });
        }

        while let Some(placement) = queue.dequeue() {
            if used[placement.word as usize] {
                continue;
            }
            let word = words[placement.word as usize];
            if let None = fit(&grid, placement.pos, placement.dir, word) {
                continue;
            }
            let (x, y) = placement.pos;
            let (dx, dy) = DIRS[placement.dir as usize];
            let new_letters: Vec<(usize, u8)> = word.bytes().enumerate().filter(|&(i, _)| {
                !grid.contains_key(&(x + (i as Coord)*dx, y + (i as Coord)*dy))
            }).collect();
            for (i, c) in word.bytes().enumerate() {
                grid.insert((x + (i as Coord)*dx, y + (i as Coord)*dy), c);
            }
            used[placement.word as usize] = true;
            remaining -= 1;

            for (i, c) in new_letters {
                for &(w1, j) in &letters[c as usize] {
                    if used[w1] {
                        continue;
                    }
                    let word1 = words[w1];
                    for (d1, &(dx1, dy1)) in DIRS.iter().enumerate() {
                        let pos1 = (
                            x + (i as Coord)*dx - (j as Coord)*dx1,
                            y + (i as Coord) - (j as Coord)*dy1);
                        if let Some(extra1) = fit(&grid, pos1, d1 as Dir, word1) {
                            queue.enqueue(extra1, word1.len(), Placement {
                                pos: pos1,
                                dir: d1 as Dir,
                                word: w1 as Word
                            });
                        }
                    }
                }
            }
        }
        grids.push(grid);
    }

    let width = grids.iter().map(|grid| {
        grid.iter().map(|(&(x, _), _)| x).max().unwrap() -
            grid.iter().map(|(&(x, _), _)| x).min().unwrap() + 1
    }).max().unwrap();
    print!(
        "{}",
        grids.iter().flat_map(|grid| {
            let x0 = grid.iter().map(|(&(x, _), _)| x).min().unwrap();
            let y0 = grid.iter().map(|(&(_, y), _)| y).min().unwrap();
            let y1 = grid.iter().map(|(&(_, y), _)| y).max().unwrap();
            (y0..y1 + 1).flat_map(move |y| {
                (x0..x0 + width).map(move |x| {
                    *grid.get(&(x, y)).unwrap_or(&('.' as u8)) as char
                }).chain(once('\n').take(1))
            })
        }).collect::<String>()
    );
}

Tôi chưa đọc mã, nhưng bạn có làm gì để khuyến khích các vị trí phi tuyến tính không? Tôi đã mong đợi một thuật toán như thế này sẽ kết thúc với một số chuỗi siêu chuỗi, nhưng có vẻ như bạn đang nhận được một số không gian khá tốt.
Dave

@Dave Không có gì cụ thể, nó chỉ hoạt động theo cách đó. Các siêu chuỗi không bao giờ dài đến mức không thể tìm thấy các vị trí phi tuyến tính tốt hơn, có thể vì có rất nhiều vị trí phi tuyến tính khác để lựa chọn.
Anders Kaseorg

bắt đầu bằng "chúc mừng", kết thúc bằng "phi thường"
BẠN

Tôi đã không bắt được rằng bạn cũng có thể đi chéo. cảm ơn vì bức hình. Tôi không biết nếu tôi muốn nhận xét về các khối mã. :)
Tít

4

C ++, lưới ký tự 27243 (248x219, điền 50,2%)

(Đăng bài này dưới dạng câu trả lời mới vì tôi muốn giữ ràng buộc 1D mà ban đầu tôi đã đăng làm tài liệu tham khảo)

Sự xé toạc này được lấy cảm hứng mạnh mẽ từ câu trả lời của @ AndersKaseorg trong cấu trúc chính của nó, nhưng có một vài điều chỉnh. Đầu tiên, tôi sử dụng chương trình ban đầu của mình để hợp nhất các chuỗi cho đến khi sự trùng lặp tốt nhất có sẵn chỉ là 3 ký tự. Sau đó, tôi sử dụng phương thức mà AndersKaseorg mô tả để lấp đầy dần lưới 2D bằng các chuỗi được tạo này. Các ràng buộc cũng có một chút khác biệt: nó vẫn cố gắng thêm ít ký tự nhất mỗi lần, nhưng các mối quan hệ bị phá vỡ bằng cách ưu tiên các ô vuông trước, sau đó là các lưới nhỏ và cuối cùng bằng cách thêm từ dài nhất.

Hành vi mà nó hiển thị là xen kẽ giữa các giai đoạn lấp đầy không gian và nhanh chóng mở rộng lưới điện (không may là nó hết lời ngay sau giai đoạn mở rộng nhanh chóng, do đó, có rất nhiều khoảng trống xung quanh các cạnh). Tôi nghi ngờ với một số điều chỉnh của hàm chi phí, nó có thể được thực hiện để có thể lấp đầy không gian tốt hơn 50%.

Có 2 tệp thực thi ở đây (để tránh phải chạy lại toàn bộ quá trình khi lặp lại cải tiến thuật toán). Đầu ra của một cái có thể được dẫn trực tiếp vào cái khác:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdlib>

std::size_t calcOverlap(const std::string &a, const std::string &b, std::size_t limit, std::size_t minimal) {
    std::size_t la = a.size();
    for(std::size_t p = std::min(std::min(la, b.size()), limit + 1); -- p > minimal; ) {
        if(a.compare(la - p, p, b, 0, p) == 0) {
            return p;
        }
    }
    return 0;
}

bool isSameReversed(const std::string &a, const std::string &b) {
    std::size_t l = a.size();
    if(b.size() != l) {
        return false;
    }
    for(std::size_t i = 0; i < l; ++ i) {
        if(a[i] != b[l-i-1]) {
            return false;
        }
    }
    return true;
}

int main(int argc, const char *const *argv) {
    // Usage: prog [<stop_threshold>]

    std::size_t stopThreshold = 3;

    if(argc >= 2) {
        char *check;
        long v = std::strtol(argv[1], &check, 10);
        if(check == argv[1] || v < 0) {
            std::cerr
                << "Invalid stop threshold. Should be an integer >= 0"
                << std::endl;
            return 1;
        }
        stopThreshold = v;
    }

    std::vector<std::string> words;

    // Load all words from input and their reverses (words can be backwards now)
    while(true) {
        std::string word;
        std::getline(std::cin, word);
        if(word.empty()) {
            break;
        }
        words.push_back(word);
        std::reverse(word.begin(), word.end());
        words.push_back(std::move(word));
    }

    std::cerr
        << "Input word count: " << words.size() << std::endl;

    // Remove all fully subsumed words

    for(auto p = words.begin(); p != words.end(); ) {
        bool subsumed = false;
        for(auto i = words.begin(); i != words.end(); ++ i) {
            if(i == p) {
                continue;
            }
            if(i->find(*p) != std::string::npos) {
                subsumed = true;
                break;
            }
        }
        if(subsumed) {
            p = words.erase(p);
        } else {
            ++ p;
        }
    }

    std::cerr
        << "After subsuming checks: " << words.size()
        << std::endl;

    // Sort words longest-to-shortest (not necessary but doesn't hurt. Makes finding maxlen a tiny bit easier)
    std::sort(words.begin(), words.end(), [](const std::string &a, const std::string &b) {
        return a.size() > b.size();
    });

    std::size_t maxlen = words.front().size();

    // Repeatedly combine most-compatible words until we reach the threshold
    std::size_t bestPossible = maxlen - 1;
    while(words.size() > 2) {
        auto bestA = words.begin();
        auto bestB = -- words.end();
        std::size_t bestOverlap = 0;
        for(auto p = ++ words.begin(), e = words.end(); p != e; ++ p) {
            if(p->size() - 1 <= bestOverlap) {
                continue;
            }
            for(auto q = words.begin(); q != p; ++ q) {
                std::size_t overlap = calcOverlap(*p, *q, bestPossible, bestOverlap);
                if(overlap > bestOverlap && !isSameReversed(*p, *q)) {
                    bestA = p;
                    bestB = q;
                    bestOverlap = overlap;
                }
                overlap = calcOverlap(*q, *p, bestPossible, bestOverlap);
                if(overlap > bestOverlap && !isSameReversed(*p, *q)) {
                    bestA = q;
                    bestB = p;
                    bestOverlap = overlap;
                }
            }
            if(bestOverlap == bestPossible) {
                break;
            }
        }
        if(bestOverlap <= stopThreshold) {
            break;
        }
        std::string newStr = std::move(*bestA);
        newStr.append(*bestB, bestOverlap, std::string::npos);

        if(bestA == -- words.end()) {
            words.pop_back();
            *bestB = std::move(words.back());
            words.pop_back();
        } else {
            *bestB = std::move(words.back());
            words.pop_back();
            *bestA = std::move(words.back());
            words.pop_back();
        }

        // Remove any words which are now in the result (forward or reverse)
        // (would not be necessary if we didn't have the reversed forms too)
        std::string newRev = newStr;
        std::reverse(newRev.begin(), newRev.end());
        for(auto p = words.begin(); p != words.end(); ) {
            if(newStr.find(*p) != std::string::npos || newRev.find(*p) != std::string::npos) {
                std::cerr << "Now subsumes: " << *p << std::endl;
                p = words.erase(p);
            } else {
                ++ p;
            }
        }

        std::cerr
            << "Words remaining: " << (words.size() + 1)
            << " Latest combination: (" << bestOverlap << ") " << newStr
            << std::endl;

        words.push_back(std::move(newStr));
        words.push_back(std::move(newRev));
        bestPossible = bestOverlap; // Merging existing words will never make longer merges possible
    }

    std::cerr
        << "After merging: " << words.size()
        << std::endl;

    // Remove all fully subsumed words (i.e. reversed words)

    for(auto p = words.begin(); p != words.end(); ) {
        bool subsumed = false;
        std::string rev = *p;
        std::reverse(rev.begin(), rev.end());
        for(auto i = words.begin(); i != words.end(); ++ i) {
            if(i == p) {
                continue;
            }
            if(i->find(*p) != std::string::npos || i->find(rev) != std::string::npos) {
                subsumed = true;
                break;
            }
        }
        if(subsumed) {
            p = words.erase(p);
        } else {
            ++ p;
        }
    }

    std::cerr
        << "After subsuming: " << words.size()
        << std::endl;

    // Sort words longest-to-shortest for display
    std::sort(words.begin(), words.end(), [](const std::string &a, const std::string &b) {
        return a.size() > b.size();
    });

    std::size_t len = 0;
    for(const auto &word : words) {
        std::cout
            << word
            << std::endl;
        len += word.size();
    }
    std::cerr
        << "Total size: " << len
        << std::endl;
    return 0;
}
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <limits>

class vec2 {
public:
    int x;
    int y;

    vec2(void) : x(0), y(0) {};
    vec2(int x, int y) : x(x), y(y) {}

    bool operator ==(const vec2 &b) const {
        return x == b.x && y == b.y;
    }

    vec2 &operator +=(const vec2 &b) {
        x += b.x;
        y += b.y;
        return *this;
    }

    vec2 &operator -=(const vec2 &b) {
        x -= b.x;
        y -= b.y;
        return *this;
    }

    vec2 operator +(const vec2 b) const {
        return vec2(x + b.x, y + b.y);
    }

    vec2 operator *(const int b) const {
        return vec2(x * b, y * b);
    }
};

class box2 {
public:
    vec2 tl;
    vec2 br;

    box2(void) : tl(), br() {};
    box2(vec2 a, vec2 b)
        : tl(std::min(a.x, b.x), std::min(a.y, b.y))
        , br(std::max(a.x, b.x) + 1, std::max(a.y, b.y) + 1)
    {}

    void grow(const box2 &b) {
        if(b.tl.x < tl.x) {
            tl.x = b.tl.x;
        }
        if(b.br.x > br.x) {
            br.x = b.br.x;
        }
        if(b.tl.y < tl.y) {
            tl.y = b.tl.y;
        }
        if(b.br.y > br.y) {
            br.y = b.br.y;
        }
    }

    bool intersects(const box2 &b) const {
        return (
            ((tl.x >= b.br.x) != (br.x > b.tl.x)) &&
            ((tl.y >= b.br.y) != (br.y > b.tl.y))
        );
    }

    box2 &operator +=(const vec2 b) {
        tl += b;
        br += b;
        return *this;
    }

    int width(void) const {
        return br.x - tl.x;
    }

    int height(void) const {
        return br.y - tl.y;
    }

    int maxdim(void) const {
        return std::max(width(), height());
    }
};

template <> struct std::hash<vec2> {
    std::size_t operator ()(const vec2 &o) const {
        return std::hash<int>()(o.x) + std::hash<int>()(o.y) * 997;
    }
};

template <class A,class B> struct std::hash<std::pair<A,B>> {
    std::size_t operator ()(const std::pair<A,B> &o) const {
        return std::hash<A>()(o.first) + std::hash<B>()(o.second) * 31;
    }
};

class word_placement {
public:
    vec2 start;
    vec2 dir;
    box2 bounds;
    const std::string *word;

    word_placement(vec2 start, vec2 dir, const std::string *word)
        : start(start)
        , dir(dir)
        , bounds(start, start + dir * (word->size() - 1))
        , word(word)
    {}

    word_placement(vec2 start, const word_placement &copy)
        : start(copy.start + start)
        , dir(copy.dir)
        , bounds(copy.bounds)
        , word(copy.word)
    {
        bounds += start;
    }

    word_placement(const word_placement &copy)
        : start(copy.start)
        , dir(copy.dir)
        , bounds(copy.bounds)
        , word(copy.word)
    {}
};

class word_placement_links {
public:
    std::unordered_set<word_placement*> placements;
    std::unordered_set<std::pair<char,word_placement*>> relativePlacements;
};

class grid {
public:
    std::vector<std::string> wordCache; // Just a block of memory for our pointers to reference
    std::unordered_map<vec2,char> state;
    std::unordered_set<word_placement*> placements;
    std::unordered_map<const std::string*,word_placement_links> wordPlacements;
    std::unordered_map<char,std::unordered_set<word_placement*>> relativeWordPlacements;
    box2 bound;

    grid(const std::vector<std::string> &words) {
        wordCache = words;
        std::vector<vec2> directions;
        directions.emplace_back(+1,  0);
        directions.emplace_back(+1, +1);
        directions.emplace_back( 0, +1);
        directions.emplace_back(-1, +1);
        directions.emplace_back(-1,  0);
        directions.emplace_back(-1, -1);
        directions.emplace_back( 0, -1);
        directions.emplace_back(+1, -1);

        wordPlacements.reserve(wordCache.size());
        placements.reserve(wordCache.size());
        relativeWordPlacements.reserve(64);

        std::size_t total = 0;
        for(const std::string &word : wordCache) {
            word_placement_links &p = wordPlacements[&word];
            p.placements.reserve(8);
            auto &rp = p.relativePlacements;
            std::size_t l = word.size();
            rp.reserve(l * directions.size());
            for(int i = 0; i < l; ++ i) {
                for(const vec2 &d : directions) {
                    word_placement *rwp = new word_placement(d * -i, d, &word);
                    rp.emplace(word[i], rwp);
                    relativeWordPlacements[word[i]].insert(rwp);
                }
            }
            total += l;
        }
        state.reserve(total);
    }

    const std::string *find_word(const std::string &word) const {
        for(const std::string &w : wordCache) {
            if(w == word) {
                return &w;
            }
        }
        throw std::string("Failed to find word in cache");
    }

    void remove_word(const std::string *word) {
        const word_placement_links &links = wordPlacements[word];
        for(word_placement *p : links.placements) {
            placements.erase(p);
            delete p;
        }
        for(auto &p : links.relativePlacements) {
            relativeWordPlacements[p.first].erase(p.second);
            delete p.second;
        }
        wordPlacements.erase(word);
    }

    void remove_placement(word_placement *placement) {
        wordPlacements[placement->word].placements.erase(placement);
        placements.erase(placement);
        delete placement;
    }

    bool check_placement(const word_placement &placement) const {
        vec2 p = placement.start;
        for(const char c : *placement.word) {
            auto i = state.find(p);
            if(i != state.end() && i->second != c) {
                return false;
            }
            p += placement.dir;
        }
        return true;
    }

    int check_new(const word_placement &placement) const {
        int n = 0;
        vec2 p = placement.start;
        for(const char c : *placement.word) {
            n += !state.count(p);
            p += placement.dir;
        }
        return n;
    }

    void check_placements(const box2 &b) {
        for(auto i = placements.begin(); i != placements.end(); ) {
            if(!b.intersects((*i)->bounds) || check_placement(**i)) {
                ++ i;
            } else {
                i = placements.erase(i);
            }
        }
    }

    void add_placement(const vec2 p, const word_placement &relative) {
        word_placement check(p, relative);
        if(check_placement(check)) {
            word_placement *wp = new word_placement(check);
            placements.insert(wp);
            wordPlacements[relative.word].placements.insert(wp);
        }
    }

    void place(word_placement placement) {
        remove_word(placement.word);
        int overlap = 0;
        for(const char c : *placement.word) {
            char &g = state[placement.start];
            if(g == '\0') {
                g = c;
                for(const word_placement *rp : relativeWordPlacements[c]) {
                    add_placement(placement.start, *rp);
                }
            } else if(g != c) {
                throw std::string("New word changes an existing character!");
            } else {
                ++ overlap;
            }
            placement.start += placement.dir;
        }
        bound.grow(placement.bounds);
        check_placements(placement.bounds);

        std::cerr
            << draw('.', "\n")
            << "Added " << *placement.word << " (overlap: " << overlap << ")"
            << ", Grid: " << bound.width() << "x" << bound.height() << " of " << state.size() << " chars"
            << ", Words remaining: " << wordPlacements.size()
            << std::endl;
    }

    int check_cost(box2 b) const {
        b.grow(bound);
        return (
            ((b.maxdim() - bound.maxdim()) << 16) |
            (b.width() + b.height() - bound.width() - bound.height())
        );
    }

    void add_next(void) {
        int bestNew = std::numeric_limits<int>::max();
        int bestCost = std::numeric_limits<int>::max();
        int bestLen = 0;
        word_placement *best = nullptr;
        for(word_placement *p : placements) {
            int n = check_new(*p);
            if(n <= bestNew) {
                int l = p->word->size();
                int cost = check_cost(box2(p->start, p->start + p->dir * l));
                if(n < bestNew || cost < bestCost || (cost == bestCost && l < bestLen)) {
                    bestNew = n;
                    bestCost = cost;
                    bestLen = l;
                    best = p;
                }
            }
        }
        if(best == nullptr) {
            throw std::string("Failed to find join to existing blob");
        }
        place(*best);
    }

    void fill(void) {
        while(!placements.empty()) {
            add_next();
        }
    }

    std::string draw(char blank, const std::string &linesep) const {
        std::string result;
        result.reserve((bound.width() + linesep.size()) * bound.height());
        for(int y = bound.tl.y; y < bound.br.y; ++ y) {
            for(int x = bound.tl.x; x < bound.br.x; ++ x) {
                auto c = state.find(vec2(x, y));
                result.push_back((c == state.end()) ? blank : c->second);
            }
            result.append(linesep);
        }
        return result;
    }

    box2 bounds(void) const {
        return bound;
    }

    int chars(void) const {
        return state.size();
    }
};

int main(int argc, const char *const *argv) {
    std::vector<std::string> words;

    // Load all words from input
    while(true) {
        std::string word;
        std::getline(std::cin, word);
        if(word.empty()) {
            break;
        }
        words.push_back(std::move(word));
    }

    std::cerr
        << "Input word count: " << words.size() << std::endl;

    // initialise grid
    grid g(words);

    // add first word (order of input file means this is longest word)
    g.place(word_placement(vec2(0, 0), vec2(1, 0), g.find_word(words.front())));

    // add all other words
    g.fill();

    std::cout << g.draw('.', "\n");

    int w = g.bounds().width();
    int h = g.bounds().height();
    int n = g.chars();
    std::cerr
        << "Final grid: " << w << "x" << h
        << " with " << n << " characters"
        << " (" << (n * 100.0 / (w * h)) << "% filled)"
        << std::endl;
    return 0;
}

Và cuối cùng, kết quả:

Lưới cuối cùng


Kết quả thay thế (sau khi sửa một vài lỗi trong chương trình sai lệch một số hướng nhất định và điều chỉnh hàm chi phí, tôi đã có một giải pháp nhỏ gọn hơn nhưng ít tối ưu hơn): 29275 ký tự, 198x195 (đã lấp đầy 75,8%):

Lưới Squarer

Một lần nữa tôi đã không làm gì nhiều để tối ưu hóa các chương trình này, vì vậy phải mất một thời gian. Nhưng bạn có thể xem khi nó lấp đầy trong lưới, điều này khá thôi miên.


2

"Lưới" C ++, 34191 ký tự (với sự can thiệp tối thiểu của con người, 6 hoặc 7 có thể dễ dàng được lưu)

Điều này nên được thực hiện nhiều hơn như là một ràng buộc cho trường hợp 2D, bởi vì câu trả lời vẫn là một chuỗi 1D. Đó chỉ là mã của tôi từ thử thách trước, nhưng với khả năng mới để đảo ngược bất kỳ chuỗi nào. Điều này cung cấp cho chúng tôi nhiều phạm vi hơn để kết hợp các từ (đặc biệt là vì nó bao trùm trường hợp xấu nhất của các siêu từ không chồng chéo lên 26; một cho mỗi chữ cái trong bảng chữ cái).

Đối với một số kháng cáo hình ảnh 2D nhỏ, nó đặt kết quả ngắt dòng trong kết quả nếu nó có thể làm điều đó miễn phí (nghĩa là giữa các từ 0 chồng chéo).

Khá chậm (vẫn không có bộ nhớ đệm). Đây là mã:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

std::size_t calcOverlap(const std::string &a, const std::string &b, std::size_t limit, std::size_t minimal) {
    std::size_t la = a.size();
    for(std::size_t p = std::min(std::min(la, b.size()), limit + 1); -- p > minimal; ) {
        if(a.compare(la - p, p, b, 0, p) == 0) {
            return p;
        }
    }
    return 0;
}

bool isSameReversed(const std::string &a, const std::string &b) {
    std::size_t l = a.size();
    if(b.size() != l) {
        return false;
    }
    for(std::size_t i = 0; i < l; ++ i) {
        if(a[i] != b[l-i-1]) {
            return false;
        }
    }
    return true;
}

int main() {
    std::vector<std::string> words;

    // Load all words from input and their reverses (words can be backwards now)
    while(true) {
        std::string word;
        std::getline(std::cin, word);
        if(word.empty()) {
            break;
        }
        words.push_back(word);
        std::reverse(word.begin(), word.end());
        words.push_back(std::move(word));
    }

    std::cerr
        << "Input word count: " << words.size() << std::endl;

    // Remove all fully subsumed words

    for(auto p = words.begin(); p != words.end(); ) {
        bool subsumed = false;
        for(auto i = words.begin(); i != words.end(); ++ i) {
            if(i == p) {
                continue;
            }
            if(i->find(*p) != std::string::npos) {
                subsumed = true;
                break;
            }
        }
        if(subsumed) {
            p = words.erase(p);
        } else {
            ++ p;
        }
    }

    std::cerr
        << "After subsuming checks: " << words.size()
        << std::endl;

    // Sort words longest-to-shortest (not necessary but doesn't hurt. Makes finding maxlen a tiny bit easier)
    std::sort(words.begin(), words.end(), [](const std::string &a, const std::string &b) {
        return a.size() > b.size();
    });

    std::size_t maxlen = words.front().size();

    // Repeatedly combine most-compatible words until we have only 1 word left (+ its reverse)
    std::size_t bestPossible = maxlen - 1;
    while(words.size() > 2) {
        auto bestA = words.begin();
        auto bestB = -- words.end();
        std::size_t bestOverlap = 0;
        for(auto p = ++ words.begin(), e = words.end(); p != e; ++ p) {
            if(p->size() - 1 <= bestOverlap) {
                continue;
            }
            for(auto q = words.begin(); q != p; ++ q) {
                std::size_t overlap = calcOverlap(*p, *q, bestPossible, bestOverlap);
                if(overlap > bestOverlap && !isSameReversed(*p, *q)) {
                    bestA = p;
                    bestB = q;
                    bestOverlap = overlap;
                }
                overlap = calcOverlap(*q, *p, bestPossible, bestOverlap);
                if(overlap > bestOverlap && !isSameReversed(*p, *q)) {
                    bestA = q;
                    bestB = p;
                    bestOverlap = overlap;
                }
            }
            if(bestOverlap == bestPossible) {
                break;
            }
        }
        std::string newStr = std::move(*bestA);
        if(bestOverlap == 0) {
            newStr.push_back('\n');
        }
        newStr.append(*bestB, bestOverlap, std::string::npos);

        if(bestA == -- words.end()) {
            words.pop_back();
            *bestB = std::move(words.back());
            words.pop_back();
        } else {
            *bestB = std::move(words.back());
            words.pop_back();
            *bestA = std::move(words.back());
            words.pop_back();
        }

        // Remove any words which are now in the result (forward or reverse)
        // (would not be necessary if we didn't have the reversed forms too)
        std::string newRev = newStr;
        std::reverse(newRev.begin(), newRev.end());
        for(auto p = words.begin(); p != words.end(); ) {
            if(newStr.find(*p) != std::string::npos || newRev.find(*p) != std::string::npos) {
                std::cerr << "Now subsumes: " << *p << std::endl;
                p = words.erase(p);
            } else {
                ++ p;
            }
        }

        std::cerr
            << "Words remaining: " << (words.size() + 1)
            << " Latest combination: (" << bestOverlap << ") " << newStr
            << std::endl;

        words.push_back(std::move(newStr));
        words.push_back(std::move(newRev));
        bestPossible = bestOverlap; // Merging existing words will never make longer merges possible
    }

    std::cerr
        << "After non-trivial merging: " << words.size()
        << std::endl;

    if(words.size() == 2 && !isSameReversed(words.front(), words.back())) {
        // must be 2 palindromes, so just join them
        words.front().append(words.back());
    }

    std::string result = words.front();

    std::cout
        << result
        << std::endl;
    std::cerr
        << "Word size: " << result.size() // Note this number includes newlines, so to get the grid size according to the rules, subtract newlines manually
        << std::endl;
    return 0;
}

Kết quả: http://pastebin.com/UTe2WMcz (ít hơn 4081 ký tự so với thử thách trước đó)

Rõ ràng là một số khoản tiết kiệm tầm thường có thể được thực hiện bằng cách đặt xdwvcác đường thẳng đứng, giao nhau với đường quái vật. Sau đó hhidetautisbneuduicó thể giao nhau với d, và lxwwwowaxocnnaesddavới w. Điều này tiết kiệm 4 ký tự. nbcllilhncó thể được thay thế bằng một lớp phủ hiện có s(nếu có thể tìm thấy) để lưu thêm 2 (hoặc chỉ 1 nếu không có sự trùng lặp như vậy tồn tại và nó phải được thêm vào theo chiều dọc). Cuối cùng mjjrajaytqcó thể được thêm theo chiều dọc ở đâu đó để lưu 1. Điều này có nghĩa là với sự can thiệp tối thiểu của con người, 6 ký tự7 có thể được lưu từ kết quả.

Tôi muốn chuyển nó thành 2D bằng phương pháp sau, nhưng tôi đang loay hoay tìm cách thực hiện nó mà không cần thực hiện thuật toán O (n ^ 4), điều này khá không thực tế để tính toán!

  1. Chạy thuật toán như trên, nhưng dừng lại khi các phần trùng lặp đạt 1 ký tự
  2. Nhiều lần:
    1. Tìm một nhóm gồm 4 từ có thể được sắp xếp thành một hình chữ nhật
    2. Thêm càng nhiều từ càng tốt trên đầu hình chữ nhật này, trong đó mỗi từ trùng nhau ít nhất 2 ký tự của hình dạng hiện tại (kiểm tra tất cả 8 hướng) - đây là giai đoạn duy nhất mà chúng ta thực sự có thể có lợi thế hơn mã hiện tại
  3. Kết hợp các lưới kết quả và các từ đơn độc tìm kiếm các ký tự đơn lẻ mỗi lần

0

PHP

cái này làm công việc trị liệu; nhưng 10000 có lẽ là quá nhiều từ để đệ quy. Kịch bản đang chạy. (vẫn chạy 24 giờ sau)
hoạt động tốt trên các thư mục nhỏ, nhưng tôi có thể tạo phiên bản lặp vào tuần tới.

$f=array("pen","op","po","ne","pro","aaa","abcd","dcba"); will output abcd apen arop ao .. although this is not an optimal result (scoring was changed ... I´m working on a generator). One optimal result is this: mở .ra .oa dcba`

Nó cũng không nhanh lắm; chỉ loại bỏ các chuỗi con và sắp xếp các phần còn lại theo chiều dài,
phần còn lại là lực lượng vũ phu: cố gắng ghép các từ vào một hình chữ nhật, thử trên một hình chữ nhật lớn hơn nếu nó thất bại.

btw: Phần chuỗi con cần 4,5 phút trên máy của tôi cho thư mục lớn
và cắt nó xuống còn 6.190 từ; sắp xếp chúng mất 11 giây.

$f=file('https://raw.githubusercontent.com/first20hours/google-10000-english/master/google-10000-english.txt');
// A: remove substrings - forward or reversed
$s=join(' ',$f);
$haystack="$s ".strrev($s);
foreach($f as$w)
{
    $r=strrev($w=trim($w)); // remove trailing line break and create reverse word
    if(!preg_match("%$w\w|\w$w%",$haystack)
        // no substr match ... now: is the reverse word in the list?
        // if so, keep only the lower one (ascii values)
        &!($w>$r&&strstr($s,$r))
        // strstr does NOT render the reverse substr regex obsolete:
        // this is only executed for $w=abc, not for $w=bca!
    )
        $g[]=$w
    ;
}

// B: sort the words by length
usort($g,function($a,$b){return strlen($a)-strlen($b);});

// C1: function to fit $words into $map
function gomap($words,$map)
{
    $h=count($map);$w=strlen($map[0]);
    $len=strlen($word=array_pop($words));
    // $x,$y=position; $d=0:horizontal, $d=1:vertical; $r=0: word, $r=1: reverse word
    for($x=$w-$len;$x>=0;$x--)for($y=$h-$len;$y>=0;$y--)for($d=0;$d<2;$d++)for($r=0;$r<2;$r++)
    {
        // does the word fit there?
        $drow=$r?strrev($word):$word;
        for($ok=1,$i=0;$ok&$i<$len;$i++)
            $ok=in_array($map[$y+$d*$i][$x+$i-$d*$i], [' ',$drow[$i]])
        ;
        // it does, paint it
        if($ok)
        {
            for($i=0;$i<$len;$i++)
                $map[$y+$d*$i][$x+$i-$d*$i]=$drow[$i];
            if(!count($words))      // this was the last word: return map
                return $map;
            else                    // there are more words: recurse
                if ($ok=gomap($words,$map))
                    return $ok;
            // no fit, try next position
        }
    }
    return 0;
}

// C2: rectangle loop
for($h=0;++$h;)for($w=0;$w++<$h;)   // define a rectangle
{
    // and try to fit the words in there
    if($map=gomap($g,
        array_fill(0,$h,str_repeat(' ',$w))
    ))
    {
        // words fit; output and break loops
        echo '<pre>',implode("\n",$map),'</pre>';
        break 2;
    }
}

Bạn có thể bao gồm một ví dụ khi chương trình được chạy trên một từ điển nhỏ hơn?
Loovjo

Tôi thực sự đã thay đổi cách tính điểm (xin lỗi!). Số lượng ký tự không sử dụng không được bao gồm trong điểm số của bạn.
Nathan Merrill

2
Vòng lặp ở đây có nghĩa là đây là ~ O ((w * h) ^ n). Chúng tôi biết giải pháp sẽ có một cái gì đó giống như 35k chữ cái (từ thử thách cuối cùng), vì vậy nó sẽ kết thúc việc gọi gomap khoảng 35000 ^ 6000 lần. Máy tính của tôi nói với tôi rằng đó là "vô cùng". Một máy tính tốt hơn cho tôi biết số thực tế ( wolframalpha.com/input/?i=35000%5E6000 ). Bây giờ, nếu chúng ta giả sử mọi nguyên tử trong vũ trụ là bộ xử lý 3 terrahertz dành riêng để chạy chương trình này, vũ trụ sẽ cần tồn tại lâu hơn 10 ^ 27154 lần so với trước khi hoàn thành. Điều tôi đang nói là: đừng đợi nó kết thúc!
Dave
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.