【问题标题】:can I create a custom iterator the iterates over one sequence then another (chain doesnt work)我可以创建一个自定义迭代器,然后迭代一个序列然后另一个(链不起作用)
【发布时间】:2021-01-05 00:42:07
【问题描述】:

我有一个struct Folder。我有一个名为contents 的方法。我希望该方法返回一个支持IntoIterator 的对象,以便调用者可以去

for x in folder.contents(){
...
}

Item 类型是(因为这是 hashmap 迭代器返回的 - 看低一点)

(&OsString, &FileOrFolder)

FileOrFolder 是一个枚举

enum FileOrFolder{
   File(File),
   Folder(Folder)
}

迭代器本身需要首先枚举文件夹拥有的HashMap<OSString, FileOrFolder>,然后再枚举Vec<File>。文件的Vec 是由内容fnIntoIterator 调用动态创建的,无论是否有效。我尝试简单地使用chain,但很快意识到这行不通。所以我对我正在尝试做的事情的粗略草图是这样的:

// the iterator 
pub struct FFIter {
    files: Vec<FileOrFolder>,
    files_iter:Box<dyn Iterator<Item=FileOrFolder>>,
    dirs: Box<dyn Iterator<Item =  (&OsString, &FileOrFolder)>>,
    dirs_done:bool
}

// the thing returned by the contents fn
struct FolderContents{
   folder:&Folder
}
// make it iterable
impl IntoIterator for FolderContents {
    type Item =(&OsString, &FileOrFolder);
    type IntoIter = FFIter;

    fn into_iter(self) -> Self::IntoIter {
        let files  = self.folder.make_the_files()
        FFIter {
            files: files, // to keep files 'alive'
            files_iter: files.iter(),
            dirs: Box::new(self.hashmap.iter()),
            dirs_done:false
        }
    }
}
impl Iterator for FFIter {
    type Item = (&OsString, &FileOrFolder);
    fn next(&mut self) -> Option<(&OsString, &FileOrFolder)> {
          None // return empty, lets just get the skeleton built
    }
}

impl Folder{
   pub fn contents(&self) -> FolderContents{
     FolderContents{folder:&self}
  }
}

我知道这充满了错误,但我需要知道这是否可行。如您所见,我什至没有尝试编写返回任何内容的代码。我只是想编译基本大纲。

我开始与终身系统搏斗,并达到了我拥有这个的地步

error[E0658]: generic associated types are unstable      
  --> src\state\files\file_or_folder.rs:46:5
   |
46 |     type Item<'a> =(&'a OsString, &'a FileOrFolder);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: see issue #44265 <https://github.com/rust-lang/rust/issues/44265> for more information

这有点糟糕,因为编译器说我应该这样做。

我很高兴按照编译器/阅读/的建议继续努力解决这个问题……但过去我曾按照这些思路发布过一个问题,并被告知 - “当然不能这样做” .那么我应该能够完成这项工作吗?

文件夹类型不是Copy 并且克隆成本很高。文件类型为简单(string and i64)CopyClone

我知道我可以简单地让调用者调用两个不同的迭代并将它们合并,但我正在尝试编写一个透明的替换模块以放入现有的大型代码库中。

如果有人说chain() 应该很好用,我会再试一次。

EDIT Jmp 说链应该可以工作,

这是我尝试过的

pub fn contents(&self) -> Box<dyn Iterator<Item = (&OsString, &FileOrFolder)> + '_> {
    let mut files = vec![];
    if self.load_done {
        for entry in WalkDir::new(&self.full_path)
            .max_depth(1)
            .skip_hidden(false)
            .follow_links(false)
            .into_iter()
        {
            let ent = entry.unwrap();
            if ent.file_type().is_file() {
                if let Some(name) = ent.path().file_name() {
                    files.push((
                        name.to_os_string(),
                        FileOrFolder::File(File {
                            name: name.to_os_string(),
                            size: ent.metadata().unwrap().len() as u128,
                        }),
                    ));
                }
            }
        }
    };

    Box::new(
        self.contents
            .iter()
            .map(|(k, v)| (k, v))
            .chain(files.iter().map(|x| (&x.0, &x.1))),
    )
}

但是编译器正确地抱怨“文件”在调用结束时被破坏。我需要的是 vec 由迭代器持有,然后在迭代结束时删除。文件夹本身不能保存文件 - 这里的重点是动态填充文件,保存它们太昂贵了,内存明智。

【问题讨论】:

  • chain 应该可以工作,但您需要确保两个迭代器具有相同的 Item 类型或使用 map 将一个(或两个)转换为适当的类型。跨度>
  • @Jmb,这正是我试图做的(地图和链),但无法让它发挥作用。我会再试一次
  • @Jmb 不,我不知道该怎么做,文件的 vec 是动态创建的。我将更新 Q 以显示我喜欢的内容
  • “动态创建”和“在项目中带有引用的迭代器”通常根本不会一起出现。
  • @SebastianRedl - 我 100% 知道这一点。我正在征求意见该怎么做,我确信我可以将'on the fly' vec 存储在迭代器对象本身中。这就是我最初寻求帮助的原因,但 Jmp 说我应该只使用链

标签: rust


【解决方案1】:

您声称files 是即时填充的,但这正是您的代码所做的:您的代码在尝试返回之前预先计算files。解决方案是真正即时计算files,如下所示:

pub fn contents(&self) -> Box<dyn Iterator<Item = (&OsString, &FileOrFolder)> + '_> {
    let files = WalkDir::new(&self.full_path)
        .max_depth(1)
        .skip_hidden(false)
        .follow_links(false)
        .into_iter()
        .filter_map (|entry| {
            let ent = entry.unwrap;
            if ent.file_type().is_file() {
                if let Some(name) = ent.path().file_name() {
                    Some((
                        name.to_os_string(),
                        FileOrFolder::File(File {
                            name: name.to_os_string(),
                            size: ent.metadata().unwrap().len() as u128,
                        }),
                    ))
                } else None
            } else None
        });
    self.contents
        .iter()
        .chain (files)
}

由于您没有给我们 MRE,我没有测试上述内容,但我认为它会失败,因为 self.contents.iter() 返回引用,而 files 返回拥有值。解决此问题需要更改函数的原型以返回某种形式的自有值,因为无法使 files 返回引用。我看到了两种方法:

  • 最简单的方法是使FileOrFolder 可克隆并摆脱原型中的引用:
pub fn contents(&self) -> Box<dyn Iterator<Item = (OsString, FileOrFolder)> + '_> {
    let files = ...;
    self.contents
        .iter()
        .cloned()
        .chain (files)
  • 或者您可以创建一个类似于Cow 的包装器类型,而不是可以保存引用或拥有的值:
enum OwnedOrRef<'a, T> {
    Owned (T),
    Ref (&'a T),
}

pub fn contents(&self) -> Box<dyn Iterator<Item = (OwnedOrRef::<OsString>, OwnedOrRef::<FileOrFolder>)> + '_> {
    let files = ...;
    self.contents
        .iter()
        .map (|(k, v)| (OwnedOrRef::Ref (k), OwnedOrRef::Ref (v))
        .chain (files
            .map (|(k, v)| (OwnedOrRef::Owned (k),
                            OwnedOrRef::Owned (v)))
}

如果FileOrFolder 可以实现ToOwned,你甚至可以使用Cow

【讨论】:

  • ty - 当我说“即时”时,我的意思是数据不是结构中的字段。它是按需生成的。如果 files 是 Folder 的成员,那将是微不足道的
  • 我明白你在做什么,你。
猜你喜欢
  • 2023-03-17
  • 1970-01-01
  • 2021-10-15
  • 1970-01-01
  • 1970-01-01
  • 2016-07-24
  • 2017-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多