न्यूनतम शब्द खोज


18

पिछले हफ्ते, हमने अंग्रेजी भाषा में शीर्ष 10,000 शब्दों का उपयोग करते हुए सबसे कम 1-डी स्ट्रिंग बनाने का काम किया । अब, 2 डी में एक ही चुनौती की कोशिश करते हैं!

आपको उपरोक्त सभी शब्दों को लेने की जरूरत है, और उन्हें एक आयत में जितना संभव हो उतना छोटा डालें, ओवरलैप्स की अनुमति दें। उदाहरण के लिए, यदि आपके शब्द थे ["ape","pen","ab","be","pa"], तो एक संभावित आयत होगी:

.b..
apen

उपरोक्त आयत 5 का स्कोर देगी।

नियम:

  • एक शब्द में कई अक्षरों को ओवरलैप करने की अनुमति है
  • शब्द 8 दिशाओं में से किसी में भी जा सकते हैं
  • शब्द चारों ओर नहीं लपेट सकते
  • आप खाली स्थानों के लिए किसी भी वर्ण का उपयोग कर सकते हैं

आपको एक शब्द खोज बनाने की आवश्यकता है जिसमें अंग्रेजी में ये शीर्ष 10,000 शब्द हैं (Google के अनुसार)। आपका शब्द खोज शब्द (अप्रयुक्त वर्णों को छोड़कर) में वर्णों की संख्या के बराबर है । यदि कोई टाई है, या यदि सबमिशन को इष्टतम साबित किया जाता है, तो वह सबमिशन जो पहले पोस्ट की गई जीत है।


1
मैं यह नोट करना चाहूंगा कि मुझे इस पिछले शब्द खोज चुनौती के बारे में पता है , लेकिन यह देखते हुए कि इस चुनौती के लिए कोई भी उत्तर उचित मात्रा में नहीं चलेगा, मुझे इसके डुप्लिकेट पर विश्वास नहीं है।
नाथन मेरिल


मुझे डर है कि इष्टतम समाधान एक nx 1 ग्रिड बन जाएगा, जिससे यह समस्या अंत में पिछले एक के समान हो जाएगी (तर्क: स्पर्श चौराहे शायद ही कभी कई पात्रों को बचाएंगे लेकिन अक्सर "छेद", अंतरिक्ष को बर्बाद कर देंगे)। हो सकता है कि आप इसे चौड़ाई + ऊँचाई पर बजाय चौड़ाई * ऊँचाई पर स्कोर करें, ताकि यह वर्ग समाधानों (अधिक दिलचस्प) का दृढ़ता से समर्थन करे।
डेव

हम्म् ... मुझे डर है कि समाधान बस एक दूसरे के ऊपर खड़ी शब्द तार होंगे, फिर। मुझे लगता है कि खाली जगहों पर स्कोर नहीं करना एक अच्छा विचार हो सकता है
नाथन मेरिल

इसके साथ जोखिम, ग्रिड आकार को छोटा रखने की कोई आवश्यकता नहीं है; 1000x1000 ग्रिड एक विशाल क्षैतिज और ऊर्ध्वाधर सूची के साथ एक तंग सर्पिल पैटर्न / समान के समान होगा। हो सकता है कि चौड़ाई + ऊँचाई की कोशिश करें, फिर टाई-ब्रेकर के रूप में पत्र-छोड़कर-रिक्त स्थान? थोड़ा और विचार की आवश्यकता हो सकती है। संपादित करें: या हो सकता है कि अक्षर-छोड़कर-रिक्तता पहले तो एक टाई ब्रेकर के रूप में चौड़ाई + ऊंचाई बेहतर काम करेगी।
डेव

जवाबों:


7

जंग, 31430 30081 पात्रों का इस्तेमाल किया

यह एक प्रकार का लालची एल्गोरिथ्म है: हम एक खाली ग्रिड के साथ शुरू करते हैं, और बार-बार उस शब्द को जोड़ते हैं जो सबसे नए अक्षरों के साथ जोड़ा जा सकता है, लंबे शब्दों को पसंद करके टूटे हुए संबंधों के साथ। इस रन को जल्दी से बनाने के लिए, हम उम्मीदवार शब्द प्लेसमेंट की प्राथमिकता कतार बनाए रखते हैं (प्रत्येक शब्द की लंबाई के लिए एक डीके युक्त नए अक्षरों के प्रत्येक संख्या के लिए वेक्टर के साथ, वैक्टर के वेक्टर के रूप में लागू किया जाता है)। प्रत्येक नए जोड़े गए पत्र के लिए, हम उस पत्र के माध्यम से चलने वाले सभी उम्मीदवार प्लेसमेंट की पुष्टि करते हैं।

संकलित करें और साथ चलाएं rustc -O wordsearch.rs; ./wordsearch < google-10000-english.txt। मेरे लैपटॉप पर, 531 MiB रैम का उपयोग करके, यह 70 सेकंड में चलता है।

आउटपुट 248 कॉलम और 253 पंक्तियों के साथ एक आयत में फिट बैठता है।

यहाँ छवि विवरण दर्ज करें

कोड

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>()
    );
}

मैंने अभी तक कोड नहीं पढ़ा है, लेकिन क्या आप गैर-रैखिक प्लेसमेंट को प्रोत्साहित करने के लिए कुछ भी करते हैं? मुझे लगता है कि इस तरह के एक एल्गोरिथ्म को सुपर-स्ट्रिंग्स को पार करने में मुट्ठी भर के साथ समाप्त होना चाहिए, लेकिन ऐसा लगता है कि आपको कुछ बहुत अच्छा स्थान-भरना मिल रहा है।
डेव

@ कुछ भी विशिष्ट नहीं है, यह सिर्फ इस तरह से काम करता है। सुपर-स्ट्रिंग्स कभी भी इतनी लंबी नहीं होती हैं कि बेहतर गैर-रेखीय प्लेसमेंट कभी नहीं मिल सकते हैं, शायद इसलिए कि चुनने के लिए बहुत अधिक गैर-रैखिक प्लेसमेंट हैं।
एंडर्स केसेर्ग

"बधाई" के साथ शुरू होता है, "असाधारण" के साथ समाप्त होता है
आप

मैंने नहीं पकड़ा कि आप विकर्ण भी हो सकते हैं। तस्वीर के लिये शुक्रिया। मुझे पता नहीं है कि क्या मुझे कोड ब्लॉक पर टिप्पणियों की इच्छा होनी चाहिए। :)
टाइटस

4

C ++, 27243 वर्ण ग्रिड (248x219, 50.2% भरा हुआ)

(इसे एक नए उत्तर के रूप में पोस्ट कर रहा हूं क्योंकि मैं 1D बाध्य रखना चाहता हूं जिसे मैंने मूल रूप से एक संदर्भ के रूप में पोस्ट किया है)

यह तो एकदम बंद rips भारी से प्रेरित है @ AndersKaseorg का जवाब अपने मुख्य संरचना में है, लेकिन तोड़ मरोड़ के एक जोड़े हैं। सबसे पहले, मैं अपने मूल कार्यक्रम का उपयोग स्ट्रिंग्स को मर्ज करने के लिए करता हूं जब तक कि सबसे अच्छा उपलब्ध ओवरलैप सिर्फ 3 वर्ण न हो। फिर मैं इन उत्पन्न स्ट्रिंग्स का उपयोग करके एक 2D ग्रिड को उत्तरोत्तर भरने के लिए AndersKaseorg विधि का उपयोग करता हूं। बाधाएं थोड़ी अलग हैं: यह अभी भी हर बार सबसे कम वर्ण जोड़ने की कोशिश करता है, लेकिन पहले वर्ग ग्रिड, फिर छोटे ग्रिड, और अंत में सबसे लंबे शब्द जोड़कर संबंधों को तोड़ दिया जाता है।

यह जो व्यवहार प्रदर्शित करता है, वह अंतरिक्ष में भरने की अवधि के बीच वैकल्पिक रूप से होता है और तेजी से ग्रिड का विस्तार करता है (दुर्भाग्य से यह तेजी से विस्तार के चरण के बाद शब्दों से बाहर भाग गया, इसलिए किनारों के आसपास बहुत सारी खाली जगह है)। मुझे लगता है कि यह 50% से अधिक जगह भरने के लिए बेहतर हो सकता है।

यहां 2 निष्पादन योग्य हैं (जब पुनरावृत्ति को एल्गोरिथ्म में सुधारने के लिए पूरी प्रक्रिया को फिर से चलाने की आवश्यकता से बचने के लिए)। एक के उत्पादन को सीधे दूसरे में डाला जा सकता है:

#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;
}

और अंत में, परिणाम:

अंतिम ग्रिड


वैकल्पिक परिणाम (कार्यक्रम में कुछ बगों को ठीक करने के बाद जो कुछ दिशा-निर्देशों को पूर्वाग्रहित कर रहे थे और लागत समारोह को मोड़ रहे थे, मुझे एक अधिक कॉम्पैक्ट लेकिन कम इष्टतम समाधान मिला): 29275 वर्ण, 198x195 (75.8% भरा):

स्क्वरर ग्रिड

फिर से मैंने इन कार्यक्रमों को अनुकूलित करने के लिए बहुत कुछ नहीं किया है, इसलिए इसमें थोड़ा समय लगता है। लेकिन आप इसे ग्रिड में भरते हुए देख सकते हैं, जो काफी सम्मोहक है।


2

C ++, 34191 वर्ण "ग्रिड" (न्यूनतम मानव हस्तक्षेप के साथ, 6 या 7 आसानी से बचाया जा सकता है)

यह 2 डी मामले के लिए बाध्य के रूप में अधिक लिया जाना चाहिए, क्योंकि उत्तर अभी भी 1 डी स्ट्रिंग है। यह पिछली चुनौती से सिर्फ मेरा कोड है, लेकिन किसी भी स्ट्रिंग को उलटने की नई क्षमता के साथ। यह हमें शब्दों के संयोजन के लिए बहुत अधिक गुंजाइश देता है (विशेषकर क्योंकि यह गैर-अतिव्यापी सुपरस्ट्रिंग के सबसे खराब मामले को 26 में कैप करता है; वर्णमाला के प्रत्येक अक्षर के लिए एक)।

कुछ मामूली 2 डी दृश्य अपील के लिए, यह परिणाम में लाइनब्रेक डालता है यदि यह मुफ्त में ऐसा कर सकता है (यानी 0-ओवरलैप शब्दों के बीच)।

बहुत धीमा (अभी भी कैशिंग नहीं)। यहाँ कोड है:

#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;
}

परिणाम: http://pastebin.com/UTe2WMcz (पिछली चुनौती से 4081 अक्षर कम)

यह बहुत स्पष्ट है कि कुछ तुच्छ बचत खड़ी रेखाओं को डालकर xdऔर wvराक्षस रेखा को पार करके बनाई जा सकती है । फिर के hhidetautisbneuduiसाथ d, और lxwwwowaxocnnaesddaसाथ में अंतर कर सकते हैं w। इससे 4 अक्षर बचते हैं। nbcllilhnएक मौजूदा sओवरलैप में प्रतिस्थापित किया जा सकता है (यदि एक पाया जा सकता है) एक और 2 को बचाने के लिए (या सिर्फ 1 अगर ऐसा कोई ओवरलैप मौजूद नहीं है और इसे इसके बजाय लंबवत जोड़ा जाना चाहिए)। अंत में mjjrajaytqबचाने के लिए कहीं-कहीं लंबवत रूप से जोड़ा जा सकता है। इसका मतलब है कि न्यूनतम मानव हस्तक्षेप से, 6-7 वर्णों को परिणाम से बचाया जा सकता है।

मैं इसे निम्नलिखित विधि के साथ 2 डी में प्राप्त करना चाहूंगा, लेकिन मैं एल्गोरिथ्म ओ (एन ^ 4) को बनाए बिना इसे लागू करने का एक तरीका खोजने के लिए संघर्ष कर रहा हूं, जो कि गणना करने के लिए काफी अव्यावहारिक है!

  1. एल्गोरिथ्म को ऊपर के रूप में चलाएं, लेकिन जब ओवरलैप्स 1 वर्ण तक पहुंच जाए, तो इसे कम कर दें
  2. बार बार:
    1. 4 शब्दों के समूह का पता लगाएं, जिसे एक आयत में व्यवस्थित किया जा सकता है
    2. इस आयत के शीर्ष पर जितने संभव हो उतने शब्द जोड़ें, जहां प्रत्येक शब्द मौजूदा आकृति के कम से कम 2 वर्णों को ओवरलैप करता है (सभी 8 दिशाओं की जांच करें) - यह एकमात्र चरण है जहां हम वास्तव में वर्तमान कोड पर एक लाभ प्राप्त कर सकते हैं
  3. हर बार एकल-पत्र की तलाश में परिणामी ग्रिड और लोन-शब्द को मिलाएं

0

पीएचपी

यह काम चिकित्सकीय रूप से करता है; लेकिन 10000 शायद पुनरावृत्ति के लिए बहुत सारे शब्द हैं। अभी स्क्रिप्ट चल रही है। (अभी भी 24 घंटे बाद भाग गया)
छोटे निर्देशिकाओं पर ठीक काम करता है, लेकिन मैं अगले सप्ताह एक पुनरावृत्त संस्करण बना सकता हूं।

$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: खुला है .ra .oa dcba`

यह भी बहुत तेज नहीं है; केवल संक्षारण को हटाता है और लंबाई द्वारा अवशेषों को छांटता है,
बाकी पाशविक बल है: शब्दों को एक आयत में फिट करने का प्रयास करें, यदि यह विफल हो जाता है तो एक बड़े आयत पर प्रयास करें।

btw: प्रतिस्थापन मशीन को बड़ी निर्देशिका के लिए मेरी मशीन पर 4.5 मिनट की आवश्यकता होती है
और इसे 6,190 शब्दों में काट देता है; उन पर छाँटे गए 11 सेकंड लगते हैं।

$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;
    }
}

क्या आप एक उदाहरण शामिल कर सकते हैं जब कार्यक्रम छोटे शब्दकोश पर चलाया जाता है?
लवजो 16

मैंने वास्तव में स्कोरिंग (क्षमा करें!) को बदल दिया है। अप्रयुक्त वर्णों की संख्या आपके स्कोर में शामिल नहीं है।
नाथन मेरिल

2
यहाँ पाशन का अर्थ है ~ O ((w * h) ^ n)। हम जानते हैं कि समाधान में 35k अक्षर (अंतिम चुनौती से) जैसा कुछ होगा, इसलिए यह लगभग 35000 या 6000 बार गोमप को कॉल करेगा। मेरा कैलकुलेटर मुझे बताता है कि "अनंत" है। एक बेहतर कैलकुलेटर मुझे वास्तविक संख्या बताता है ( wolframalpha.com/input/?i=35000%5E6000 )। अब, यदि हम मान लें कि ब्रह्मांड में प्रत्येक परमाणु एक 3 टेराथर्ट्ज़ प्रोसेसर है, जो इस कार्यक्रम को चलाने के लिए समर्पित है, तो ब्रह्मांड को 10 ^ 27154 बार मौजूद रहने की आवश्यकता होगी, जितना कि यह पूरा होने से पहले अब तक है। मैं जो कह रहा हूं वह है: इसके खत्म होने का इंतजार मत करो!
डेव
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.